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);
/// <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); }
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>(); } } }
/// <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 !); }
public Context(ServiceDefinitionText source, IReadOnlyDictionary <string, FsdRemarksSection> remarksSectionsByName) { m_source = source; m_remarksSectionsByName = remarksSectionsByName; }
public static ServiceInfo ParseDefinition(ServiceDefinitionText source, IReadOnlyDictionary <string, FsdRemarksSection> remarksSections) => DefinitionParser(new Context(source, remarksSections)).Parse(source.Text);
/// <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); }
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); }