示例#1
0
        private static void ReadRemarksAfterDefinition(ServiceDefinitionText source, List <string> definitionLines, Dictionary <string, FsdRemarksSection> remarksSections, List <ServiceDefinitionError> errorList)
        {
            using (var reader = new StringReader(source.Text))
            {
                string name              = null;
                var    lines             = new List <string>();
                int    lineNumber        = 0;
                int    headingLineNumber = 0;

                while (true)
                {
                    string line = reader.ReadLine();
                    lineNumber++;

                    Match match = line == null ? null : s_markdownHeading.Match(line);
                    if (match == null || match.Success)
                    {
                        if (name == null)
                        {
                            definitionLines.AddRange(lines);
                        }
                        else
                        {
                            while (lines.Count != 0 && string.IsNullOrWhiteSpace(lines[0]))
                            {
                                lines.RemoveAt(0);
                            }
                            while (lines.Count != 0 && string.IsNullOrWhiteSpace(lines[lines.Count - 1]))
                            {
                                lines.RemoveAt(lines.Count - 1);
                            }

                            var position = new ServiceDefinitionPosition(source.Name, headingLineNumber, 1);
                            if (remarksSections.ContainsKey(name))
                            {
                                errorList.Add(new ServiceDefinitionError("Duplicate remarks heading: " + name, position));
                            }
                            else
                            {
                                remarksSections.Add(name, new FsdRemarksSection(lines, position));
                            }
                        }

                        if (match == null)
                        {
                            break;
                        }

                        name              = line.Substring(match.Index + match.Length).Trim();
                        lines             = new List <string>();
                        headingLineNumber = lineNumber;
                    }
                    else
                    {
                        lines.Add(line);
                    }
                }
            }
        }
 public static SwaggerParserContext FromJson(ServiceDefinitionText serviceDefinitionText) => new SwaggerParserContext(serviceDefinitionText, isYaml: false);
 public static SwaggerParserContext FromYaml(ServiceDefinitionText serviceDefinitionText) => new SwaggerParserContext(serviceDefinitionText, isYaml: true);
示例#4
0
        /// <summary>
        /// Implements TryParseDefinition.
        /// </summary>
        protected override bool TryParseDefinitionCore(ServiceDefinitionText source, out ServiceInfo?service, out IReadOnlyList <ServiceDefinitionError> errors)
        {
            var errorList                  = new List <ServiceDefinitionError>();
            var definitionLines            = new List <string>();
            var remarksSectionsByName      = new Dictionary <string, FsdRemarksSection>(StringComparer.OrdinalIgnoreCase);
            var interleavedRemarksSections = new List <FsdRemarksSection>();

            if (!s_interleavedMarkdown.IsMatch(source.Text))
            {
                ReadRemarksAfterDefinition(source, definitionLines, remarksSectionsByName, errorList);
            }
            else
            {
                ReadInterleavedRemarks(source, definitionLines, interleavedRemarksSections);
            }

            source  = new ServiceDefinitionText(source.Name, string.Join("\n", definitionLines));
            service = null;

            try
            {
                service = FsdParsers.ParseDefinition(source, remarksSectionsByName);
                errorList.AddRange(service.GetValidationErrors());

                // check for unused remarks sections
                foreach (var remarksSectionPair in remarksSectionsByName)
                {
                    var sectionName = remarksSectionPair.Key;
                    if (service.Name != sectionName && service.FindMember(sectionName) == null)
                    {
                        errorList.Add(new ServiceDefinitionError($"Unused remarks heading: {sectionName}", remarksSectionPair.Value.Position));
                    }
                }

                // check for interleaved remarks sections
                foreach (var remarksSection in interleavedRemarksSections)
                {
                    var remarksLineNumber = remarksSection.Position.LineNumber;
                    if (remarksLineNumber > service.GetPart(ServicePartKind.Name) !.Position.LineNumber &&
                        remarksLineNumber < service.GetPart(ServicePartKind.End) !.Position.LineNumber)
                    {
                        ServiceMemberInfo targetMember = service;
                        var targetLineNumber           = 0;
                        foreach (var member in service.Members)
                        {
                            var memberLineNumber = member.GetPart(ServicePartKind.Name) !.Position.LineNumber;
                            if (remarksLineNumber > memberLineNumber && memberLineNumber > targetLineNumber)
                            {
                                targetMember     = member;
                                targetLineNumber = memberLineNumber;
                            }
                        }

                        if (targetMember.Remarks.Count == 0)
                        {
                            targetMember.Remarks = remarksSection.Lines;
                        }
                        else
                        {
                            targetMember.Remarks = targetMember.Remarks.Concat(new[] { "" }).Concat(remarksSection.Lines).ToList();
                        }
                    }
                }
            }
            catch (ParseException exception)
            {
                var expectation = exception
                                  .Result
                                  .GetNamedFailures()
                                  .Distinct()
                                  .GroupBy(x => x.Position)
                                  .Select(x => new { LineColumn = x.Key.GetLineColumn(), Names = x.Select(y => y.Name) })
                                  .OrderByDescending(x => x.LineColumn.LineNumber)
                                  .ThenByDescending(x => x.LineColumn.ColumnNumber)
                                  .First();

                int GetExpectationNameRank(string name) => name == "')'" || name == "']'" || name == "'}'" || name == "';'" ? 1 : 2;

                errorList.Add(new ServiceDefinitionError(
                                  "expected " + string.Join(" or ", expectation.Names.Distinct().OrderBy(GetExpectationNameRank).ThenBy(x => x, StringComparer.Ordinal)),
                                  new ServiceDefinitionPosition(source.Name, expectation.LineColumn.LineNumber, expectation.LineColumn.ColumnNumber)));
            }

            errors = errorList;
            return(errorList.Count == 0);
        }
示例#5
0
        private static void ReadInterleavedRemarks(ServiceDefinitionText source, List <string> definitionLines, List <FsdRemarksSection> remarksSections)
        {
            using var reader = new StringReader(source.Text);

            var remarksLines = new List <string>();
            var inFsdCode    = false;

            while (true)
            {
                var line = reader.ReadLine();
                if (line == null)
                {
                    AddRemarksSection();
                    break;
                }

                if (inFsdCode)
                {
                    if (line.StartsWith("```", StringComparison.Ordinal))
                    {
                        inFsdCode = false;
                        definitionLines.Add("");
                    }
                    else
                    {
                        definitionLines.Add(line);
                    }
                }
                else
                {
                    if (s_interleavedMarkdown.IsMatch(line))
                    {
                        AddRemarksSection();
                        inFsdCode = true;
                    }
                    else
                    {
                        remarksLines.Add(line);
                    }

                    definitionLines.Add("");
                }
            }

            void AddRemarksSection()
            {
                while (remarksLines.Count != 0 && string.IsNullOrWhiteSpace(remarksLines[0]))
                {
                    remarksLines.RemoveAt(0);
                }

                var remarksLineNumber = definitionLines.Count - remarksLines.Count;

                while (remarksLines.Count != 0 && string.IsNullOrWhiteSpace(remarksLines[remarksLines.Count - 1]))
                {
                    remarksLines.RemoveAt(remarksLines.Count - 1);
                }

                if (remarksLines.Count != 0)
                {
                    var position = new ServiceDefinitionPosition(source.Name, remarksLineNumber, 1);
                    remarksSections.Add(new FsdRemarksSection(remarksLines, position));
                    remarksLines = new List <string>();
                }
            }
        }
示例#6
0
 /// <summary>
 /// Parses the text into a service definition.
 /// </summary>
 /// <exception cref="ServiceDefinitionException">Thrown if parsing fails or the service would be invalid.</exception>
 public ServiceInfo ParseDefinition(ServiceDefinitionText text)
 {
     if (TryParseDefinition(text, out var service, out var errors))
     {
         return(service !);
     }
示例#7
0
 public Context(ServiceDefinitionText source, IReadOnlyDictionary <string, FsdRemarksSection> remarksSectionsByName)
 {
     m_source = source;
     m_remarksSectionsByName = remarksSectionsByName;
 }
示例#8
0
 public static ServiceInfo ParseDefinition(ServiceDefinitionText source, IReadOnlyDictionary <string, FsdRemarksSection> remarksSections) =>
 DefinitionParser(new Context(source, remarksSections)).Parse(source.Text);
示例#9
0
        /// <summary>
        /// Parses input files and generates output files.
        /// </summary>
        /// <param name="parser">The service parser.</param>
        /// <param name="generator">The code generator.</param>
        /// <param name="settings">The settings.</param>
        /// <returns>The number of updated files.</returns>
        public static int GenerateFiles(ServiceParser parser, CodeGenerator generator, FileGeneratorSettings settings)
        {
            if (parser == null)
            {
                throw new ArgumentNullException(nameof(parser));
            }
            if (generator == null)
            {
                throw new ArgumentNullException(nameof(generator));
            }

            if (settings.InputPath == null)
            {
                throw new ArgumentException("InputPath required.", nameof(settings));
            }
            if (settings.OutputPath == null)
            {
                throw new ArgumentException("OutputPath required.", nameof(settings));
            }

            if (settings.IndentText != null)
            {
                if (!generator.RespectsIndentText)
                {
                    throw new ArgumentException("Generator does not support IndentText setting.");
                }
                generator.IndentText = settings.IndentText;
            }

            if (settings.NewLine != null)
            {
                if (!generator.RespectsNewLine)
                {
                    throw new ArgumentException("Generator does not support NewLine setting.");
                }
                generator.NewLine = settings.NewLine;
            }

            generator.ApplySettings(settings);

            var shouldClean = settings.ShouldClean;

            if (shouldClean && !generator.HasPatternsToClean)
            {
                throw new ArgumentException("Generator does not support ShouldClean setting.");
            }

            ServiceDefinitionText input;

            if (settings.InputPath == "-")
            {
                input = new ServiceDefinitionText("", Console.In.ReadToEnd());
            }
            else
            {
                if (!File.Exists(settings.InputPath))
                {
                    throw new ApplicationException("Input file does not exist: " + settings.InputPath);
                }
                input = new ServiceDefinitionText(Path.GetFileName(settings.InputPath), File.ReadAllText(settings.InputPath));
            }

            var service = parser.ParseDefinition(input);

            if (settings.ExcludeTags != null)
            {
                foreach (var excludeTag in settings.ExcludeTags)
                {
                    service = service.ExcludeTag(excludeTag);
                }
            }

            var output = generator.GenerateOutput(service);

            var filesToWrite   = new List <CodeGenFile>();
            var namesToDelete  = new List <string>();
            var outputIsFile   = false;
            var writeToConsole = false;

            if (generator.SupportsSingleOutput &&
                !settings.OutputPath.EndsWith("/", StringComparison.Ordinal) &&
                !settings.OutputPath.EndsWith("\\", StringComparison.Ordinal) &&
                !Directory.Exists(settings.OutputPath) &&
                output.Files.Count == 1)
            {
                outputIsFile   = true;
                writeToConsole = settings.OutputPath == "-";
            }

            var notQuiet = !settings.IsQuiet && !outputIsFile;

            foreach (var file in output.Files)
            {
                var existingFilePath = outputIsFile ? settings.OutputPath : Path.Combine(settings.OutputPath, file.Name);
                if (File.Exists(existingFilePath))
                {
                    // ignore CR when comparing files
                    string Normalize(string text) => settings.IgnoreNewLines ? text.Replace("\r", "") : text;

                    if (Normalize(file.Text) != Normalize(File.ReadAllText(existingFilePath)))
                    {
                        filesToWrite.Add(file);
                        if (notQuiet)
                        {
                            Console.WriteLine("changed " + file.Name);
                        }
                    }
                }
                else
                {
                    filesToWrite.Add(file);
                    if (notQuiet)
                    {
                        Console.WriteLine("added " + file.Name);
                    }
                }
            }

            if (shouldClean && output.PatternsToClean.Count != 0)
            {
                var directoryInfo = new DirectoryInfo(settings.OutputPath);
                if (directoryInfo.Exists)
                {
                    foreach (var nameMatchingPattern in FindNamesMatchingPatterns(directoryInfo, output.PatternsToClean))
                    {
                        if (output.Files.All(x => x.Name != nameMatchingPattern))
                        {
                            namesToDelete.Add(nameMatchingPattern);
                            if (notQuiet)
                            {
                                Console.WriteLine("removed " + nameMatchingPattern);
                            }
                        }
                    }
                }
            }

            if (!settings.IsDryRun)
            {
                if (!outputIsFile && !Directory.Exists(settings.OutputPath))
                {
                    Directory.CreateDirectory(settings.OutputPath);
                }

                foreach (var fileToWrite in filesToWrite)
                {
                    var outputFilePath = outputIsFile ? settings.OutputPath : Path.Combine(settings.OutputPath, fileToWrite.Name);

                    var outputFileDirectoryPath = Path.GetDirectoryName(outputFilePath);
                    if (outputFileDirectoryPath != null && outputFileDirectoryPath != settings.OutputPath && !Directory.Exists(outputFileDirectoryPath))
                    {
                        Directory.CreateDirectory(outputFileDirectoryPath);
                    }

                    if (writeToConsole)
                    {
                        Console.Write(fileToWrite.Text);
                    }
                    else
                    {
                        File.WriteAllText(outputFilePath, fileToWrite.Text);
                    }
                }

                foreach (var nameToDelete in namesToDelete)
                {
                    File.Delete(Path.Combine(settings.OutputPath, nameToDelete));
                }
            }

            return(filesToWrite.Count + namesToDelete.Count);
        }
示例#10
0
        public async Task <ServiceResult <GenerateResponseDto> > GenerateAsync(GenerateRequestDto request, CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            try
            {
                var input     = new ServiceDefinitionText(request.Definition?.Name ?? "", request.Definition?.Text ?? "");
                var isSwagger = input.Text.StartsWith("{", StringComparison.Ordinal) || input.Text.StartsWith("swagger:", StringComparison.Ordinal);
                var service   = isSwagger ? new SwaggerParser().ParseDefinition(input) : new FsdParser().ParseDefinition(input);

                var generatorName = request.Generator?.Name;
                switch (generatorName)
                {
                case "csharp":
                    return(ServiceResult.Success(GenerateCode(() => new CSharpGenerator(), g => g.GenerateOutput(service))));

                case "javascript":
                    return(ServiceResult.Success(GenerateCode(() => new JavaScriptGenerator(), g => g.GenerateOutput(service))));

                case "typescript":
                    return(ServiceResult.Success(GenerateCode(() => new JavaScriptGenerator {
                        TypeScript = true
                    }, g => g.GenerateOutput(service))));

                case "markdown":
                    return(ServiceResult.Success(GenerateCode(() => new MarkdownGenerator(), g => g.GenerateOutput(service))));

                case "fsd":
                    return(ServiceResult.Success(GenerateCode(() => new FsdGenerator(), g => g.GenerateOutput(service))));

                case "swagger-json":
                    return(ServiceResult.Success(GenerateCode(() => new SwaggerGenerator {
                        GeneratesJson = true
                    }, g => g.GenerateOutput(service))));

                case "swagger-yaml":
                    return(ServiceResult.Success(GenerateCode(() => new SwaggerGenerator(), g => g.GenerateOutput(service))));

                case "asp-net-web-api":
                    return(ServiceResult.Success(GenerateCode(() => new AspNetGenerator(), g => g.GenerateOutput(service))));

                case "crash":
                    throw new InvalidOperationException("Intentional exception for diagnostic purposes.");

                default:
                    return(ServiceResult.Failure(ServiceErrors.CreateInvalidRequest($"Unrecognized generator '{generatorName}'.")));
                }
            }
            catch (ServiceDefinitionException exception)
            {
                var error = exception.Errors[0];
                return(ServiceResult.Success(new GenerateResponseDto
                {
                    Failure = new FailureDto
                    {
                        Message = error.Message,
                        Line = error.Position?.LineNumber,
                        Column = error.Position?.ColumnNumber,
                    },
                }));
            }
        }
        /// <summary>
        /// Implements TryParseDefinition.
        /// </summary>
        protected override bool TryParseDefinitionCore(ServiceDefinitionText text, out ServiceInfo?service, out IReadOnlyList <ServiceDefinitionError> errors)
        {
            var isFsd = new FsdParser().TryParseDefinition(text, out service, out errors);

            if (isFsd || text.Name.EndsWith(".fsd", StringComparison.OrdinalIgnoreCase))
            {
                return(isFsd);
            }

            service = null;

            if (string.IsNullOrWhiteSpace(text.Text))
            {
                errors = new[] { new ServiceDefinitionError("Service definition is missing.", new ServiceDefinitionPosition(text.Name, 1, 1)) };
                return(false);
            }

            SwaggerService       swaggerService;
            SwaggerParserContext context;

            if (!s_detectJsonRegex.IsMatch(text.Text))
            {
                // parse YAML
                var yamlDeserializer = new DeserializerBuilder()
                                       .IgnoreUnmatchedProperties()
                                       .WithNamingConvention(new OurNamingConvention())
                                       .Build();
                using (var stringReader = new StringReader(text.Text))
                {
                    try
                    {
                        swaggerService = yamlDeserializer.Deserialize <SwaggerService>(stringReader);
                    }
                    catch (YamlException exception)
                    {
                        var          errorMessage    = exception.InnerException?.Message ?? exception.Message;
                        const string errorStart      = "): ";
                        var          errorStartIndex = errorMessage.IndexOf(errorStart, StringComparison.OrdinalIgnoreCase);
                        if (errorStartIndex != -1)
                        {
                            errorMessage = errorMessage.Substring(errorStartIndex + errorStart.Length);
                        }

                        errors = new[] { new ServiceDefinitionError(errorMessage, new ServiceDefinitionPosition(text.Name, exception.End.Line, exception.End.Column)) };
                        return(false);
                    }
                }

                if (swaggerService == null)
                {
                    errors = new[] { new ServiceDefinitionError("Service definition is missing.", new ServiceDefinitionPosition(text.Name, 1, 1)) };
                    return(false);
                }

                context = SwaggerParserContext.FromYaml(text);
            }
            else
            {
                // parse JSON
                using (var stringReader = new StringReader(text.Text))
                    using (var jsonTextReader = new JsonTextReader(stringReader))
                    {
                        try
                        {
                            swaggerService = JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings).Deserialize <SwaggerService>(jsonTextReader);
                        }
                        catch (JsonException exception)
                        {
                            errors = new[] { new ServiceDefinitionError(exception.Message, new ServiceDefinitionPosition(text.Name, jsonTextReader.LineNumber, jsonTextReader.LinePosition)) };
                            return(false);
                        }

                        context = SwaggerParserContext.FromJson(text);
                    }
            }

            var conversion = SwaggerConversion.Create(swaggerService, ServiceName, context);

            service = conversion.Service;
            errors  = conversion.Errors;
            return(errors.Count == 0);
        }