From c02657890dbeb14361612fc43b0dfb1db5d5d219 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Nov 2023 12:01:41 -0500 Subject: [PATCH 1/7] Upgrade .NET and NuGet packages There were some breaking changes in the Word Converter packages. The only source code changes were to address the Word converter source breaks. --- tools/ExampleExtractor/ExampleExtractor.csproj | 4 ++-- tools/ExampleFormatter/ExampleFormatter.csproj | 6 +++--- tools/ExampleTester/ExampleTester.csproj | 8 ++++---- .../MarkdownConverter.Tests.csproj | 13 +++++++------ .../Converter/ConversionContext.cs | 2 +- tools/MarkdownConverter/Converter/FlatItem.cs | 4 +++- .../Converter/MarkdownSourceConverter.cs | 16 ++++++++-------- .../Converter/MarkdownSpecConverter.cs | 4 ++-- tools/MarkdownConverter/MarkdownConverter.csproj | 13 +++++++------ tools/MarkdownConverter/Spec/MarkdownSpec.cs | 4 ++-- .../MarkdownConverter/Spec/MarkdownUtilities.cs | 2 +- tools/MarkdownConverter/Spec/Reporter.cs | 2 +- tools/MarkdownConverter/Spec/SectionRef.cs | 2 +- tools/MarkdownConverter/Spec/SourceLocation.cs | 2 +- .../StandardAnchorTags/StandardAnchorTags.csproj | 3 ++- tools/Utilities/Utilities.csproj | 4 ++-- 16 files changed, 47 insertions(+), 42 deletions(-) diff --git a/tools/ExampleExtractor/ExampleExtractor.csproj b/tools/ExampleExtractor/ExampleExtractor.csproj index 11b28f3a8..5b21f414c 100644 --- a/tools/ExampleExtractor/ExampleExtractor.csproj +++ b/tools/ExampleExtractor/ExampleExtractor.csproj @@ -2,13 +2,13 @@ Exe - net6.0 + net8.0 enable enable - + diff --git a/tools/ExampleFormatter/ExampleFormatter.csproj b/tools/ExampleFormatter/ExampleFormatter.csproj index 19ca933ac..49bc6aaf1 100644 --- a/tools/ExampleFormatter/ExampleFormatter.csproj +++ b/tools/ExampleFormatter/ExampleFormatter.csproj @@ -1,14 +1,14 @@ - + Exe - net6.0 + net8.0 enable enable - + diff --git a/tools/ExampleTester/ExampleTester.csproj b/tools/ExampleTester/ExampleTester.csproj index a8dabbc54..e8c24d392 100644 --- a/tools/ExampleTester/ExampleTester.csproj +++ b/tools/ExampleTester/ExampleTester.csproj @@ -2,15 +2,15 @@ Exe - net6.0 + net8.0 enable enable - - - + + + diff --git a/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj b/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj index 870a90b4d..e46882f54 100644 --- a/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj +++ b/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj @@ -1,8 +1,9 @@  - net6.0 + net8.0 enable + enable false @@ -13,14 +14,14 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tools/MarkdownConverter/Converter/ConversionContext.cs b/tools/MarkdownConverter/Converter/ConversionContext.cs index e651fc5d8..a108699af 100644 --- a/tools/MarkdownConverter/Converter/ConversionContext.cs +++ b/tools/MarkdownConverter/Converter/ConversionContext.cs @@ -1,4 +1,4 @@ -using FSharp.Markdown; +using FSharp.Formatting.Markdown; using MarkdownConverter.Spec; using System; using System.Collections.Generic; diff --git a/tools/MarkdownConverter/Converter/FlatItem.cs b/tools/MarkdownConverter/Converter/FlatItem.cs index 1651bc9cc..d8c743a66 100644 --- a/tools/MarkdownConverter/Converter/FlatItem.cs +++ b/tools/MarkdownConverter/Converter/FlatItem.cs @@ -1,4 +1,6 @@ -using FSharp.Markdown; + + +using FSharp.Formatting.Markdown; namespace MarkdownConverter.Converter { diff --git a/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs b/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs index 43dcce77e..5f9360d94 100644 --- a/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs +++ b/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs @@ -3,7 +3,7 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using FSharp.Formatting.Common; -using FSharp.Markdown; +using FSharp.Formatting.Markdown; using MarkdownConverter.Spec; using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; @@ -306,7 +306,7 @@ IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) yield return table; } } - else if (content is MarkdownParagraph.InlineBlock inlineBlock && GetCustomBlockId(inlineBlock) is string customBlockId) + else if (content is MarkdownParagraph.InlineHtmlBlock inlineBlock && GetCustomBlockId(inlineBlock) is string customBlockId) { foreach (var element in GenerateCustomBlockElements(customBlockId, inlineBlock)) { @@ -469,7 +469,7 @@ IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) } } // Special handling for elements (typically tables) we can't represent nicely in Markdown - else if (md is MarkdownParagraph.InlineBlock block && GetCustomBlockId(block) is string customBlockId) + else if (md is MarkdownParagraph.InlineHtmlBlock block && GetCustomBlockId(block) is string customBlockId) { foreach (var element in GenerateCustomBlockElements(customBlockId, block)) { @@ -477,7 +477,7 @@ IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) } } // Ignore any other HTML comments entirely - else if (md is MarkdownParagraph.InlineBlock inlineBlock && inlineBlock.code.StartsWith(""); var match = customBlockComment.Match(block.code); @@ -550,7 +550,7 @@ IEnumerable MaybeSplitParagraph(MarkdownParagraph paragraph) yield return MarkdownParagraph.NewSpan(ListModule.OfSeq(currentSpanBody), span.range); currentSpanBody.Clear(); } - yield return MarkdownParagraph.NewCodeBlock(code.code.Substring(csharpPrefix.Length), "csharp", "", code.range); + yield return MarkdownParagraph.NewCodeBlock(code.code.Substring(csharpPrefix.Length), null, null, "csharp", "", code.range); } else { @@ -592,7 +592,7 @@ IEnumerable FlattenList(MarkdownParagraph.ListBlock md, int level) yield return subitem; } } - else if (mdp.IsTableBlock || mdp is MarkdownParagraph.InlineBlock inline && GetCustomBlockId(inline) is not null) + else if (mdp.IsTableBlock || mdp is MarkdownParagraph.InlineHtmlBlock inline && GetCustomBlockId(inline) is not null) { yield return new FlatItem(level, false, isOrdered, mdp); } @@ -961,7 +961,7 @@ IEnumerable Literal2Elements(string literal, bool isNested, bool } } - IEnumerable GenerateCustomBlockElements(string customBlockId, MarkdownParagraph.InlineBlock block) => customBlockId switch + IEnumerable GenerateCustomBlockElements(string customBlockId, MarkdownParagraph.InlineHtmlBlock block) => customBlockId switch { "multiplication" => TableHelpers.CreateMultiplicationTable(), "division" => TableHelpers.CreateDivisionTable(), diff --git a/tools/MarkdownConverter/Converter/MarkdownSpecConverter.cs b/tools/MarkdownConverter/Converter/MarkdownSpecConverter.cs index 2b5f2ccb2..e8f13eea8 100644 --- a/tools/MarkdownConverter/Converter/MarkdownSpecConverter.cs +++ b/tools/MarkdownConverter/Converter/MarkdownSpecConverter.cs @@ -81,7 +81,7 @@ private static bool FindToc(Body body, out int ifirst, out int iLast, out string for (int i = 0; i < body.ChildElements.Count; i++) { - var p = body.ChildElements.GetItem(i) as Paragraph; + var p = body.ChildElements[i] as Paragraph; if (p == null) { continue; @@ -138,7 +138,7 @@ private static bool FindToc(Body body, out int ifirst, out int iLast, out string for (int i = ifirst; i <= iLast; i++) { - var p = body.ChildElements.GetItem(i) as Paragraph; + var p = body.ChildElements[i] as Paragraph; if (p == null) { continue; diff --git a/tools/MarkdownConverter/MarkdownConverter.csproj b/tools/MarkdownConverter/MarkdownConverter.csproj index 81e40a6e3..638753c96 100644 --- a/tools/MarkdownConverter/MarkdownConverter.csproj +++ b/tools/MarkdownConverter/MarkdownConverter.csproj @@ -2,17 +2,18 @@ Exe - net6.0 - latest + net8.0 + enable + enable NU1701 - - - - + + + + diff --git a/tools/MarkdownConverter/Spec/MarkdownSpec.cs b/tools/MarkdownConverter/Spec/MarkdownSpec.cs index 4bdb8fbe1..065bb830e 100644 --- a/tools/MarkdownConverter/Spec/MarkdownSpec.cs +++ b/tools/MarkdownConverter/Spec/MarkdownSpec.cs @@ -1,5 +1,5 @@ using FSharp.Formatting.Common; -using FSharp.Markdown; +using FSharp.Formatting.Markdown; using MarkdownConverter.Converter; using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; @@ -80,7 +80,7 @@ public static MarkdownSpec ReadFiles(IEnumerable files, Reporter reporte ValidateLists(fn, text, reporter); text = BugWorkaroundEncode(text); text = RemoveBlockComments(text, Path.GetFileName(fn)); - return Tuple.Create(fn, Markdown.Parse(text)); + return Tuple.Create(fn, Markdown.Parse(text, null, null)); } }).OrderBy(tuple => GetSectionOrderingKey(tuple.Item2)).ToList(); return new MarkdownSpec(sources, reporter); diff --git a/tools/MarkdownConverter/Spec/MarkdownUtilities.cs b/tools/MarkdownConverter/Spec/MarkdownUtilities.cs index fdce1d22f..e5013f457 100644 --- a/tools/MarkdownConverter/Spec/MarkdownUtilities.cs +++ b/tools/MarkdownConverter/Spec/MarkdownUtilities.cs @@ -1,4 +1,4 @@ -using FSharp.Markdown; +using FSharp.Formatting.Markdown; namespace MarkdownConverter.Spec { diff --git a/tools/MarkdownConverter/Spec/Reporter.cs b/tools/MarkdownConverter/Spec/Reporter.cs index e5092044e..a0426ae7a 100644 --- a/tools/MarkdownConverter/Spec/Reporter.cs +++ b/tools/MarkdownConverter/Spec/Reporter.cs @@ -1,4 +1,4 @@ -using FSharp.Markdown; +using FSharp.Formatting.Markdown; using System.IO; namespace MarkdownConverter.Spec diff --git a/tools/MarkdownConverter/Spec/SectionRef.cs b/tools/MarkdownConverter/Spec/SectionRef.cs index 143da8328..443424542 100644 --- a/tools/MarkdownConverter/Spec/SectionRef.cs +++ b/tools/MarkdownConverter/Spec/SectionRef.cs @@ -1,4 +1,4 @@ -using FSharp.Markdown; +using FSharp.Formatting.Markdown; using System; using System.Linq; diff --git a/tools/MarkdownConverter/Spec/SourceLocation.cs b/tools/MarkdownConverter/Spec/SourceLocation.cs index 78591b04b..2519b26dd 100644 --- a/tools/MarkdownConverter/Spec/SourceLocation.cs +++ b/tools/MarkdownConverter/Spec/SourceLocation.cs @@ -1,5 +1,5 @@ using FSharp.Formatting.Common; -using FSharp.Markdown; +using FSharp.Formatting.Markdown; using Microsoft.FSharp.Core; namespace MarkdownConverter.Spec diff --git a/tools/StandardAnchorTags/StandardAnchorTags.csproj b/tools/StandardAnchorTags/StandardAnchorTags.csproj index 622f125f9..240e54f0c 100644 --- a/tools/StandardAnchorTags/StandardAnchorTags.csproj +++ b/tools/StandardAnchorTags/StandardAnchorTags.csproj @@ -2,8 +2,9 @@ Exe - net6.0 + net8.0 enable + enable diff --git a/tools/Utilities/Utilities.csproj b/tools/Utilities/Utilities.csproj index 50da09fa1..fdaee7f70 100644 --- a/tools/Utilities/Utilities.csproj +++ b/tools/Utilities/Utilities.csproj @@ -1,13 +1,13 @@ - net6.0 + net8.0 enable enable - + From 167dd35c692b59d101c9e6e2f56248de35e271dd Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Nov 2023 16:33:20 -0500 Subject: [PATCH 2/7] interim checkin. Revert to 4.1.0 --- .../Converter/ConversionContext.cs | 2 +- tools/MarkdownConverter/Converter/FlatItem.cs | 2 +- .../Converter/MarkdownSourceConverter.cs | 16 ++++++++-------- tools/MarkdownConverter/MarkdownConverter.csproj | 2 +- tools/MarkdownConverter/Spec/MarkdownSpec.cs | 4 ++-- .../MarkdownConverter/Spec/MarkdownUtilities.cs | 2 +- tools/MarkdownConverter/Spec/Reporter.cs | 2 +- tools/MarkdownConverter/Spec/SectionRef.cs | 2 +- tools/MarkdownConverter/Spec/SourceLocation.cs | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/MarkdownConverter/Converter/ConversionContext.cs b/tools/MarkdownConverter/Converter/ConversionContext.cs index a108699af..e651fc5d8 100644 --- a/tools/MarkdownConverter/Converter/ConversionContext.cs +++ b/tools/MarkdownConverter/Converter/ConversionContext.cs @@ -1,4 +1,4 @@ -using FSharp.Formatting.Markdown; +using FSharp.Markdown; using MarkdownConverter.Spec; using System; using System.Collections.Generic; diff --git a/tools/MarkdownConverter/Converter/FlatItem.cs b/tools/MarkdownConverter/Converter/FlatItem.cs index d8c743a66..6c216ebd2 100644 --- a/tools/MarkdownConverter/Converter/FlatItem.cs +++ b/tools/MarkdownConverter/Converter/FlatItem.cs @@ -1,6 +1,6 @@  -using FSharp.Formatting.Markdown; +using FSharp.Markdown; namespace MarkdownConverter.Converter { diff --git a/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs b/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs index 5f9360d94..43dcce77e 100644 --- a/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs +++ b/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs @@ -3,7 +3,7 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using FSharp.Formatting.Common; -using FSharp.Formatting.Markdown; +using FSharp.Markdown; using MarkdownConverter.Spec; using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; @@ -306,7 +306,7 @@ IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) yield return table; } } - else if (content is MarkdownParagraph.InlineHtmlBlock inlineBlock && GetCustomBlockId(inlineBlock) is string customBlockId) + else if (content is MarkdownParagraph.InlineBlock inlineBlock && GetCustomBlockId(inlineBlock) is string customBlockId) { foreach (var element in GenerateCustomBlockElements(customBlockId, inlineBlock)) { @@ -469,7 +469,7 @@ IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) } } // Special handling for elements (typically tables) we can't represent nicely in Markdown - else if (md is MarkdownParagraph.InlineHtmlBlock block && GetCustomBlockId(block) is string customBlockId) + else if (md is MarkdownParagraph.InlineBlock block && GetCustomBlockId(block) is string customBlockId) { foreach (var element in GenerateCustomBlockElements(customBlockId, block)) { @@ -477,7 +477,7 @@ IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) } } // Ignore any other HTML comments entirely - else if (md is MarkdownParagraph.InlineHtmlBlock inlineBlock && inlineBlock.code.StartsWith(""); var match = customBlockComment.Match(block.code); @@ -550,7 +550,7 @@ IEnumerable MaybeSplitParagraph(MarkdownParagraph paragraph) yield return MarkdownParagraph.NewSpan(ListModule.OfSeq(currentSpanBody), span.range); currentSpanBody.Clear(); } - yield return MarkdownParagraph.NewCodeBlock(code.code.Substring(csharpPrefix.Length), null, null, "csharp", "", code.range); + yield return MarkdownParagraph.NewCodeBlock(code.code.Substring(csharpPrefix.Length), "csharp", "", code.range); } else { @@ -592,7 +592,7 @@ IEnumerable FlattenList(MarkdownParagraph.ListBlock md, int level) yield return subitem; } } - else if (mdp.IsTableBlock || mdp is MarkdownParagraph.InlineHtmlBlock inline && GetCustomBlockId(inline) is not null) + else if (mdp.IsTableBlock || mdp is MarkdownParagraph.InlineBlock inline && GetCustomBlockId(inline) is not null) { yield return new FlatItem(level, false, isOrdered, mdp); } @@ -961,7 +961,7 @@ IEnumerable Literal2Elements(string literal, bool isNested, bool } } - IEnumerable GenerateCustomBlockElements(string customBlockId, MarkdownParagraph.InlineHtmlBlock block) => customBlockId switch + IEnumerable GenerateCustomBlockElements(string customBlockId, MarkdownParagraph.InlineBlock block) => customBlockId switch { "multiplication" => TableHelpers.CreateMultiplicationTable(), "division" => TableHelpers.CreateDivisionTable(), diff --git a/tools/MarkdownConverter/MarkdownConverter.csproj b/tools/MarkdownConverter/MarkdownConverter.csproj index 638753c96..0d8f506f9 100644 --- a/tools/MarkdownConverter/MarkdownConverter.csproj +++ b/tools/MarkdownConverter/MarkdownConverter.csproj @@ -12,7 +12,7 @@ - + diff --git a/tools/MarkdownConverter/Spec/MarkdownSpec.cs b/tools/MarkdownConverter/Spec/MarkdownSpec.cs index 065bb830e..4bdb8fbe1 100644 --- a/tools/MarkdownConverter/Spec/MarkdownSpec.cs +++ b/tools/MarkdownConverter/Spec/MarkdownSpec.cs @@ -1,5 +1,5 @@ using FSharp.Formatting.Common; -using FSharp.Formatting.Markdown; +using FSharp.Markdown; using MarkdownConverter.Converter; using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; @@ -80,7 +80,7 @@ public static MarkdownSpec ReadFiles(IEnumerable files, Reporter reporte ValidateLists(fn, text, reporter); text = BugWorkaroundEncode(text); text = RemoveBlockComments(text, Path.GetFileName(fn)); - return Tuple.Create(fn, Markdown.Parse(text, null, null)); + return Tuple.Create(fn, Markdown.Parse(text)); } }).OrderBy(tuple => GetSectionOrderingKey(tuple.Item2)).ToList(); return new MarkdownSpec(sources, reporter); diff --git a/tools/MarkdownConverter/Spec/MarkdownUtilities.cs b/tools/MarkdownConverter/Spec/MarkdownUtilities.cs index e5013f457..fdce1d22f 100644 --- a/tools/MarkdownConverter/Spec/MarkdownUtilities.cs +++ b/tools/MarkdownConverter/Spec/MarkdownUtilities.cs @@ -1,4 +1,4 @@ -using FSharp.Formatting.Markdown; +using FSharp.Markdown; namespace MarkdownConverter.Spec { diff --git a/tools/MarkdownConverter/Spec/Reporter.cs b/tools/MarkdownConverter/Spec/Reporter.cs index a0426ae7a..e5092044e 100644 --- a/tools/MarkdownConverter/Spec/Reporter.cs +++ b/tools/MarkdownConverter/Spec/Reporter.cs @@ -1,4 +1,4 @@ -using FSharp.Formatting.Markdown; +using FSharp.Markdown; using System.IO; namespace MarkdownConverter.Spec diff --git a/tools/MarkdownConverter/Spec/SectionRef.cs b/tools/MarkdownConverter/Spec/SectionRef.cs index 443424542..143da8328 100644 --- a/tools/MarkdownConverter/Spec/SectionRef.cs +++ b/tools/MarkdownConverter/Spec/SectionRef.cs @@ -1,4 +1,4 @@ -using FSharp.Formatting.Markdown; +using FSharp.Markdown; using System; using System.Linq; diff --git a/tools/MarkdownConverter/Spec/SourceLocation.cs b/tools/MarkdownConverter/Spec/SourceLocation.cs index 2519b26dd..78591b04b 100644 --- a/tools/MarkdownConverter/Spec/SourceLocation.cs +++ b/tools/MarkdownConverter/Spec/SourceLocation.cs @@ -1,5 +1,5 @@ using FSharp.Formatting.Common; -using FSharp.Formatting.Markdown; +using FSharp.Markdown; using Microsoft.FSharp.Core; namespace MarkdownConverter.Spec From 402e1fb2ef4588d511eeaeaa5446c81783a8c6c8 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Nov 2023 17:01:16 -0500 Subject: [PATCH 3/7] Remove unnecessary usings, filespace using Review this diff by hiding whitespace. First, remove all unnecessary usings. Second, convert all namespace declarations to file scoped namespaces. --- tools/ExampleTester/TesterConfiguration.cs | 1 - .../MarkdownSourceConverterTests.cs | 189 +- .../MarkdownSpecFileListTests.cs | 15 +- .../Converter/ConversionContext.cs | 109 +- tools/MarkdownConverter/Converter/FlatItem.cs | 29 +- .../Converter/MarkdownSourceConverter.cs | 1986 ++++++++--------- .../Converter/MarkdownSpecConverter.cs | 218 +- tools/MarkdownConverter/Converter/Needle.cs | 23 +- tools/MarkdownConverter/OptionExtensions.cs | 17 +- tools/MarkdownConverter/Program.cs | 210 +- tools/MarkdownConverter/Spec/ItalicUse.cs | 25 +- tools/MarkdownConverter/Spec/MarkdownSpec.cs | 354 ++- .../Spec/MarkdownUtilities.cs | 23 +- tools/MarkdownConverter/Spec/Reporter.cs | 162 +- tools/MarkdownConverter/Spec/SectionRef.cs | 149 +- .../MarkdownConverter/Spec/SourceLocation.cs | 125 +- tools/MarkdownConverter/Spec/Span.cs | 19 +- .../Spec/StringLengthComparer.cs | 25 +- tools/MarkdownConverter/Spec/TermRef.cs | 27 +- tools/StandardAnchorTags/GenerateGrammar.cs | 5 +- tools/StandardAnchorTags/Program.cs | 292 ++- .../ReferenceUpdateProcessor.cs | 231 +- tools/StandardAnchorTags/SectionLink.cs | 57 +- .../TocSectionNumberBuilder.cs | 362 ++- tools/Utilities/Clauses.cs | 21 +- 25 files changed, 2303 insertions(+), 2371 deletions(-) diff --git a/tools/ExampleTester/TesterConfiguration.cs b/tools/ExampleTester/TesterConfiguration.cs index 8b1a07b60..b6a0c43d9 100644 --- a/tools/ExampleTester/TesterConfiguration.cs +++ b/tools/ExampleTester/TesterConfiguration.cs @@ -1,6 +1,5 @@ using System.CommandLine; using System.CommandLine.Binding; -using System.CommandLine.Invocation; namespace ExampleTester; diff --git a/tools/MarkdownConverter.Tests/MarkdownSourceConverterTests.cs b/tools/MarkdownConverter.Tests/MarkdownSourceConverterTests.cs index 9323ecede..ceac254d7 100644 --- a/tools/MarkdownConverter.Tests/MarkdownSourceConverterTests.cs +++ b/tools/MarkdownConverter.Tests/MarkdownSourceConverterTests.cs @@ -5,116 +5,111 @@ using Org.XmlUnit; using Org.XmlUnit.Builder; using Org.XmlUnit.Diff; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Xml.Linq; using Xunit; -namespace MarkdownConverter.Tests +namespace MarkdownConverter.Tests; + +public class MarkdownSourceConverterTests { - public class MarkdownSourceConverterTests + [Theory] + [InlineData("antlr-with-line-comment")] + [InlineData("code-block-in-list")] + [InlineData("list-in-note", true)] + [InlineData("markdown-lint")] + [InlineData("note")] + [InlineData("table-in-list")] + [InlineData("table-with-pipe")] + public void SingleResourceConversion(string name, bool includeNumbering = false) { - [Theory] - [InlineData("antlr-with-line-comment")] - [InlineData("code-block-in-list")] - [InlineData("list-in-note", true)] - [InlineData("markdown-lint")] - [InlineData("note")] - [InlineData("table-in-list")] - [InlineData("table-with-pipe")] - public void SingleResourceConversion(string name, bool includeNumbering = false) - { - var reporter = new Reporter(TextWriter.Null); - var expectedXml = ReadResource($"{name}.xml"); - var spec = MarkdownSpec.ReadFiles(new[] { $"{name}.md" }, reporter, name => new StreamReader(new MemoryStream(ReadResource(name)))); + var reporter = new Reporter(TextWriter.Null); + var expectedXml = ReadResource($"{name}.xml"); + var spec = MarkdownSpec.ReadFiles(new[] { $"{name}.md" }, reporter, name => new StreamReader(new MemoryStream(ReadResource(name)))); - var resultDoc = WordprocessingDocument.Create(new MemoryStream(), WordprocessingDocumentType.Document); - resultDoc.AddMainDocumentPart(); - var source = spec.Sources.Single(); - var converter = new MarkdownSourceConverter(source.Item2, wordDocument: resultDoc, - spec: spec, - filename: $"{name}.md", - reporter); + var resultDoc = WordprocessingDocument.Create(new MemoryStream(), WordprocessingDocumentType.Document); + resultDoc.AddMainDocumentPart(); + var source = spec.Sources.Single(); + var converter = new MarkdownSourceConverter(source.Item2, wordDocument: resultDoc, + spec: spec, + filename: $"{name}.md", + reporter); - // Gather all the paragraphs together, but remove all namespaces aliases so our test documents can be simpler. - // (While a single declaration of the namespace in the root element works as a default for element names, - // it doesn't help with attribute names.) - // We optionally include the numbering details - this is basically for tests where list indentation is important. - var paragraphs = converter.Paragraphs.ToList(); - string? numberingXml = includeNumbering ? resultDoc.MainDocumentPart?.NumberingDefinitionsPart?.Numbering?.OuterXml : null; - string paragraphsXml = string.Join("\r\n", paragraphs.Select(p => p.OuterXml)); - XDocument actualXDocument = XDocument.Parse($@"{numberingXml}{paragraphsXml}"); - // Remove attributes - foreach (var element in actualXDocument.Root!.Descendants()) - { - element.Name = element.Name.LocalName; - element.Attributes().Where(attr => attr.Name.Namespace == XNamespace.Xmlns).Remove(); - element.ReplaceAttributes(element.Attributes().Select(attr => new XAttribute(attr.Name.LocalName, attr.Value))); - } - - ISource expectedDoc = Input.FromByteArray(expectedXml).Build(); - ISource actualDoc = Input.FromDocument(actualXDocument).Build(); - IDifferenceEngine diff = new DOMDifferenceEngine(); - var differences = new List(); - diff.DifferenceListener += (comparison, outcome) => differences.Add(comparison); - diff.Compare(expectedDoc, actualDoc); - Assert.Empty(differences); - Assert.Equal(0, reporter.Warnings); - Assert.Equal(0, reporter.Errors); + // Gather all the paragraphs together, but remove all namespaces aliases so our test documents can be simpler. + // (While a single declaration of the namespace in the root element works as a default for element names, + // it doesn't help with attribute names.) + // We optionally include the numbering details - this is basically for tests where list indentation is important. + var paragraphs = converter.Paragraphs.ToList(); + string? numberingXml = includeNumbering ? resultDoc.MainDocumentPart?.NumberingDefinitionsPart?.Numbering?.OuterXml : null; + string paragraphsXml = string.Join("\r\n", paragraphs.Select(p => p.OuterXml)); + XDocument actualXDocument = XDocument.Parse($@"{numberingXml}{paragraphsXml}"); + // Remove attributes + foreach (var element in actualXDocument.Root!.Descendants()) + { + element.Name = element.Name.LocalName; + element.Attributes().Where(attr => attr.Name.Namespace == XNamespace.Xmlns).Remove(); + element.ReplaceAttributes(element.Attributes().Select(attr => new XAttribute(attr.Name.LocalName, attr.Value))); } - [Theory] - [InlineData(MarkdownSourceConverter.MaximumCodeLineLength, true, 0)] - [InlineData(MarkdownSourceConverter.MaximumCodeLineLength + 1, false, 0)] - [InlineData(MarkdownSourceConverter.MaximumCodeLineLength + 1, true, 1)] - public void LongLineWarnings(int lineLength, bool code, int expectedWarningCount) - { - string prefix = code ? "```csharp\r\n" : ""; - string line = new string('x', lineLength); - string suffix = code ? "```\r\n" : ""; - string text = $"# 1 Heading\r\n{prefix}{line}\r\n{suffix}"; - var reporter = new Reporter(TextWriter.Null); - var spec = MarkdownSpec.ReadFiles(new[] { "test.md" }, reporter, _ => new StringReader(text)); - var resultDoc = WordprocessingDocument.Create(new MemoryStream(), WordprocessingDocumentType.Document); - var source = spec.Sources.Single(); - var converter = new MarkdownSourceConverter(source.Item2, wordDocument: resultDoc, - spec: spec, - filename: "test.md", - reporter); + ISource expectedDoc = Input.FromByteArray(expectedXml).Build(); + ISource actualDoc = Input.FromDocument(actualXDocument).Build(); + IDifferenceEngine diff = new DOMDifferenceEngine(); + var differences = new List(); + diff.DifferenceListener += (comparison, outcome) => differences.Add(comparison); + diff.Compare(expectedDoc, actualDoc); + Assert.Empty(differences); + Assert.Equal(0, reporter.Warnings); + Assert.Equal(0, reporter.Errors); + } - Assert.Equal(expectedWarningCount, reporter.Warnings); - } + [Theory] + [InlineData(MarkdownSourceConverter.MaximumCodeLineLength, true, 0)] + [InlineData(MarkdownSourceConverter.MaximumCodeLineLength + 1, false, 0)] + [InlineData(MarkdownSourceConverter.MaximumCodeLineLength + 1, true, 1)] + public void LongLineWarnings(int lineLength, bool code, int expectedWarningCount) + { + string prefix = code ? "```csharp\r\n" : ""; + string line = new string('x', lineLength); + string suffix = code ? "```\r\n" : ""; + string text = $"# 1 Heading\r\n{prefix}{line}\r\n{suffix}"; + var reporter = new Reporter(TextWriter.Null); + var spec = MarkdownSpec.ReadFiles(new[] { "test.md" }, reporter, _ => new StringReader(text)); + var resultDoc = WordprocessingDocument.Create(new MemoryStream(), WordprocessingDocumentType.Document); + var source = spec.Sources.Single(); + var converter = new MarkdownSourceConverter(source.Item2, wordDocument: resultDoc, + spec: spec, + filename: "test.md", + reporter); - [Theory] - [InlineData("Valid\r\n\r\n- Item 1\r\n- Item 2", 0)] - [InlineData("Valid\r\n\r\n* Item 1\r\n* Item 2", 0)] - [InlineData("Invalid\r\n- Item", 1)] - [InlineData("Invalid\r\n\r\n* Item 1\r\n- Item 2", 1)] - [InlineData("Multiple invalid\r\n- Item\r\n\r\nText\r\n- Item", 2)] - [InlineData("Valid nested list\r\n\r\n- Item 1\r\n - Item 1.1\r\n- Item 2", 0)] - [InlineData("Not a list\r\nHeading 1 | Heading 2\r\n-----------------\r\nItem1 | Item 2", 0)] - public void InvalidListStartErrors(string text, int expectedErrorCount) - { - text = $"# 1 Heading\r\n{text}"; - var reporter = new Reporter(TextWriter.Null); - var spec = MarkdownSpec.ReadFiles(new[] { "test.md" }, reporter, _ => new StringReader(text)); - Assert.Equal(expectedErrorCount, reporter.Errors); - } + Assert.Equal(expectedWarningCount, reporter.Warnings); + } - private static byte[] ReadResource(string name) + [Theory] + [InlineData("Valid\r\n\r\n- Item 1\r\n- Item 2", 0)] + [InlineData("Valid\r\n\r\n* Item 1\r\n* Item 2", 0)] + [InlineData("Invalid\r\n- Item", 1)] + [InlineData("Invalid\r\n\r\n* Item 1\r\n- Item 2", 1)] + [InlineData("Multiple invalid\r\n- Item\r\n\r\nText\r\n- Item", 2)] + [InlineData("Valid nested list\r\n\r\n- Item 1\r\n - Item 1.1\r\n- Item 2", 0)] + [InlineData("Not a list\r\nHeading 1 | Heading 2\r\n-----------------\r\nItem1 | Item 2", 0)] + public void InvalidListStartErrors(string text, int expectedErrorCount) + { + text = $"# 1 Heading\r\n{text}"; + var reporter = new Reporter(TextWriter.Null); + var spec = MarkdownSpec.ReadFiles(new[] { "test.md" }, reporter, _ => new StringReader(text)); + Assert.Equal(expectedErrorCount, reporter.Errors); + } + + private static byte[] ReadResource(string name) + { + var fullName = $"MarkdownConverter.Tests.{name}"; + var asm = typeof(MarkdownSourceConverterTests).Assembly; + using var resource = asm.GetManifestResourceStream(fullName); + if (resource is null) { - var fullName = $"MarkdownConverter.Tests.{name}"; - var asm = typeof(MarkdownSourceConverterTests).Assembly; - using var resource = asm.GetManifestResourceStream(fullName); - if (resource is null) - { - throw new ArgumentException($"Can't find resource '{fullName}'. Available resources: {string.Join(", ", asm.GetManifestResourceNames())}"); - } - using var memory = new MemoryStream(); - resource.CopyTo(memory); - return memory.ToArray(); + throw new ArgumentException($"Can't find resource '{fullName}'. Available resources: {string.Join(", ", asm.GetManifestResourceNames())}"); } + using var memory = new MemoryStream(); + resource.CopyTo(memory); + return memory.ToArray(); } } diff --git a/tools/MarkdownConverter.Tests/MarkdownSpecFileListTests.cs b/tools/MarkdownConverter.Tests/MarkdownSpecFileListTests.cs index 1c9d5039d..f641707e2 100644 --- a/tools/MarkdownConverter.Tests/MarkdownSpecFileListTests.cs +++ b/tools/MarkdownConverter.Tests/MarkdownSpecFileListTests.cs @@ -1,16 +1,13 @@ using MarkdownConverter.Spec; -using System; -using System.IO; using Xunit; -namespace MarkdownConverter.Tests +namespace MarkdownConverter.Tests; + +public class MarkdownSpecFileListTests { - public class MarkdownSpecFileListTests + [Fact] + public void EmptyListTest() { - [Fact] - public void EmptyListTest() - { - Assert.Throws(() => MarkdownSpec.ReadFiles(null, new Reporter(TextWriter.Null))); - } + Assert.Throws(() => MarkdownSpec.ReadFiles(null, new Reporter(TextWriter.Null))); } } \ No newline at end of file diff --git a/tools/MarkdownConverter/Converter/ConversionContext.cs b/tools/MarkdownConverter/Converter/ConversionContext.cs index e651fc5d8..e07caef1e 100644 --- a/tools/MarkdownConverter/Converter/ConversionContext.cs +++ b/tools/MarkdownConverter/Converter/ConversionContext.cs @@ -1,80 +1,77 @@ using FSharp.Markdown; using MarkdownConverter.Spec; -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace MarkdownConverter.Converter +namespace MarkdownConverter.Converter; + +/// +/// Maintains conversion context across multiple Markdown files. +/// +public sealed class ConversionContext { - /// - /// Maintains conversion context across multiple Markdown files. - /// - public sealed class ConversionContext - { - internal Dictionary Terms { get; } = new Dictionary(); - internal List TermKeys { get; } = new List(); - internal List Italics { get; } = new List(); - internal StrongBox MaxBookmarkId { get; } = new StrongBox(); + internal Dictionary Terms { get; } = new Dictionary(); + internal List TermKeys { get; } = new List(); + internal List Italics { get; } = new List(); + internal StrongBox MaxBookmarkId { get; } = new StrongBox(); + + private readonly List needleCounts = new List(200); - private readonly List needleCounts = new List(200); + private int sectionRefCount = 0; - private int sectionRefCount = 0; + internal SectionRef CreateSectionRef(MarkdownParagraph.Heading mdh, string filename) + { + string bookmarkName = $"_Toc{++sectionRefCount:00000}"; + return new SectionRef(mdh, filename, bookmarkName); + } - internal SectionRef CreateSectionRef(MarkdownParagraph.Heading mdh, string filename) + // TODO: Work out what this actually does. It's very confusing... + internal IEnumerable FindNeedles(IEnumerable needles0, string haystack) + { + IList needles = (needles0 as IList) ?? new List(needles0); + for (int i = 0; i < Math.Min(needleCounts.Count, needles.Count); i++) { - string bookmarkName = $"_Toc{++sectionRefCount:00000}"; - return new SectionRef(mdh, filename, bookmarkName); + needleCounts[i] = 0; } - // TODO: Work out what this actually does. It's very confusing... - internal IEnumerable FindNeedles(IEnumerable needles0, string haystack) + while (needleCounts.Count < needles.Count) { - IList needles = (needles0 as IList) ?? new List(needles0); - for (int i = 0; i < Math.Min(needleCounts.Count, needles.Count); i++) - { - needleCounts[i] = 0; - } - - while (needleCounts.Count < needles.Count) - { - needleCounts.Add(0); - } + needleCounts.Add(0); + } - var xcount = 0; - for (int ic = 0; ic < haystack.Length; ic++) + var xcount = 0; + for (int ic = 0; ic < haystack.Length; ic++) + { + var c = haystack[ic]; + xcount++; + for (int i = 0; i < needles.Count; i++) { - var c = haystack[ic]; - xcount++; - for (int i = 0; i < needles.Count; i++) + if (needles[i][needleCounts[i]] == c) { - if (needles[i][needleCounts[i]] == c) + needleCounts[i]++; + if (needleCounts[i] == needles[i].Length) { - needleCounts[i]++; - if (needleCounts[i] == needles[i].Length) + if (xcount > needleCounts[i]) { - if (xcount > needleCounts[i]) - { - yield return new Needle(-1, ic + 1 - xcount, xcount - needleCounts[i]); - } - yield return new Needle(i, ic + 1 - needleCounts[i], needleCounts[i]); - xcount = 0; - for (int j = 0; j < needles.Count; j++) - { - needleCounts[j] = 0; - } - break; + yield return new Needle(-1, ic + 1 - xcount, xcount - needleCounts[i]); } - } - else - { - needleCounts[i] = 0; + yield return new Needle(i, ic + 1 - needleCounts[i], needleCounts[i]); + xcount = 0; + for (int j = 0; j < needles.Count; j++) + { + needleCounts[j] = 0; + } + break; } } + else + { + needleCounts[i] = 0; + } } - if (xcount > 0) - { - yield return new Needle(-1, haystack.Length - xcount, xcount); - } + } + if (xcount > 0) + { + yield return new Needle(-1, haystack.Length - xcount, xcount); } } } diff --git a/tools/MarkdownConverter/Converter/FlatItem.cs b/tools/MarkdownConverter/Converter/FlatItem.cs index 6c216ebd2..90024bc8e 100644 --- a/tools/MarkdownConverter/Converter/FlatItem.cs +++ b/tools/MarkdownConverter/Converter/FlatItem.cs @@ -1,22 +1,19 @@ - +using FSharp.Markdown; -using FSharp.Markdown; +namespace MarkdownConverter.Converter; -namespace MarkdownConverter.Converter +internal sealed class FlatItem { - internal sealed class FlatItem - { - public int Level { get; } - public bool HasBullet { get; } - public bool IsBulletOrdered { get; } - public MarkdownParagraph Paragraph { get; } + public int Level { get; } + public bool HasBullet { get; } + public bool IsBulletOrdered { get; } + public MarkdownParagraph Paragraph { get; } - public FlatItem(int level, bool hasBullet, bool isBulletOrdered, MarkdownParagraph paragraph) - { - Level = level; - HasBullet = hasBullet; - IsBulletOrdered = isBulletOrdered; - Paragraph = paragraph; - } + public FlatItem(int level, bool hasBullet, bool isBulletOrdered, MarkdownParagraph paragraph) + { + Level = level; + HasBullet = hasBullet; + IsBulletOrdered = isBulletOrdered; + Paragraph = paragraph; } } diff --git a/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs b/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs index 43dcce77e..14d935131 100644 --- a/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs +++ b/tools/MarkdownConverter/Converter/MarkdownSourceConverter.cs @@ -7,1254 +7,1250 @@ using MarkdownConverter.Spec; using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml.Linq; -namespace MarkdownConverter.Converter +namespace MarkdownConverter.Converter; + +public class MarkdownSourceConverter { - public class MarkdownSourceConverter - { - /// - /// The maximum code line length that's allowed without generating a warning. - /// (80 would be normal, but 81 appears to be okay, and avoids a couple of difficult line breaks.) - /// - public const int MaximumCodeLineLength = 81; + /// + /// The maximum code line length that's allowed without generating a warning. + /// (80 would be normal, but 81 appears to be okay, and avoids a couple of difficult line breaks.) + /// + public const int MaximumCodeLineLength = 81; - private const int InitialIndentation = 540; - private const int ListLevelIndentation = 360; - private const int TableIndentation = 360; + private const int InitialIndentation = 540; + private const int ListLevelIndentation = 360; + private const int TableIndentation = 360; - private static readonly Dictionary SubscriptUnicodeToAscii = new Dictionary - { - { '\u1d62', 'i' }, - { '\u1d65', 'v' }, - { '\u2080', '0' }, - { '\u2081', '1' }, - { '\u2082', '2' }, - { '\u2083', '3' }, - { '\u2084', '4' }, - { '\u2085', '5' }, - { '\u2086', '6' }, - { '\u2087', '7' }, - { '\u2088', '8' }, - { '\u2089', '9' }, - { '\u208a', '+' }, - { '\u208b', '-' }, - { '\u2091', 'e' }, - { '\u2093', 'x' }, - }; - - private static readonly Dictionary SuperscriptUnicodeToAscii = new Dictionary - { - { '\u00aa', 'a' }, - { '\u207f', 'n' }, - { '\u00b9', '1' }, - }; - - private readonly MarkdownDocument markdownDocument; - private readonly WordprocessingDocument wordDocument; - private readonly Dictionary sections; - private readonly ConversionContext context; - private readonly string filename; - private readonly Reporter reporter; - - public IReadOnlyList Paragraphs { get; } - - public MarkdownSourceConverter( - MarkdownDocument markdownDocument, - WordprocessingDocument wordDocument, - MarkdownSpec spec, - string filename, - Reporter reporter) - { - this.markdownDocument = markdownDocument; - this.wordDocument = wordDocument; - sections = spec.Sections.ToDictionary(sr => sr.Url); - this.filename = filename; - this.reporter = reporter; - context = spec.Context; - Paragraphs = Paragraphs2Paragraphs(markdownDocument.Paragraphs).ToList(); - } + private static readonly Dictionary SubscriptUnicodeToAscii = new Dictionary + { + { '\u1d62', 'i' }, + { '\u1d65', 'v' }, + { '\u2080', '0' }, + { '\u2081', '1' }, + { '\u2082', '2' }, + { '\u2083', '3' }, + { '\u2084', '4' }, + { '\u2085', '5' }, + { '\u2086', '6' }, + { '\u2087', '7' }, + { '\u2088', '8' }, + { '\u2089', '9' }, + { '\u208a', '+' }, + { '\u208b', '-' }, + { '\u2091', 'e' }, + { '\u2093', 'x' }, + }; + + private static readonly Dictionary SuperscriptUnicodeToAscii = new Dictionary + { + { '\u00aa', 'a' }, + { '\u207f', 'n' }, + { '\u00b9', '1' }, + }; + + private readonly MarkdownDocument markdownDocument; + private readonly WordprocessingDocument wordDocument; + private readonly Dictionary sections; + private readonly ConversionContext context; + private readonly string filename; + private readonly Reporter reporter; + + public IReadOnlyList Paragraphs { get; } + + public MarkdownSourceConverter( + MarkdownDocument markdownDocument, + WordprocessingDocument wordDocument, + MarkdownSpec spec, + string filename, + Reporter reporter) + { + this.markdownDocument = markdownDocument; + this.wordDocument = wordDocument; + sections = spec.Sections.ToDictionary(sr => sr.Url); + this.filename = filename; + this.reporter = reporter; + context = spec.Context; + Paragraphs = Paragraphs2Paragraphs(markdownDocument.Paragraphs).ToList(); + } - IEnumerable Paragraphs2Paragraphs(IEnumerable pars) => - pars.SelectMany(md => Paragraph2Paragraphs(md)); + IEnumerable Paragraphs2Paragraphs(IEnumerable pars) => + pars.SelectMany(md => Paragraph2Paragraphs(md)); - IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) + IEnumerable Paragraph2Paragraphs(MarkdownParagraph md) + { + reporter.CurrentParagraph = md; + if (md.IsHeading) { - reporter.CurrentParagraph = md; - if (md.IsHeading) + var mdh = md as MarkdownParagraph.Heading; + var level = mdh.size; + var spans = mdh.body; + var sr = sections[context.CreateSectionRef(mdh, filename).Url]; + reporter.CurrentSection = sr; + var properties = new List { - var mdh = md as MarkdownParagraph.Heading; - var level = mdh.size; - var spans = mdh.body; - var sr = sections[context.CreateSectionRef(mdh, filename).Url]; - reporter.CurrentSection = sr; - var properties = new List - { - new ParagraphStyleId { Val = $"Heading{level}" } - }; - if (sr.Number is null) - { - properties.Add(new NumberingProperties(new NumberingLevelReference { Val = 0 }, new NumberingId { Val = 0 })); - } - var props = new ParagraphProperties(properties); - var p = new Paragraph { ParagraphProperties = props }; - context.MaxBookmarkId.Value += 1; - p.AppendChild(new BookmarkStart { Name = sr.BookmarkName, Id = context.MaxBookmarkId.Value.ToString() }); - p.Append(Span2Elements(MarkdownSpan.NewLiteral(sr.TitleWithoutNumber, FSharpOption.None))); - p.AppendChild(new BookmarkEnd { Id = context.MaxBookmarkId.Value.ToString() }); - yield return p; - - var i = sr.Url.IndexOf("#"); - string currentSection = $"{sr.Url.Substring(0, i)} {new string('#', level)} {sr.Title} [{sr.Number}]"; - reporter.Log(currentSection); - yield break; - } - - else if (md.IsParagraph) + new ParagraphStyleId { Val = $"Heading{level}" } + }; + if (sr.Number is null) { - var mdp = md as MarkdownParagraph.Paragraph; - var spans = mdp.body; - yield return new Paragraph(Spans2Elements(spans)); - yield break; + properties.Add(new NumberingProperties(new NumberingLevelReference { Val = 0 }, new NumberingId { Val = 0 })); } + var props = new ParagraphProperties(properties); + var p = new Paragraph { ParagraphProperties = props }; + context.MaxBookmarkId.Value += 1; + p.AppendChild(new BookmarkStart { Name = sr.BookmarkName, Id = context.MaxBookmarkId.Value.ToString() }); + p.Append(Span2Elements(MarkdownSpan.NewLiteral(sr.TitleWithoutNumber, FSharpOption.None))); + p.AppendChild(new BookmarkEnd { Id = context.MaxBookmarkId.Value.ToString() }); + yield return p; + + var i = sr.Url.IndexOf("#"); + string currentSection = $"{sr.Url.Substring(0, i)} {new string('#', level)} {sr.Title} [{sr.Number}]"; + reporter.Log(currentSection); + yield break; + } - else if (md.IsQuotedBlock) + else if (md.IsParagraph) + { + var mdp = md as MarkdownParagraph.Paragraph; + var spans = mdp.body; + yield return new Paragraph(Spans2Elements(spans)); + yield break; + } + + else if (md.IsQuotedBlock) + { + // Keep track of which list numbering schemes we've already indented. + // Lists are flattened into multiple paragraphs, but all paragraphs within one list + // keep the same numbering scheme, and we only want to increase the indentation level once. + var indentedLists = new HashSet(); + + var mdq = md as MarkdownParagraph.QuotedBlock; + // TODO: Actually make this a block quote. + // We're now indenting, which is a start... a proper block would be nicer though. + foreach (var element in mdq.paragraphs.SelectMany(Paragraph2Paragraphs)) { - // Keep track of which list numbering schemes we've already indented. - // Lists are flattened into multiple paragraphs, but all paragraphs within one list - // keep the same numbering scheme, and we only want to increase the indentation level once. - var indentedLists = new HashSet(); - - var mdq = md as MarkdownParagraph.QuotedBlock; - // TODO: Actually make this a block quote. - // We're now indenting, which is a start... a proper block would be nicer though. - foreach (var element in mdq.paragraphs.SelectMany(Paragraph2Paragraphs)) + if (element is Paragraph paragraph) { - if (element is Paragraph paragraph) - { - paragraph.ParagraphProperties ??= new ParagraphProperties(); + paragraph.ParagraphProperties ??= new ParagraphProperties(); - // Indentation in lists is controlled by numbering properties. - // Each list creates its own numbering, with a set of properties for each numbering level. - // If there's a list within a note, we need to increase the indentation of each numbering level. - if (paragraph.ParagraphProperties.NumberingProperties?.NumberingId?.Val?.Value is int numberingId) + // Indentation in lists is controlled by numbering properties. + // Each list creates its own numbering, with a set of properties for each numbering level. + // If there's a list within a note, we need to increase the indentation of each numbering level. + if (paragraph.ParagraphProperties.NumberingProperties?.NumberingId?.Val?.Value is int numberingId) + { + if (indentedLists.Add(numberingId)) { - if (indentedLists.Add(numberingId)) + var numbering = wordDocument.MainDocumentPart.NumberingDefinitionsPart.Numbering.OfType().First(ni => ni.NumberID.Value == numberingId); + var abstractNumberingId = numbering.AbstractNumId.Val; + var abstractNumbering = wordDocument.MainDocumentPart.NumberingDefinitionsPart.Numbering.OfType().FirstOrDefault(ani => ani.AbstractNumberId.Value == abstractNumberingId); + foreach (var level in abstractNumbering.OfType()) { - var numbering = wordDocument.MainDocumentPart.NumberingDefinitionsPart.Numbering.OfType().First(ni => ni.NumberID.Value == numberingId); - var abstractNumberingId = numbering.AbstractNumId.Val; - var abstractNumbering = wordDocument.MainDocumentPart.NumberingDefinitionsPart.Numbering.OfType().FirstOrDefault(ani => ani.AbstractNumberId.Value == abstractNumberingId); - foreach (var level in abstractNumbering.OfType()) - { - var paragraphProperties = level.GetFirstChild(); - int indentation = int.Parse(paragraphProperties.Indentation.Left.Value); - paragraphProperties.Indentation.Left.Value = (indentation + InitialIndentation).ToString(); - } + var paragraphProperties = level.GetFirstChild(); + int indentation = int.Parse(paragraphProperties.Indentation.Left.Value); + paragraphProperties.Indentation.Left.Value = (indentation + InitialIndentation).ToString(); } } - else - { - paragraph.ParagraphProperties.Indentation = new Indentation { Left = InitialIndentation.ToString() }; - } - yield return paragraph; } - else if (element is Table table) + else { - if (table.ElementAt(0) is TableProperties tableProperties) - { - tableProperties.TableIndentation ??= new TableIndentation(); - // TODO: This will be incorrect if we ever have a table in a list in a note. - // Let's just try not to do that. - tableProperties.TableIndentation.Width = InitialIndentation; - yield return table; - } - else - { - reporter.Error("MD31", $"Table in quoted block does not start with table properties"); - } + paragraph.ParagraphProperties.Indentation = new Indentation { Left = InitialIndentation.ToString() }; + } + yield return paragraph; + } + else if (element is Table table) + { + if (table.ElementAt(0) is TableProperties tableProperties) + { + tableProperties.TableIndentation ??= new TableIndentation(); + // TODO: This will be incorrect if we ever have a table in a list in a note. + // Let's just try not to do that. + tableProperties.TableIndentation.Width = InitialIndentation; + yield return table; } else { - reporter.Error("MD30", $"Unhandled element type in quoted block: {element.GetType()}"); + reporter.Error("MD31", $"Table in quoted block does not start with table properties"); } } - yield break; + else + { + reporter.Error("MD30", $"Unhandled element type in quoted block: {element.GetType()}"); + } } + yield break; + } + + else if (md is MarkdownParagraph.ListBlock mdl) + { + mdl = MaybeRewriteListBlock(mdl); + var flat = FlattenList(mdl); - else if (md is MarkdownParagraph.ListBlock mdl) + // Let's figure out what kind of list it is - ordered or unordered? nested? + var format0 = new[] { "1", "1", "1", "1" }; + foreach (var item in flat) { - mdl = MaybeRewriteListBlock(mdl); - var flat = FlattenList(mdl); + format0[item.Level] = (item.IsBulletOrdered ? "1" : "o"); + } - // Let's figure out what kind of list it is - ordered or unordered? nested? - var format0 = new[] { "1", "1", "1", "1" }; - foreach (var item in flat) - { - format0[item.Level] = (item.IsBulletOrdered ? "1" : "o"); - } + var format = string.Join("", format0); - var format = string.Join("", format0); + var numberingPart = wordDocument.MainDocumentPart.NumberingDefinitionsPart ?? wordDocument.MainDocumentPart.AddNewPart("NumberingDefinitionsPart001"); + if (numberingPart.Numbering == null) + { + numberingPart.Numbering = new Numbering(); + } - var numberingPart = wordDocument.MainDocumentPart.NumberingDefinitionsPart ?? wordDocument.MainDocumentPart.AddNewPart("NumberingDefinitionsPart001"); - if (numberingPart.Numbering == null) + Func createLevel = (level, isOrdered) => + { + var numformat = NumberFormatValues.Bullet; + var levelText = new[] { "·", "o", "·", "o" }[level]; + if (isOrdered && level == 0) { numformat = NumberFormatValues.Decimal; levelText = "%1."; } + if (isOrdered && level == 1) { numformat = NumberFormatValues.LowerLetter; levelText = "%2."; } + if (isOrdered && level == 2) { numformat = NumberFormatValues.LowerRoman; levelText = "%3."; } + if (isOrdered && level == 3) { numformat = NumberFormatValues.LowerRoman; levelText = "%4."; } + var r = new Level { LevelIndex = level }; + r.Append(new StartNumberingValue { Val = 1 }); + r.Append(new NumberingFormat { Val = numformat }); + r.Append(new LevelText { Val = levelText }); + r.Append(new ParagraphProperties(new Indentation { Left = (InitialIndentation + ListLevelIndentation * level).ToString(), Hanging = ListLevelIndentation.ToString() })); + if (levelText == "·") { - numberingPart.Numbering = new Numbering(); + r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi = "Symbol", EastAsia = "Times new Roman", ComplexScript = "Times new Roman" })); } - Func createLevel = (level, isOrdered) => + if (levelText == "o") { - var numformat = NumberFormatValues.Bullet; - var levelText = new[] { "·", "o", "·", "o" }[level]; - if (isOrdered && level == 0) { numformat = NumberFormatValues.Decimal; levelText = "%1."; } - if (isOrdered && level == 1) { numformat = NumberFormatValues.LowerLetter; levelText = "%2."; } - if (isOrdered && level == 2) { numformat = NumberFormatValues.LowerRoman; levelText = "%3."; } - if (isOrdered && level == 3) { numformat = NumberFormatValues.LowerRoman; levelText = "%4."; } - var r = new Level { LevelIndex = level }; - r.Append(new StartNumberingValue { Val = 1 }); - r.Append(new NumberingFormat { Val = numformat }); - r.Append(new LevelText { Val = levelText }); - r.Append(new ParagraphProperties(new Indentation { Left = (InitialIndentation + ListLevelIndentation * level).ToString(), Hanging = ListLevelIndentation.ToString() })); - if (levelText == "·") - { - r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi = "Symbol", EastAsia = "Times new Roman", ComplexScript = "Times new Roman" })); - } - - if (levelText == "o") - { - r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Courier New", HighAnsi = "Courier New", ComplexScript = "Courier New" })); - } + r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Courier New", HighAnsi = "Courier New", ComplexScript = "Courier New" })); + } - return r; - }; - var level0 = createLevel(0, format[0] == '1'); - var level1 = createLevel(1, format[1] == '1'); - var level2 = createLevel(2, format[2] == '1'); - var level3 = createLevel(3, format[3] == '1'); + return r; + }; + var level0 = createLevel(0, format[0] == '1'); + var level1 = createLevel(1, format[1] == '1'); + var level2 = createLevel(2, format[2] == '1'); + var level3 = createLevel(3, format[3] == '1'); - var abstracts = numberingPart.Numbering.OfType().Select(an => an.AbstractNumberId.Value).ToList(); - var aid = (abstracts.Count == 0 ? 1 : abstracts.Max() + 1); - var aabstract = new AbstractNum(new MultiLevelType() { Val = MultiLevelValues.Multilevel }, level0, level1, level2, level3) { AbstractNumberId = aid }; - numberingPart.Numbering.InsertAt(aabstract, 0); + var abstracts = numberingPart.Numbering.OfType().Select(an => an.AbstractNumberId.Value).ToList(); + var aid = (abstracts.Count == 0 ? 1 : abstracts.Max() + 1); + var aabstract = new AbstractNum(new MultiLevelType() { Val = MultiLevelValues.Multilevel }, level0, level1, level2, level3) { AbstractNumberId = aid }; + numberingPart.Numbering.InsertAt(aabstract, 0); - var instances = numberingPart.Numbering.OfType().Select(ni => ni.NumberID.Value); - var nid = (instances.Count() == 0 ? 1 : instances.Max() + 1); - var numInstance = new NumberingInstance(new AbstractNumId { Val = aid }) { NumberID = nid }; - numberingPart.Numbering.AppendChild(numInstance); + var instances = numberingPart.Numbering.OfType().Select(ni => ni.NumberID.Value); + var nid = (instances.Count() == 0 ? 1 : instances.Max() + 1); + var numInstance = new NumberingInstance(new AbstractNumId { Val = aid }) { NumberID = nid }; + numberingPart.Numbering.AppendChild(numInstance); - // We'll also figure out the indentation(for the benefit of those paragraphs that should be - // indented with the list but aren't numbered). The indentation is generated by the createLevel delegate. - Func calcIndent = level => (InitialIndentation + level * ListLevelIndentation).ToString(); + // We'll also figure out the indentation(for the benefit of those paragraphs that should be + // indented with the list but aren't numbered). The indentation is generated by the createLevel delegate. + Func calcIndent = level => (InitialIndentation + level * ListLevelIndentation).ToString(); - foreach (var item in flat) + foreach (var item in flat) + { + var content = item.Paragraph; + if (content.IsParagraph || content.IsSpan) { - var content = item.Paragraph; - if (content.IsParagraph || content.IsSpan) + var spans = (content.IsParagraph ? (content as MarkdownParagraph.Paragraph).body : (content as MarkdownParagraph.Span).body); + if (item.HasBullet) + { + yield return new Paragraph(Spans2Elements(spans, inList: true)) { ParagraphProperties = new ParagraphProperties(new NumberingProperties(new ParagraphStyleId { Val = "ListParagraph" }, new NumberingLevelReference { Val = item.Level }, new NumberingId { Val = nid })) }; + } + else { - var spans = (content.IsParagraph ? (content as MarkdownParagraph.Paragraph).body : (content as MarkdownParagraph.Span).body); - if (item.HasBullet) + yield return new Paragraph(Spans2Elements(spans, inList: true)) { ParagraphProperties = new ParagraphProperties(new Indentation { Left = calcIndent(item.Level) }) }; + } + } + else if (content.IsQuotedBlock || content.IsCodeBlock) + { + foreach (var p in Paragraph2Paragraphs(content)) + { + var props = p.GetFirstChild(); + if (props == null) { - yield return new Paragraph(Spans2Elements(spans, inList: true)) { ParagraphProperties = new ParagraphProperties(new NumberingProperties(new ParagraphStyleId { Val = "ListParagraph" }, new NumberingLevelReference { Val = item.Level }, new NumberingId { Val = nid })) }; + props = new ParagraphProperties(); + p.InsertAt(props, 0); } - else + var indent = props?.GetFirstChild(); + if (indent == null) { - yield return new Paragraph(Spans2Elements(spans, inList: true)) { ParagraphProperties = new ParagraphProperties(new Indentation { Left = calcIndent(item.Level) }) }; + indent = new Indentation(); + props.Append(indent); } + indent.Left = calcIndent(item.Level); + yield return p; } - else if (content.IsQuotedBlock || content.IsCodeBlock) + } + else if (content.IsTableBlock) + { + foreach (var p in Paragraph2Paragraphs(content)) { - foreach (var p in Paragraph2Paragraphs(content)) + var table = p as Table; + if (table == null) { - var props = p.GetFirstChild(); - if (props == null) - { - props = new ParagraphProperties(); - p.InsertAt(props, 0); - } - var indent = props?.GetFirstChild(); - if (indent == null) - { - indent = new Indentation(); - props.Append(indent); - } - indent.Left = calcIndent(item.Level); yield return p; + continue; } - } - else if (content.IsTableBlock) - { - foreach (var p in Paragraph2Paragraphs(content)) - { - var table = p as Table; - if (table == null) - { - yield return p; - continue; - } - var tprops = table.GetFirstChild(); - var tindent = tprops?.GetFirstChild(); - if (tindent == null) - { - throw new Exception("Ooops! Table is missing indentation"); - } - - tindent.Width = int.Parse(calcIndent(item.Level)); - yield return table; - } - } - else if (content is MarkdownParagraph.InlineBlock inlineBlock && GetCustomBlockId(inlineBlock) is string customBlockId) - { - foreach (var element in GenerateCustomBlockElements(customBlockId, inlineBlock)) + var tprops = table.GetFirstChild(); + var tindent = tprops?.GetFirstChild(); + if (tindent == null) { - yield return element; + throw new Exception("Ooops! Table is missing indentation"); } + + tindent.Width = int.Parse(calcIndent(item.Level)); + yield return table; } - else + } + else if (content is MarkdownParagraph.InlineBlock inlineBlock && GetCustomBlockId(inlineBlock) is string customBlockId) + { + foreach (var element in GenerateCustomBlockElements(customBlockId, inlineBlock)) { - reporter.Error("MD08", $"Unexpected item in list '{content.GetType().Name}'"); + yield return element; } } + else + { + reporter.Error("MD08", $"Unexpected item in list '{content.GetType().Name}'"); + } } + } - else if (md.IsCodeBlock) + else if (md.IsCodeBlock) + { + var mdc = md as MarkdownParagraph.CodeBlock; + var code = mdc.code; + var lang = mdc.language; + code = BugWorkaroundDecode(code); + var runs = new List(); + var onFirstLine = true; + IEnumerable lines; + switch (lang) { - var mdc = md as MarkdownParagraph.CodeBlock; - var code = mdc.code; - var lang = mdc.language; - code = BugWorkaroundDecode(code); - var runs = new List(); - var onFirstLine = true; - IEnumerable lines; - switch (lang) + case "csharp": + case "c#": + case "cs": + lines = Colorize.CSharp(code); + break; + case "vb": + case "vbnet": + case "vb.net": + lines = Colorize.VB(code); + break; + case "": + case "console": + case "xml": + lines = Colorize.PlainText(code); + break; + case "ANTLR": + lines = Colorize.PlainText(code); + break; + default: + reporter.Error("MD09", $"unrecognized language {lang}"); + lines = Colorize.PlainText(code); + break; + } + + foreach (var line in lines) + { + int lineLength = line.Words.Sum(w => w.Text.Length); + if (lineLength > MaximumCodeLineLength) { - case "csharp": - case "c#": - case "cs": - lines = Colorize.CSharp(code); - break; - case "vb": - case "vbnet": - case "vb.net": - lines = Colorize.VB(code); - break; - case "": - case "console": - case "xml": - lines = Colorize.PlainText(code); - break; - case "ANTLR": - lines = Colorize.PlainText(code); - break; - default: - reporter.Error("MD09", $"unrecognized language {lang}"); - lines = Colorize.PlainText(code); - break; + reporter.Warning("MD32", $"Line length {lineLength} > maximum {MaximumCodeLineLength}"); } - foreach (var line in lines) + if (onFirstLine) { - int lineLength = line.Words.Sum(w => w.Text.Length); - if (lineLength > MaximumCodeLineLength) - { - reporter.Warning("MD32", $"Line length {lineLength} > maximum {MaximumCodeLineLength}"); - } + onFirstLine = false; + } + else + { + runs.Add(new Run(new Break())); + } - if (onFirstLine) + foreach (var word in line.Words) + { + var run = new Run(); + var props = new RunProperties(); + if (word.Red != 0 || word.Green != 0 || word.Blue != 0) { - onFirstLine = false; + props.Append(new Color { Val = $"{word.Red:X2}{word.Green:X2}{word.Blue:X2}" }); } - else + + if (word.IsItalic) { - runs.Add(new Run(new Break())); + props.Append(new Italic()); } - foreach (var word in line.Words) + if (props.HasChildren) { - var run = new Run(); - var props = new RunProperties(); - if (word.Red != 0 || word.Green != 0 || word.Blue != 0) - { - props.Append(new Color { Val = $"{word.Red:X2}{word.Green:X2}{word.Blue:X2}" }); - } - - if (word.IsItalic) - { - props.Append(new Italic()); - } - - if (props.HasChildren) - { - run.Append(props); - } - - run.Append(new Text(word.Text) { Space = SpaceProcessingModeValues.Preserve }); - runs.Add(run); + run.Append(props); } + + run.Append(new Text(word.Text) { Space = SpaceProcessingModeValues.Preserve }); + runs.Add(run); } - var p = new Paragraph() { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "Code" }) }; - p.Append(runs); - yield return p; } + var p = new Paragraph() { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "Code" }) }; + p.Append(runs); + yield return p; + } - else if (md.IsTableBlock) + else if (md.IsTableBlock) + { + var mdt = md as MarkdownParagraph.TableBlock; + var header = mdt.headers.Option(); + var align = mdt.alignments; + var rows = mdt.rows; + var table = TableHelpers.CreateTable(); + if (header == null) { - var mdt = md as MarkdownParagraph.TableBlock; - var header = mdt.headers.Option(); - var align = mdt.alignments; - var rows = mdt.rows; - var table = TableHelpers.CreateTable(); - if (header == null) - { - reporter.Error("MD10", "Github requires all tables to have header rows"); - } + reporter.Error("MD10", "Github requires all tables to have header rows"); + } - if (!header.Any(cell => cell.Length > 0)) + if (!header.Any(cell => cell.Length > 0)) + { + header = null; // even if Github requires an empty header, we can at least cull it from Docx + } + + var ncols = align.Length; + for (int irow = -1; irow < rows.Length; irow++) + { + if (irow == -1 && header == null) { - header = null; // even if Github requires an empty header, we can at least cull it from Docx + continue; } - var ncols = align.Length; - for (int irow = -1; irow < rows.Length; irow++) + var mdrow = (irow == -1 ? header : rows[irow]); + var row = new TableRow(); + for (int icol = 0; icol < Math.Min(ncols, mdrow.Length); icol++) { - if (irow == -1 && header == null) - { - continue; - } - - var mdrow = (irow == -1 ? header : rows[irow]); - var row = new TableRow(); - for (int icol = 0; icol < Math.Min(ncols, mdrow.Length); icol++) + var mdcell = mdrow[icol]; + var cell = new TableCell(); + var pars = Paragraphs2Paragraphs(mdcell).ToList(); + for (int ip = 0; ip < pars.Count; ip++) { - var mdcell = mdrow[icol]; - var cell = new TableCell(); - var pars = Paragraphs2Paragraphs(mdcell).ToList(); - for (int ip = 0; ip < pars.Count; ip++) + var p = pars[ip] as Paragraph; + if (p == null) { - var p = pars[ip] as Paragraph; - if (p == null) - { - cell.Append(pars[ip]); - continue; - } - var props = new ParagraphProperties(new ParagraphStyleId { Val = "TableCellNormal" }); - if (align[icol].IsAlignCenter) - { - props.Append(new Justification { Val = JustificationValues.Center }); - } - - if (align[icol].IsAlignRight) - { - props.Append(new Justification { Val = JustificationValues.Right }); - } - - p.InsertAt(props, 0); cell.Append(pars[ip]); + continue; } - if (pars.Count == 0) + var props = new ParagraphProperties(new ParagraphStyleId { Val = "TableCellNormal" }); + if (align[icol].IsAlignCenter) { - cell.Append(new Paragraph(new ParagraphProperties(new SpacingBetweenLines { After = "0" }), new Run(new Text("")))); + props.Append(new Justification { Val = JustificationValues.Center }); } - row.Append(cell); + if (align[icol].IsAlignRight) + { + props.Append(new Justification { Val = JustificationValues.Right }); + } + + p.InsertAt(props, 0); + cell.Append(pars[ip]); } - table.Append(row); - } - foreach (var element in TableHelpers.CreateTableElements(table)) - { - yield return element; - } - } - // Special handling for elements (typically tables) we can't represent nicely in Markdown - else if (md is MarkdownParagraph.InlineBlock block && GetCustomBlockId(block) is string customBlockId) - { - foreach (var element in GenerateCustomBlockElements(customBlockId, block)) - { - yield return element; + if (pars.Count == 0) + { + cell.Append(new Paragraph(new ParagraphProperties(new SpacingBetweenLines { After = "0" }), new Run(new Text("")))); + } + + row.Append(cell); } + table.Append(row); } - // Ignore any other HTML comments entirely - else if (md is MarkdownParagraph.InlineBlock inlineBlock && inlineBlock.code.StartsWith(""); - var match = customBlockComment.Match(block.code); - return match.Success ? match.Groups[1].Value : null; + yield break; } + else + { + reporter.Error("MD11", $"Unrecognized markdown element {md.GetType().Name}"); + yield return new Paragraph(new Run(new Text($"[{md.GetType().Name}]"))); + } + } - IEnumerable FlattenList(MarkdownParagraph.ListBlock md) + static string GetCustomBlockId(MarkdownParagraph.InlineBlock block) + { + Regex customBlockComment = new Regex(@"^"); + var match = customBlockComment.Match(block.code); + return match.Success ? match.Groups[1].Value : null; + } + + IEnumerable FlattenList(MarkdownParagraph.ListBlock md) + { + var flat = FlattenList(md, 0).ToList(); + var isOrdered = new Dictionary(); + foreach (var item in flat) { - var flat = FlattenList(md, 0).ToList(); - var isOrdered = new Dictionary(); - foreach (var item in flat) + var level = item.Level; + var isItemOrdered = item.IsBulletOrdered; + var content = item.Paragraph; + if (isOrdered.ContainsKey(level) && isOrdered[level] != isItemOrdered) { - var level = item.Level; - var isItemOrdered = item.IsBulletOrdered; - var content = item.Paragraph; - if (isOrdered.ContainsKey(level) && isOrdered[level] != isItemOrdered) - { - reporter.Error("MD12", "List can't mix ordered and unordered items at same level"); - } + reporter.Error("MD12", "List can't mix ordered and unordered items at same level"); + } - isOrdered[level] = isItemOrdered; - if (level > 3) - { - reporter.Error("MD13", "Can't have more than 4 levels in a list"); - } + isOrdered[level] = isItemOrdered; + if (level > 3) + { + reporter.Error("MD13", "Can't have more than 4 levels in a list"); } - return flat; } + return flat; + } - // Workaround for https://github.com/dotnet/csharpstandard/issues/440 - // Code blocks in list items are parsed as InlineCode in a span instead of CodeBlock, - // so we detect that and rewrite it. - MarkdownParagraph.ListBlock MaybeRewriteListBlock(MarkdownParagraph.ListBlock listBlock) - { - // Regardless of the source, the Markdown parser rewrites the inline code to use the environment newline. - string csharpPrefix = "csharp" + Environment.NewLine; + // Workaround for https://github.com/dotnet/csharpstandard/issues/440 + // Code blocks in list items are parsed as InlineCode in a span instead of CodeBlock, + // so we detect that and rewrite it. + MarkdownParagraph.ListBlock MaybeRewriteListBlock(MarkdownParagraph.ListBlock listBlock) + { + // Regardless of the source, the Markdown parser rewrites the inline code to use the environment newline. + string csharpPrefix = "csharp" + Environment.NewLine; - var items = listBlock.items.Select(paragraphList => paragraphList.SelectMany(MaybeSplitParagraph)); - var fsharpItems = ListModule.OfSeq(items.Select(item => ListModule.OfSeq(item))); - return (MarkdownParagraph.ListBlock) MarkdownParagraph.NewListBlock(listBlock.kind, fsharpItems, listBlock.range); + var items = listBlock.items.Select(paragraphList => paragraphList.SelectMany(MaybeSplitParagraph)); + var fsharpItems = ListModule.OfSeq(items.Select(item => ListModule.OfSeq(item))); + return (MarkdownParagraph.ListBlock) MarkdownParagraph.NewListBlock(listBlock.kind, fsharpItems, listBlock.range); - IEnumerable MaybeSplitParagraph(MarkdownParagraph paragraph) + IEnumerable MaybeSplitParagraph(MarkdownParagraph paragraph) + { + if (paragraph is not MarkdownParagraph.Span span) { - if (paragraph is not MarkdownParagraph.Span span) - { - yield return paragraph; - yield break; - } - var currentSpanBody = new List(); - // Note: the ranges in these paragraphs will be messed up, but that will rarely matter. - // TODO: Maybe trim whitespace from the end of a literal before the block, and from the start of a literal - // after the block? Otherwise they include blank lines. This looks okay, but may not be "strictly" ideal. - foreach (var item in span.body) + yield return paragraph; + yield break; + } + var currentSpanBody = new List(); + // Note: the ranges in these paragraphs will be messed up, but that will rarely matter. + // TODO: Maybe trim whitespace from the end of a literal before the block, and from the start of a literal + // after the block? Otherwise they include blank lines. This looks okay, but may not be "strictly" ideal. + foreach (var item in span.body) + { + if (item is MarkdownSpan.InlineCode code && code.code.StartsWith(csharpPrefix)) { - if (item is MarkdownSpan.InlineCode code && code.code.StartsWith(csharpPrefix)) - { - if (currentSpanBody.Count > 0) - { - yield return MarkdownParagraph.NewSpan(ListModule.OfSeq(currentSpanBody), span.range); - currentSpanBody.Clear(); - } - yield return MarkdownParagraph.NewCodeBlock(code.code.Substring(csharpPrefix.Length), "csharp", "", code.range); - } - else + if (currentSpanBody.Count > 0) { - currentSpanBody.Add(item); + yield return MarkdownParagraph.NewSpan(ListModule.OfSeq(currentSpanBody), span.range); + currentSpanBody.Clear(); } + yield return MarkdownParagraph.NewCodeBlock(code.code.Substring(csharpPrefix.Length), "csharp", "", code.range); } - if (currentSpanBody.Count > 0) + else { - yield return MarkdownParagraph.NewSpan(ListModule.OfSeq(currentSpanBody), span.range); + currentSpanBody.Add(item); } } + if (currentSpanBody.Count > 0) + { + yield return MarkdownParagraph.NewSpan(ListModule.OfSeq(currentSpanBody), span.range); + } } + } - IEnumerable FlattenList(MarkdownParagraph.ListBlock md, int level) + IEnumerable FlattenList(MarkdownParagraph.ListBlock md, int level) + { + var isOrdered = md.kind.IsOrdered; + var items = md.items; + foreach (var mdpars in items) { - var isOrdered = md.kind.IsOrdered; - var items = md.items; - foreach (var mdpars in items) + var isFirstParagraph = true; + foreach (var mdp in mdpars) { - var isFirstParagraph = true; - foreach (var mdp in mdpars) - { - var wasFirstParagraph = isFirstParagraph; isFirstParagraph = false; + var wasFirstParagraph = isFirstParagraph; isFirstParagraph = false; - if (mdp.IsParagraph || mdp.IsSpan) - { - var mdp1 = mdp; - var buglevel = BugWorkaroundIndent(ref mdp1, level); - yield return new FlatItem(buglevel, wasFirstParagraph, isOrdered, mdp1); - } - else if (mdp.IsQuotedBlock || mdp.IsCodeBlock) - { - yield return new FlatItem(level, false, isOrdered, mdp); - } - else if (mdp.IsListBlock) - { - foreach (var subitem in FlattenList(mdp as MarkdownParagraph.ListBlock, level + 1)) - { - yield return subitem; - } - } - else if (mdp.IsTableBlock || mdp is MarkdownParagraph.InlineBlock inline && GetCustomBlockId(inline) is not null) - { - yield return new FlatItem(level, false, isOrdered, mdp); - } - else + if (mdp.IsParagraph || mdp.IsSpan) + { + var mdp1 = mdp; + var buglevel = BugWorkaroundIndent(ref mdp1, level); + yield return new FlatItem(buglevel, wasFirstParagraph, isOrdered, mdp1); + } + else if (mdp.IsQuotedBlock || mdp.IsCodeBlock) + { + yield return new FlatItem(level, false, isOrdered, mdp); + } + else if (mdp.IsListBlock) + { + foreach (var subitem in FlattenList(mdp as MarkdownParagraph.ListBlock, level + 1)) { - reporter.Error("MD14", $"nothing fancy allowed in lists - specifically not '{mdp.GetType().Name}'"); + yield return subitem; } } + else if (mdp.IsTableBlock || mdp is MarkdownParagraph.InlineBlock inline && GetCustomBlockId(inline) is not null) + { + yield return new FlatItem(level, false, isOrdered, mdp); + } + else + { + reporter.Error("MD14", $"nothing fancy allowed in lists - specifically not '{mdp.GetType().Name}'"); + } } } + } - IEnumerable Spans2Elements(IEnumerable mds, bool nestedSpan = false, bool inList = false) + IEnumerable Spans2Elements(IEnumerable mds, bool nestedSpan = false, bool inList = false) + { + // This is more longwinded than it might be, because we want to avoid ending with a break. + // (That would occur naturally with a bullet point ending in a note, for example; the break + // at the end adds too much space.) + OpenXmlElement previous = null; + foreach (var md in mds) { - // This is more longwinded than it might be, because we want to avoid ending with a break. - // (That would occur naturally with a bullet point ending in a note, for example; the break - // at the end adds too much space.) - OpenXmlElement previous = null; - foreach (var md in mds) + foreach (var e in Span2Elements(md, nestedSpan, inList)) { - foreach (var e in Span2Elements(md, nestedSpan, inList)) + if (previous is object) { - if (previous is object) - { - yield return previous; - } - previous = e; + yield return previous; } - } - if (previous is object && !(previous is Break)) - { - yield return previous; + previous = e; } } + if (previous is object && !(previous is Break)) + { + yield return previous; + } + } - IEnumerable Span2Elements(MarkdownSpan md, bool nestedSpan = false, bool inList = false) + IEnumerable Span2Elements(MarkdownSpan md, bool nestedSpan = false, bool inList = false) + { + // Handle the end of a note or example in a list. Add a break at the end. + if (inList && md.IsEmphasis) { - // Handle the end of a note or example in a list. Add a break at the end. - if (inList && md.IsEmphasis) + var emphasis = (MarkdownSpan.Emphasis) md; + if (emphasis.body.Length == 1 && emphasis.body[0] is MarkdownSpan.Literal { text: string literalText } && + (literalText == "end example" || literalText == "end note")) { - var emphasis = (MarkdownSpan.Emphasis) md; - if (emphasis.body.Length == 1 && emphasis.body[0] is MarkdownSpan.Literal { text: string literalText } && - (literalText == "end example" || literalText == "end note")) + foreach (var element in Span2Elements(md, nestedSpan, inList: false)) { - foreach (var element in Span2Elements(md, nestedSpan, inList: false)) - { - yield return element; - } - yield return new Break(); - yield break; + yield return element; } + yield return new Break(); + yield break; } + } - reporter.CurrentSpan = md; - if (md.IsLiteral) - { - var mdl = md as MarkdownSpan.Literal; - var s = MarkdownUtilities.UnescapeLiteral(mdl); - - // We have lines containing just "" which end up being - // reported as literals after the note/example they end (rather than as InlineBlocks). Just ignore them. - // Note that Environment.NewLine is needed as the Markdown parser replaces line breaks with that, regardless of the source. - if (s.StartsWith($"{Environment.NewLine}" which end up being + // reported as literals after the note/example they end (rather than as InlineBlocks). Just ignore them. + // Note that Environment.NewLine is needed as the Markdown parser replaces line breaks with that, regardless of the source. + if (s.StartsWith($"{Environment.NewLine}\r\n"); + if (endIndex < startIndex) { - int startIndex = text.IndexOf("\r\n\r\n"); - if (endIndex < startIndex) - { - throw new InvalidOperationException($"End comment before start comment in {file}"); - } - if (startIndex == -1) + throw new InvalidOperationException($"End comment before start comment in {file}"); + } + if (startIndex == -1) + { + if (endIndex != -1) { - if (endIndex != -1) - { - throw new InvalidOperationException($"End comment with no start comment in {file}"); - } - return text; + throw new InvalidOperationException($"End comment with no start comment in {file}"); } - // Remove everything from the start of the match (CRLF) to the end of CLRF--> but not including the *trailing* CRLF. - text = text.Remove(startIndex, endIndex - startIndex + 5); + return text; } + // Remove everything from the start of the match (CRLF) to the end of CLRF--> but not including the *trailing* CRLF. + text = text.Remove(startIndex, endIndex - startIndex + 5); } } } diff --git a/tools/MarkdownConverter/Spec/MarkdownUtilities.cs b/tools/MarkdownConverter/Spec/MarkdownUtilities.cs index fdce1d22f..bd1a03273 100644 --- a/tools/MarkdownConverter/Spec/MarkdownUtilities.cs +++ b/tools/MarkdownConverter/Spec/MarkdownUtilities.cs @@ -1,16 +1,15 @@ using FSharp.Markdown; -namespace MarkdownConverter.Spec +namespace MarkdownConverter.Spec; + +internal static class MarkdownUtilities { - internal static class MarkdownUtilities - { - internal static string UnescapeLiteral(MarkdownSpan.Literal literal) => - literal.text - .Replace("&", "&") - .Replace("<", "<") - .Replace(">", ">") - .Replace("®", "®") - .Replace("\\<", "<") - .Replace("\\>", ">"); - } + internal static string UnescapeLiteral(MarkdownSpan.Literal literal) => + literal.text + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("®", "®") + .Replace("\\<", "<") + .Replace("\\>", ">"); } diff --git a/tools/MarkdownConverter/Spec/Reporter.cs b/tools/MarkdownConverter/Spec/Reporter.cs index e5092044e..3b8350b3e 100644 --- a/tools/MarkdownConverter/Spec/Reporter.cs +++ b/tools/MarkdownConverter/Spec/Reporter.cs @@ -1,91 +1,89 @@ using FSharp.Markdown; -using System.IO; -namespace MarkdownConverter.Spec +namespace MarkdownConverter.Spec; + +// Note: while this and SourceLocation sound like they should be somewhat neutral classes, +// the presence of CurrentSection etc tie them closely to the Spec namespace. + +/// +/// Diagnostic reporter +/// +public class Reporter { - // Note: while this and SourceLocation sound like they should be somewhat neutral classes, - // the presence of CurrentSection etc tie them closely to the Spec namespace. + /// + /// The text writer to write Messages to. + /// + private readonly TextWriter writer; /// - /// Diagnostic reporter + /// The parent reporter, if any. (This is to allow a complete error/warning count to be kept.) /// - public class Reporter + private readonly Reporter parent; + + public int Errors { get; private set; } + public int Warnings { get; private set; } + + public SourceLocation Location { get; set; } = new SourceLocation(null, null, null, null); + + private Reporter(Reporter parent, TextWriter writer, string filename) + { + this.parent = parent; + this.writer = writer; + Location = new SourceLocation(filename, null, null, null); + } + + public Reporter(TextWriter writer) : this(null, writer, null) + { + } + + public Reporter WithFileName(string filename) => new Reporter(this, writer, filename); + + public string CurrentFile => Location.File; + + public SectionRef CurrentSection + { + get => Location.Section; + set => Location = new SourceLocation(CurrentFile, value, CurrentParagraph, null); + } + + public MarkdownParagraph CurrentParagraph { - /// - /// The text writer to write Messages to. - /// - private readonly TextWriter writer; - - /// - /// The parent reporter, if any. (This is to allow a complete error/warning count to be kept.) - /// - private readonly Reporter parent; - - public int Errors { get; private set; } - public int Warnings { get; private set; } - - public SourceLocation Location { get; set; } = new SourceLocation(null, null, null, null); - - private Reporter(Reporter parent, TextWriter writer, string filename) - { - this.parent = parent; - this.writer = writer; - Location = new SourceLocation(filename, null, null, null); - } - - public Reporter(TextWriter writer) : this(null, writer, null) - { - } - - public Reporter WithFileName(string filename) => new Reporter(this, writer, filename); - - public string CurrentFile => Location.File; - - public SectionRef CurrentSection - { - get => Location.Section; - set => Location = new SourceLocation(CurrentFile, value, CurrentParagraph, null); - } - - public MarkdownParagraph CurrentParagraph - { - get => Location.Paragraph; - set => Location = new SourceLocation(CurrentFile, CurrentSection, value, null); - } - - public MarkdownSpan CurrentSpan - { - get => Location.Span; - set => Location = new SourceLocation(CurrentFile, CurrentSection, CurrentParagraph, value); - } - - public void Error(string code, string msg, SourceLocation loc = null) - { - IncrementErrors(); - Report(code, "ERROR", msg, loc?.Description ?? Location.Description); - } - - public void Warning(string code, string msg, SourceLocation loc = null) - { - IncrementWarnings(); - Report(code, "WARNING", msg, loc?.Description ?? Location.Description); - } - - private void IncrementWarnings() - { - Warnings++; - parent?.IncrementWarnings(); - } - - private void IncrementErrors() - { - Errors++; - parent?.IncrementErrors(); - } - - public void Log(string msg) { } - - internal void Report(string code, string severity, string msg, string loc) => - writer.WriteLine($"{loc}: {severity} {code}: {msg}"); + get => Location.Paragraph; + set => Location = new SourceLocation(CurrentFile, CurrentSection, value, null); } + + public MarkdownSpan CurrentSpan + { + get => Location.Span; + set => Location = new SourceLocation(CurrentFile, CurrentSection, CurrentParagraph, value); + } + + public void Error(string code, string msg, SourceLocation loc = null) + { + IncrementErrors(); + Report(code, "ERROR", msg, loc?.Description ?? Location.Description); + } + + public void Warning(string code, string msg, SourceLocation loc = null) + { + IncrementWarnings(); + Report(code, "WARNING", msg, loc?.Description ?? Location.Description); + } + + private void IncrementWarnings() + { + Warnings++; + parent?.IncrementWarnings(); + } + + private void IncrementErrors() + { + Errors++; + parent?.IncrementErrors(); + } + + public void Log(string msg) { } + + internal void Report(string code, string severity, string msg, string loc) => + writer.WriteLine($"{loc}: {severity} {code}: {msg}"); } diff --git a/tools/MarkdownConverter/Spec/SectionRef.cs b/tools/MarkdownConverter/Spec/SectionRef.cs index 143da8328..cd3e77f8d 100644 --- a/tools/MarkdownConverter/Spec/SectionRef.cs +++ b/tools/MarkdownConverter/Spec/SectionRef.cs @@ -1,97 +1,94 @@ using FSharp.Markdown; -using System; -using System.Linq; -namespace MarkdownConverter.Spec +namespace MarkdownConverter.Spec; + +public class SectionRef { - public class SectionRef - { - /// - /// Section number, e.g. 10.1.2, or A.3 or null for sections without a number (e.g. Foreword). - /// - public string Number { get; } + /// + /// Section number, e.g. 10.1.2, or A.3 or null for sections without a number (e.g. Foreword). + /// + public string Number { get; } - /// - /// Section title, e.g. "10.1.2 Goto Statement" - /// - public string Title { get; } + /// + /// Section title, e.g. "10.1.2 Goto Statement" + /// + public string Title { get; } - /// - /// Section title not including the number, e.g. "Goto Statement" - /// - public string TitleWithoutNumber { get; } + /// + /// Section title not including the number, e.g. "Goto Statement" + /// + public string TitleWithoutNumber { get; } - /// - /// 1-based level, e.g. 3 - /// - public int Level { get; } + /// + /// 1-based level, e.g. 3 + /// + public int Level { get; } - /// - /// URL for the Markdown source, e.g. statements.md#goto-statement - /// - public string Url { get; } + /// + /// URL for the Markdown source, e.g. statements.md#goto-statement + /// + public string Url { get; } - /// - /// Name of generated bookmark, e.g. _Toc00023. - /// - public string BookmarkName { get; } + /// + /// Name of generated bookmark, e.g. _Toc00023. + /// + public string BookmarkName { get; } - /// - /// Location in source Markdown. - /// - public SourceLocation Loc { get; } + /// + /// Location in source Markdown. + /// + public SourceLocation Loc { get; } - public SectionRef(MarkdownParagraph.Heading mdh, string filename, string bookmarkName) + public SectionRef(MarkdownParagraph.Heading mdh, string filename, string bookmarkName) + { + Level = mdh.size; + var spans = mdh.body; + if (spans.Length == 1 && spans.First().IsLiteral) { - Level = mdh.size; - var spans = mdh.body; - if (spans.Length == 1 && spans.First().IsLiteral) + Title = MarkdownUtilities.UnescapeLiteral(spans.First() as MarkdownSpan.Literal).Trim(); + if (char.IsDigit(Title[0]) || (Title[0] >= 'A' && Title[0] <= 'D' && Title[1] == '.')) { - Title = MarkdownUtilities.UnescapeLiteral(spans.First() as MarkdownSpan.Literal).Trim(); - if (char.IsDigit(Title[0]) || (Title[0] >= 'A' && Title[0] <= 'D' && Title[1] == '.')) - { - var titleParts = Title.Split(new[] { ' ' }, 2); - Number = titleParts[0]; - TitleWithoutNumber = titleParts[1]; - } - else - { - Number = null; - TitleWithoutNumber = Title; - } + var titleParts = Title.Split(new[] { ' ' }, 2); + Number = titleParts[0]; + TitleWithoutNumber = titleParts[1]; } else { - throw new NotSupportedException($"Heading must be a single literal. Got: {mdh.body}"); + Number = null; + TitleWithoutNumber = Title; + } + } + else + { + throw new NotSupportedException($"Heading must be a single literal. Got: {mdh.body}"); + } + foreach (var c in Title) + { + if (c >= 'a' && c <= 'z') + { + Url += c; } - foreach (var c in Title) + else if (c >= 'A' && c <= 'Z') { - if (c >= 'a' && c <= 'z') - { - Url += c; - } - else if (c >= 'A' && c <= 'Z') - { - Url += char.ToLowerInvariant(c); - } - else if (c >= '0' && c <= '9') - { - Url += c; - } - else if (c == '-' || c == '_') - { - Url += c; - } - else if (c == ' ') - { - Url += '-'; - } + Url += char.ToLowerInvariant(c); + } + else if (c >= '0' && c <= '9') + { + Url += c; + } + else if (c == '-' || c == '_') + { + Url += c; + } + else if (c == ' ') + { + Url += '-'; } - Url = filename + "#" + Url; - Loc = new SourceLocation(filename, this, mdh, null); - BookmarkName = bookmarkName; } - - public override string ToString() => Url; + Url = filename + "#" + Url; + Loc = new SourceLocation(filename, this, mdh, null); + BookmarkName = bookmarkName; } + + public override string ToString() => Url; } diff --git a/tools/MarkdownConverter/Spec/SourceLocation.cs b/tools/MarkdownConverter/Spec/SourceLocation.cs index 78591b04b..7116a5a2e 100644 --- a/tools/MarkdownConverter/Spec/SourceLocation.cs +++ b/tools/MarkdownConverter/Spec/SourceLocation.cs @@ -2,89 +2,88 @@ using FSharp.Markdown; using Microsoft.FSharp.Core; -namespace MarkdownConverter.Spec +namespace MarkdownConverter.Spec; + +public class SourceLocation { - public class SourceLocation - { - public string File { get; } - public SectionRef Section { get; } - public MarkdownParagraph Paragraph { get; } - public MarkdownSpan Span { get; } - public string _loc; // generated lazily. + public string File { get; } + public SectionRef Section { get; } + public MarkdownParagraph Paragraph { get; } + public MarkdownSpan Span { get; } + public string _loc; // generated lazily. - public SourceLocation(string file, SectionRef section, MarkdownParagraph paragraph, MarkdownSpan span) - { - File = file; - Section = section; - Paragraph = paragraph; - Span = span; - } + public SourceLocation(string file, SectionRef section, MarkdownParagraph paragraph, MarkdownSpan span) + { + File = file; + Section = section; + Paragraph = paragraph; + Span = span; + } - /// - /// Description of the location, of the form "file(line/col)" in a format recognizable by msbuild. - /// - public string Description + /// + /// Description of the location, of the form "file(line/col)" in a format recognizable by msbuild. + /// + public string Description + { + get { - get + if (_loc != null) { - if (_loc != null) - { - return _loc; - } + return _loc; + } - if (File == null) - { - _loc = "mdspec2docx"; - } - else if (Section == null && Paragraph == null) + if (File == null) + { + _loc = "mdspec2docx"; + } + else if (Section == null && Paragraph == null) + { + _loc = File; + } + else + { + // Note: we now use the F# Markdown support for ranges, rather than finding text directly. + // This produces slightly weaker diagnostics than before, but it avoids an awful lot of fiddly fuzzy text matching code. + + // TODO: Revisit SectionRef.Loc, possibly just exposing the paragraph directly. + var maybeRange = GetRange(Span); + if (maybeRange != null) { - _loc = File; + var range = maybeRange.Value; + _loc = $"{File}({range.StartLine},{range.StartColumn},{range.EndLine},{range.EndColumn})"; } else { - // Note: we now use the F# Markdown support for ranges, rather than finding text directly. - // This produces slightly weaker diagnostics than before, but it avoids an awful lot of fiddly fuzzy text matching code. - - // TODO: Revisit SectionRef.Loc, possibly just exposing the paragraph directly. - var maybeRange = GetRange(Span); - if (maybeRange != null) + maybeRange = GetRange(Paragraph) ?? GetRange(Section.Loc.Paragraph); + if (maybeRange == null) { - var range = maybeRange.Value; - _loc = $"{File}({range.StartLine},{range.StartColumn},{range.EndLine},{range.EndColumn})"; + // We don't have any line or column information. Just report the filename. + _loc = File; } else { - maybeRange = GetRange(Paragraph) ?? GetRange(Section.Loc.Paragraph); - if (maybeRange == null) - { - // We don't have any line or column information. Just report the filename. - _loc = File; - } - else - { - var range = maybeRange.Value; - _loc = range.StartLine == range.EndLine - ? $"{File}({range.StartLine})" - : $"{File}({range.StartLine}-{range.EndLine})"; - } + var range = maybeRange.Value; + _loc = range.StartLine == range.EndLine + ? $"{File}({range.StartLine})" + : $"{File}({range.StartLine}-{range.EndLine})"; } } - - return _loc; } + + return _loc; } + } - // Each tagged type within the F# Markdown library for pargraph/span contains a "range" property, but - // I don't think there's a common way of getting it. So use reflection, horrible as that is... - private static MarkdownRange? GetRange(object obj) + // Each tagged type within the F# Markdown library for pargraph/span contains a "range" property, but + // I don't think there's a common way of getting it. So use reflection, horrible as that is... + private static MarkdownRange? GetRange(object obj) + { + if (obj == null) { - if (obj == null) - { - return null; - } - var rangeProperty = obj.GetType().GetProperty("range"); - var range = rangeProperty?.GetValue(obj) as FSharpOption; - return range?.Value; + return null; } + var rangeProperty = obj.GetType().GetProperty("range"); + var range = rangeProperty?.GetValue(obj) as FSharpOption; + return range?.Value; } } diff --git a/tools/MarkdownConverter/Spec/Span.cs b/tools/MarkdownConverter/Spec/Span.cs index d492f33bf..f4b88f254 100644 --- a/tools/MarkdownConverter/Spec/Span.cs +++ b/tools/MarkdownConverter/Spec/Span.cs @@ -1,12 +1,13 @@ -namespace MarkdownConverter.Spec +namespace MarkdownConverter.Spec; + +// TODO: Now that there's a Span type in the .NET runtime, this should be renamed. +// I'll suggest TextSpan. +internal class Span { - internal class Span - { - public int Start { get; } - public int Length { get; } - public int End => Start + Length; + public int Start { get; } + public int Length { get; } + public int End => Start + Length; - public Span(int start, int length) => - (Start, Length) = (start, length); - } + public Span(int start, int length) => + (Start, Length) = (start, length); } diff --git a/tools/MarkdownConverter/Spec/StringLengthComparer.cs b/tools/MarkdownConverter/Spec/StringLengthComparer.cs index 148cce02d..ea4ef6258 100644 --- a/tools/MarkdownConverter/Spec/StringLengthComparer.cs +++ b/tools/MarkdownConverter/Spec/StringLengthComparer.cs @@ -1,22 +1,19 @@ -using System.Collections.Generic; +namespace MarkdownConverter.Spec; -namespace MarkdownConverter.Spec +public class StringLengthComparer : IComparer { - public class StringLengthComparer : IComparer + public int Compare(string x, string y) { - public int Compare(string x, string y) + if (x.Length > y.Length) { - if (x.Length > y.Length) - { - return -1; - } - - if (x.Length < y.Length) - { - return 1; - } + return -1; + } - return string.Compare(x, y); + if (x.Length < y.Length) + { + return 1; } + + return string.Compare(x, y); } } diff --git a/tools/MarkdownConverter/Spec/TermRef.cs b/tools/MarkdownConverter/Spec/TermRef.cs index f796165b9..f88d0c4ce 100644 --- a/tools/MarkdownConverter/Spec/TermRef.cs +++ b/tools/MarkdownConverter/Spec/TermRef.cs @@ -1,19 +1,18 @@ -namespace MarkdownConverter.Spec +namespace MarkdownConverter.Spec; + +internal class TermRef { - internal class TermRef - { - private static int count = 1; + private static int count = 1; - public string Term { get; } - public string BookmarkName { get; } - public SourceLocation Loc { get; } + public string Term { get; } + public string BookmarkName { get; } + public SourceLocation Loc { get; } - public TermRef(string term, SourceLocation loc) - { - Term = term; - Loc = loc; - BookmarkName = $"_Trm{count:00000}"; - count++; - } + public TermRef(string term, SourceLocation loc) + { + Term = term; + Loc = loc; + BookmarkName = $"_Trm{count:00000}"; + count++; } } diff --git a/tools/StandardAnchorTags/GenerateGrammar.cs b/tools/StandardAnchorTags/GenerateGrammar.cs index cfedffd23..45022d986 100644 --- a/tools/StandardAnchorTags/GenerateGrammar.cs +++ b/tools/StandardAnchorTags/GenerateGrammar.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; -using System.Text; -using System.Threading.Tasks; +using System.Text; namespace StandardAnchorTags; diff --git a/tools/StandardAnchorTags/Program.cs b/tools/StandardAnchorTags/Program.cs index ca69700a8..635889c0c 100644 --- a/tools/StandardAnchorTags/Program.cs +++ b/tools/StandardAnchorTags/Program.cs @@ -1,189 +1,185 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Utilities; +using Utilities; using System.Text.Json; -namespace StandardAnchorTags +namespace StandardAnchorTags; + +public class Program { - public class Program + const string TOCHeader = ""; + private const string PathToStandard = "../standard/"; + private const string ReadMePath = "../standard/README.md"; + private const string FilesPath = "../standard/clauses.json"; + private const string GrammarFile = "grammar.md"; + + private static Clauses? standardClauses; + + static async Task Main(string[] args) { - const string TOCHeader = ""; - private const string PathToStandard = "../standard/"; - private const string ReadMePath = "../standard/README.md"; - private const string FilesPath = "../standard/clauses.json"; - private const string GrammarFile = "grammar.md"; + using FileStream openStream = File.OpenRead(FilesPath); + standardClauses = await JsonSerializer.DeserializeAsync(openStream); + if (standardClauses is null) + { + Console.WriteLine("Could not read list of clauses. Exiting"); + return 1; + } - private static Clauses? standardClauses; + bool dryRun = ((args.Length > 0) && (args[0].Contains("dryrun"))); + if (dryRun) + { + Console.WriteLine("Doing a dry run"); + } - static async Task Main(string[] args) + try { - using FileStream openStream = File.OpenRead(FilesPath); - standardClauses = await JsonSerializer.DeserializeAsync(openStream); - if (standardClauses is null) + Console.WriteLine("=========================== Front Matter ==================================="); + var sectionMap = new TocSectionNumberBuilder(PathToStandard, dryRun); + foreach (var file in standardClauses.FrontMatter) { - Console.WriteLine("Could not read list of clauses. Exiting"); - return 1; + Console.WriteLine($" -- {file}"); + await sectionMap.AddFrontMatterTocEntries(file); } - bool dryRun = ((args.Length > 0) && (args[0].Contains("dryrun"))); - if (dryRun) + Console.WriteLine("================= GENERATE UPDATED SECTION NUMBERS ========================="); + Console.WriteLine("============================ Scope and Conformance ======================================"); + foreach (var file in standardClauses.ScopeAndConformance) + { + Console.WriteLine($" -- {file}"); + await sectionMap.AddContentsToTOC(file); + + } + Console.WriteLine("============================ Lexical Structure ======================================"); + foreach (var file in standardClauses.LexicalStructure) { - Console.WriteLine("Doing a dry run"); + Console.WriteLine($" -- {file}"); + await sectionMap.AddContentsToTOC(file); + } + Console.WriteLine("============================ Main text======================================"); + foreach (var file in standardClauses.MainBody) + { + Console.WriteLine($" -- {file}"); + await sectionMap.AddContentsToTOC(file); + } + Console.WriteLine("============================ Unsafe clauses======================================"); + foreach (var file in standardClauses.UnsafeClauses) + { + Console.WriteLine($" -- {file}"); + await sectionMap.AddContentsToTOC(file); + } + Console.WriteLine("============================= Annexes ======================================"); + sectionMap.FinishMainSection(); + foreach (var file in standardClauses.Annexes) + { + Console.WriteLine($" -- {file}"); + await sectionMap.AddContentsToTOC(file); + } + if (!dryRun) + { + Console.WriteLine("Update TOC"); + var existingReadMe = await ReadExistingReadMe(); + using var readme = new StreamWriter(ReadMePath, false); + await readme.WriteAsync(existingReadMe); + await readme.WriteLineAsync(TOCHeader); + await readme.WriteLineAsync(); + await readme.WriteAsync(sectionMap.Toc); } - try + Console.WriteLine("======================= UPDATE ALL REFERENCES =============================="); + var fixup = new ReferenceUpdateProcessor(PathToStandard, sectionMap.LinkMap, dryRun); + + Console.WriteLine("=========================== Front Matter ==================================="); + foreach (var file in standardClauses.FrontMatter) { - Console.WriteLine("=========================== Front Matter ==================================="); - var sectionMap = new TocSectionNumberBuilder(PathToStandard, dryRun); - foreach (var file in standardClauses.FrontMatter) - { - Console.WriteLine($" -- {file}"); - await sectionMap.AddFrontMatterTocEntries(file); - } + Console.WriteLine($" -- {file}"); + await fixup.ReplaceReferences(file); + } + Console.WriteLine("============================ Scope and Conformance ======================================"); + foreach (var file in standardClauses.ScopeAndConformance) + { + Console.WriteLine($" -- {file}"); + await fixup.ReplaceReferences(file); - Console.WriteLine("================= GENERATE UPDATED SECTION NUMBERS ========================="); - Console.WriteLine("============================ Scope and Conformance ======================================"); - foreach (var file in standardClauses.ScopeAndConformance) - { - Console.WriteLine($" -- {file}"); - await sectionMap.AddContentsToTOC(file); + } + Console.WriteLine("============================ Lexical Structure ======================================"); + foreach (var file in standardClauses.LexicalStructure) + { + Console.WriteLine($" -- {file}"); + await fixup.ReplaceReferences(file); + } + Console.WriteLine("============================ Main text======================================"); + foreach (var file in standardClauses.MainBody) + { + Console.WriteLine($" -- {file}"); + await fixup.ReplaceReferences(file); - } - Console.WriteLine("============================ Lexical Structure ======================================"); - foreach (var file in standardClauses.LexicalStructure) - { - Console.WriteLine($" -- {file}"); - await sectionMap.AddContentsToTOC(file); - } - Console.WriteLine("============================ Main text======================================"); - foreach (var file in standardClauses.MainBody) - { - Console.WriteLine($" -- {file}"); - await sectionMap.AddContentsToTOC(file); - } - Console.WriteLine("============================ Unsafe clauses======================================"); - foreach (var file in standardClauses.UnsafeClauses) - { - Console.WriteLine($" -- {file}"); - await sectionMap.AddContentsToTOC(file); - } - Console.WriteLine("============================= Annexes ======================================"); - sectionMap.FinishMainSection(); - foreach (var file in standardClauses.Annexes) - { - Console.WriteLine($" -- {file}"); - await sectionMap.AddContentsToTOC(file); - } - if (!dryRun) - { - Console.WriteLine("Update TOC"); - var existingReadMe = await ReadExistingReadMe(); - using var readme = new StreamWriter(ReadMePath, false); - await readme.WriteAsync(existingReadMe); - await readme.WriteLineAsync(TOCHeader); - await readme.WriteLineAsync(); - await readme.WriteAsync(sectionMap.Toc); - } + } + Console.WriteLine("============================ Unsafe clauses======================================"); + foreach (var file in standardClauses.UnsafeClauses) + { + Console.WriteLine($" -- {file}"); + await fixup.ReplaceReferences(file); - Console.WriteLine("======================= UPDATE ALL REFERENCES =============================="); - var fixup = new ReferenceUpdateProcessor(PathToStandard, sectionMap.LinkMap, dryRun); + } + Console.WriteLine("============================= Annexes ======================================"); + foreach (var file in standardClauses.Annexes) + { + Console.WriteLine($" -- {file}"); + await fixup.ReplaceReferences(file); + } + if (!dryRun) + { + Console.WriteLine("======================= READ EXISTING GRAMMAR HEADERS ======================="); + var headers = await GenerateGrammar.ReadExistingHeaders(PathToStandard, GrammarFile); - Console.WriteLine("=========================== Front Matter ==================================="); - foreach (var file in standardClauses.FrontMatter) - { - Console.WriteLine($" -- {file}"); - await fixup.ReplaceReferences(file); - } - Console.WriteLine("============================ Scope and Conformance ======================================"); - foreach (var file in standardClauses.ScopeAndConformance) - { - Console.WriteLine($" -- {file}"); - await fixup.ReplaceReferences(file); + Console.WriteLine("======================= GENERATE GRAMMAR ANNEX =============================="); + using var grammarGenerator = new GenerateGrammar(GrammarFile, PathToStandard, headers); - } Console.WriteLine("============================ Lexical Structure ======================================"); + + await grammarGenerator.WriteHeader(); foreach (var file in standardClauses.LexicalStructure) { Console.WriteLine($" -- {file}"); - await fixup.ReplaceReferences(file); + await grammarGenerator.ExtractGrammarFrom(file); } Console.WriteLine("============================ Main text======================================"); + + await grammarGenerator.WriteSyntaxHeader(); foreach (var file in standardClauses.MainBody) { Console.WriteLine($" -- {file}"); - await fixup.ReplaceReferences(file); - + await grammarGenerator.ExtractGrammarFrom(file); } Console.WriteLine("============================ Unsafe clauses======================================"); + await grammarGenerator.WriteUnsafeExtensionHeader(); foreach (var file in standardClauses.UnsafeClauses) { Console.WriteLine($" -- {file}"); - await fixup.ReplaceReferences(file); - - } - Console.WriteLine("============================= Annexes ======================================"); - foreach (var file in standardClauses.Annexes) - { - Console.WriteLine($" -- {file}"); - await fixup.ReplaceReferences(file); + await grammarGenerator.ExtractGrammarFrom(file); } - if (!dryRun) - { - Console.WriteLine("======================= READ EXISTING GRAMMAR HEADERS ======================="); - var headers = await GenerateGrammar.ReadExistingHeaders(PathToStandard, GrammarFile); - - Console.WriteLine("======================= GENERATE GRAMMAR ANNEX =============================="); - using var grammarGenerator = new GenerateGrammar(GrammarFile, PathToStandard, headers); - - Console.WriteLine("============================ Lexical Structure ======================================"); - - await grammarGenerator.WriteHeader(); - foreach (var file in standardClauses.LexicalStructure) - { - Console.WriteLine($" -- {file}"); - await grammarGenerator.ExtractGrammarFrom(file); - } - Console.WriteLine("============================ Main text======================================"); - - await grammarGenerator.WriteSyntaxHeader(); - foreach (var file in standardClauses.MainBody) - { - Console.WriteLine($" -- {file}"); - await grammarGenerator.ExtractGrammarFrom(file); - } - Console.WriteLine("============================ Unsafe clauses======================================"); - await grammarGenerator.WriteUnsafeExtensionHeader(); - foreach (var file in standardClauses.UnsafeClauses) - { - Console.WriteLine($" -- {file}"); - await grammarGenerator.ExtractGrammarFrom(file); - } - await grammarGenerator.WriteGrammarFooter(); - } - return fixup.ErrorCount; - } - catch (InvalidOperationException e) - { - Console.WriteLine("\tError encountered:"); - Console.WriteLine(e.Message.ToString()); - Console.WriteLine("To recover, do the following:"); - Console.WriteLine("1. Discard all changes from the section numbering tool"); - Console.WriteLine("2. Fix the error noted above."); - Console.WriteLine("3. Run the tool again."); - return 1; + await grammarGenerator.WriteGrammarFooter(); } + return fixup.ErrorCount; } - - private static async Task ReadExistingReadMe() + catch (InvalidOperationException e) { - using var reader = new StreamReader(ReadMePath); - var contents = await reader.ReadToEndAsync(); + Console.WriteLine("\tError encountered:"); + Console.WriteLine(e.Message.ToString()); + Console.WriteLine("To recover, do the following:"); + Console.WriteLine("1. Discard all changes from the section numbering tool"); + Console.WriteLine("2. Fix the error noted above."); + Console.WriteLine("3. Run the tool again."); + return 1; + } + } - // This is the first node in the TOC, so truncate here: - var index = contents.IndexOf(TOCHeader); + private static async Task ReadExistingReadMe() + { + using var reader = new StreamReader(ReadMePath); + var contents = await reader.ReadToEndAsync(); - return contents[..index]; - } + // This is the first node in the TOC, so truncate here: + var index = contents.IndexOf(TOCHeader); + + return contents[..index]; } } \ No newline at end of file diff --git a/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs b/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs index 9ff828aa8..586373f2e 100644 --- a/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs +++ b/tools/StandardAnchorTags/ReferenceUpdateProcessor.cs @@ -1,143 +1,138 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; +using System.Text; -namespace StandardAnchorTags +namespace StandardAnchorTags; + +internal class ReferenceUpdateProcessor { - internal class ReferenceUpdateProcessor - { - const char sectionReference = '§'; + const char sectionReference = '§'; - private readonly IReadOnlyDictionary linkMap; - private readonly bool dryRun; - private readonly string PathToFiles; - public int ErrorCount { get; private set; } + private readonly IReadOnlyDictionary linkMap; + private readonly bool dryRun; + private readonly string PathToFiles; + public int ErrorCount { get; private set; } - public ReferenceUpdateProcessor(string pathToFiles, IReadOnlyDictionary linkMap, bool dryRun) - { - PathToFiles = pathToFiles; - this.linkMap = linkMap; - this.dryRun = dryRun; - } + public ReferenceUpdateProcessor(string pathToFiles, IReadOnlyDictionary linkMap, bool dryRun) + { + PathToFiles = pathToFiles; + this.linkMap = linkMap; + this.dryRun = dryRun; + } - public async Task ReplaceReferences(string file) + public async Task ReplaceReferences(string file) + { + var inputPath = $"{PathToFiles}/{file}"; + var tmpFileName = $"{file}.tmp"; + int lineNumber = 0; + using (var readStream = new StreamReader(inputPath)) { - var inputPath = $"{PathToFiles}/{file}"; - var tmpFileName = $"{file}.tmp"; - int lineNumber = 0; - using (var readStream = new StreamReader(inputPath)) + using StreamWriter writeStream = new(tmpFileName); + while (await readStream.ReadLineAsync() is string line) { - using StreamWriter writeStream = new(tmpFileName); - while (await readStream.ReadLineAsync() is string line) - { - lineNumber++; - var updatedLine = line.Contains(sectionReference) - ? ProcessSectionLinks(line, lineNumber, file) - : line; - await writeStream.WriteLineAsync(updatedLine); - } - writeStream.Close(); - readStream.Close(); - } - if (dryRun) - { - File.Delete(tmpFileName); - } - else - { - File.Move(tmpFileName, inputPath, true); + lineNumber++; + var updatedLine = line.Contains(sectionReference) + ? ProcessSectionLinks(line, lineNumber, file) + : line; + await writeStream.WriteLineAsync(updatedLine); } + writeStream.Close(); + readStream.Close(); } - - private string ProcessSectionLinks(string line, int lineNumber, string file) + if (dryRun) + { + File.Delete(tmpFileName); + } + else { - var returnedLine = new StringBuilder(); - int index = 0; + File.Move(tmpFileName, inputPath, true); + } + } + + private string ProcessSectionLinks(string line, int lineNumber, string file) + { + var returnedLine = new StringBuilder(); + int index = 0; - while (FindNextSectionReference(line, index) is Range sectionReferenceRange) // found another section reference. + while (FindNextSectionReference(line, index) is Range sectionReferenceRange) // found another section reference. + { + // Grab the section text: + string referenceText = line[sectionReferenceRange]; + string linkText = referenceText; + if ((referenceText.Length > 1) && + (!linkMap.ContainsKey(referenceText))) { - // Grab the section text: - string referenceText = line[sectionReferenceRange]; - string linkText = referenceText; - if ((referenceText.Length > 1) && - (!linkMap.ContainsKey(referenceText))) + var msg = $"Section reference [{referenceText}] not found at line {lineNumber} in {file}"; + if (dryRun) { - var msg = $"Section reference [{referenceText}] not found at line {lineNumber} in {file}"; - if (dryRun) - { - ErrorCount++; - Console.WriteLine(msg); - } - else - throw new InvalidOperationException(msg); - } else - { - linkText = linkMap[referenceText].FormattedMarkdownLink; + ErrorCount++; + Console.WriteLine(msg); } - // expand the range for any existing link: - sectionReferenceRange = ExpandToIncludeExistingLink(line, sectionReferenceRange); + else + throw new InvalidOperationException(msg); + } else + { + linkText = linkMap[referenceText].FormattedMarkdownLink; + } + // expand the range for any existing link: + sectionReferenceRange = ExpandToIncludeExistingLink(line, sectionReferenceRange); - var textRangeToCopyUnchanged = new Range(index, sectionReferenceRange.Start); - // Copy text up to replacement: - returnedLine.Append(line[textRangeToCopyUnchanged]); + var textRangeToCopyUnchanged = new Range(index, sectionReferenceRange.Start); + // Copy text up to replacement: + returnedLine.Append(line[textRangeToCopyUnchanged]); - returnedLine.Append(linkText); - index = sectionReferenceRange.End.Value; - } - // Copy remaining text - returnedLine.Append(line[index..]); - return returnedLine.ToString(); + returnedLine.Append(linkText); + index = sectionReferenceRange.End.Value; } + // Copy remaining text + returnedLine.Append(line[index..]); + return returnedLine.ToString(); + } - private static Range? FindNextSectionReference(string line, int startIndex) + private static Range? FindNextSectionReference(string line, int startIndex) + { + // Find the start: + startIndex = line.IndexOf(sectionReference, startIndex); + + if (startIndex == -1) return default; + int endIndex = startIndex + 1; + + // The first character not in the set of: + // A..Z + // a..z + // 0..9 + // . + // - + // indicates the end of the link reference. + while ((endIndex < line.Length) && (line[endIndex] switch { - // Find the start: - startIndex = line.IndexOf(sectionReference, startIndex); - - if (startIndex == -1) return default; - int endIndex = startIndex + 1; - - // The first character not in the set of: - // A..Z - // a..z - // 0..9 - // . - // - - // indicates the end of the link reference. - while ((endIndex < line.Length) && (line[endIndex] switch - { - >= 'A' and <= 'Z' => true, - >= 'a' and <= 'z' => true, - >= '0' and <= '9' => true, - '.' or '-' => true, - _ => false, - })) endIndex++; - - // One final special case: If the last character is '.', it's not - // part of the section reference, it's the period at the end of a sentence: - if (line[endIndex - 1] == '.') - endIndex--; - return new Range(startIndex, endIndex); - } + >= 'A' and <= 'Z' => true, + >= 'a' and <= 'z' => true, + >= '0' and <= '9' => true, + '.' or '-' => true, + _ => false, + })) endIndex++; + + // One final special case: If the last character is '.', it's not + // part of the section reference, it's the period at the end of a sentence: + if (line[endIndex - 1] == '.') + endIndex--; + return new Range(startIndex, endIndex); + } - private static Range ExpandToIncludeExistingLink(string line, Range range) - { - // If the character before the start of the range isn't the '[' character, - // return => no existing link. - if (range.Start.Value == 0) return range; - var previous = range.Start.Value - 1; - if (line[previous] != '[') return range; + private static Range ExpandToIncludeExistingLink(string line, Range range) + { + // If the character before the start of the range isn't the '[' character, + // return => no existing link. + if (range.Start.Value == 0) return range; + var previous = range.Start.Value - 1; + if (line[previous] != '[') return range; - // Start and the end of the range, look for "](", then ']'. - int endIndex = range.End.Value; - if (line.Substring(endIndex, 2) != "](") throw new InvalidOperationException("Unexpected link text"); + // Start and the end of the range, look for "](", then ']'. + int endIndex = range.End.Value; + if (line.Substring(endIndex, 2) != "](") throw new InvalidOperationException("Unexpected link text"); - endIndex += 2; - while (line[endIndex] != ')') endIndex++; + endIndex += 2; + while (line[endIndex] != ')') endIndex++; - return new Range(previous, endIndex + 1); - } + return new Range(previous, endIndex + 1); } } \ No newline at end of file diff --git a/tools/StandardAnchorTags/SectionLink.cs b/tools/StandardAnchorTags/SectionLink.cs index 2bea704ff..1d632b00e 100644 --- a/tools/StandardAnchorTags/SectionLink.cs +++ b/tools/StandardAnchorTags/SectionLink.cs @@ -1,37 +1,36 @@ -namespace StandardAnchorTags +namespace StandardAnchorTags; + +/// +/// Data stored for generating a link to a section. +/// +public readonly struct SectionLink { - /// - /// Data stored for generating a link to a section. - /// - public readonly struct SectionLink + private const char sectionReference = '§'; + public SectionLink(string oldLink, string newLink, string anchor) { - private const char sectionReference = '§'; - public SectionLink(string oldLink, string newLink, string anchor) - { - ExistingLinkText = oldLink; - NewLinkText = newLink; - AnchorText = anchor; - } + ExistingLinkText = oldLink; + NewLinkText = newLink; + AnchorText = anchor; + } - /// - /// The property is the link text currently used. - /// - /// Might not be needed. - public string ExistingLinkText { get; } + /// + /// The property is the link text currently used. + /// + /// Might not be needed. + public string ExistingLinkText { get; } - /// - /// The text following the § character for any link in the updated standard. - /// - public string NewLinkText { get; } + /// + /// The text following the § character for any link in the updated standard. + /// + public string NewLinkText { get; } - /// - /// The text string for the destination file and anchor. - /// - public string AnchorText { get; } + /// + /// The text string for the destination file and anchor. + /// + public string AnchorText { get; } - public string FormattedMarkdownLink => $"[{sectionReference}{NewLinkText}]({AnchorText})"; + public string FormattedMarkdownLink => $"[{sectionReference}{NewLinkText}]({AnchorText})"; - public string TOCMarkdownLink() - => $"[{sectionReference}{NewLinkText}]({AnchorText})"; - } + public string TOCMarkdownLink() + => $"[{sectionReference}{NewLinkText}]({AnchorText})"; } diff --git a/tools/StandardAnchorTags/TocSectionNumberBuilder.cs b/tools/StandardAnchorTags/TocSectionNumberBuilder.cs index 777311443..8e28db9d0 100644 --- a/tools/StandardAnchorTags/TocSectionNumberBuilder.cs +++ b/tools/StandardAnchorTags/TocSectionNumberBuilder.cs @@ -1,229 +1,223 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System; -namespace StandardAnchorTags +namespace StandardAnchorTags; + +/// +/// This builds the TOC numbers, and a mapping of all anchors. +/// +/// +/// In addition, the creation of the TOC Section map creates two side-effects: +/// 1. Updates all headers in the source markdown to have the proper updated section numbers. +/// 2. Create the toc.md file, containing updated section numbers. +/// +public class TocSectionNumberBuilder { - /// - /// This builds the TOC numbers, and a mapping of all anchors. - /// - /// - /// In addition, the creation of the TOC Section map creates two side-effects: - /// 1. Updates all headers in the source markdown to have the proper updated section numbers. - /// 2. Create the toc.md file, containing updated section numbers. - /// - public class TocSectionNumberBuilder + private struct SectionHeader { - private struct SectionHeader - { - public int level; - public string sectionHeaderText; - public string title; + public int level; + public string sectionHeaderText; + public string title; - } + } - private const string MainSectionPattern = @"^\d+(\.\d+)*$"; - private const string AnnexPattern = @"^[A-Z](\.\d+)*$"; + private const string MainSectionPattern = @"^\d+(\.\d+)*$"; + private const string AnnexPattern = @"^[A-Z](\.\d+)*$"; - private readonly string PathToStandardFiles; - private readonly bool dryRun; + private readonly string PathToStandardFiles; + private readonly bool dryRun; - // String builder to store the full TOC for the standard. - private readonly StringBuilder tocContent = new(); - private readonly Dictionary sectionLinkMap = new(); - private bool isAnnexes; + // String builder to store the full TOC for the standard. + private readonly StringBuilder tocContent = new(); + private readonly Dictionary sectionLinkMap = new(); + private bool isAnnexes; - // Running array of entries for the current headings. - // Starting with H1 is headings[0], H2 is headings[1] etc. - private readonly int[] headings = new int[8]; + // Running array of entries for the current headings. + // Starting with H1 is headings[0], H2 is headings[1] etc. + private readonly int[] headings = new int[8]; - /// - /// Construct the map Builder. - /// - public TocSectionNumberBuilder(string pathFromToolToStandard, bool dryRun) - { - PathToStandardFiles = pathFromToolToStandard; - this.dryRun = dryRun; - } + /// + /// Construct the map Builder. + /// + public TocSectionNumberBuilder(string pathFromToolToStandard, bool dryRun) + { + PathToStandardFiles = pathFromToolToStandard; + this.dryRun = dryRun; + } - /// - /// Add the front matter entries to the TOC - /// - /// The front matter file name. - /// - /// The front matter entries don't add section numbers in the headers. - /// Otherwise, this is the same logic for the main file. - /// - public async Task AddFrontMatterTocEntries(string fileName) + /// + /// Add the front matter entries to the TOC + /// + /// The front matter file name. + /// + /// The front matter entries don't add section numbers in the headers. + /// Otherwise, this is the same logic for the main file. + /// + public async Task AddFrontMatterTocEntries(string fileName) + { + using var stream = File.OpenText(Path.Combine(PathToStandardFiles,fileName)); + string? line = await stream.ReadLineAsync(); { - using var stream = File.OpenText(Path.Combine(PathToStandardFiles,fileName)); - string? line = await stream.ReadLineAsync(); + if (line?.StartsWith("# ") == true) { - if (line?.StartsWith("# ") == true) - { - var linkText = line[2..]; - tocContent.AppendLine($"- [{linkText}]({fileName})"); - // Done: return. - return; - } + var linkText = line[2..]; + tocContent.AppendLine($"- [{linkText}]({fileName})"); + // Done: return. + return; } - // Getting here means this file doesn't have an H1. That's an error: - throw new InvalidOperationException($"File {fileName} doesn't have an H1 tag as its first line."); } + // Getting here means this file doesn't have an H1. That's an error: + throw new InvalidOperationException($"File {fileName} doesn't have an H1 tag as its first line."); + } - public async Task AddContentsToTOC(string filename) + public async Task AddContentsToTOC(string filename) + { + string pathToFile = $"{PathToStandardFiles}/{filename}"; + string tmpFileName = $"{filename}-updated.md"; + string? line; + int lineNumber = 0; + using (var stream = new StreamReader(pathToFile)) { - string pathToFile = $"{PathToStandardFiles}/{filename}"; - string tmpFileName = $"{filename}-updated.md"; - string? line; - int lineNumber = 0; - using (var stream = new StreamReader(pathToFile)) + using var writeStream = new StreamWriter(tmpFileName); + while ((line = await stream.ReadLineAsync()) != null) { - using var writeStream = new StreamWriter(tmpFileName); - while ((line = await stream.ReadLineAsync()) != null) + lineNumber++; + if (FindHeader(line) is SectionHeader header) { - lineNumber++; - if (FindHeader(line) is SectionHeader header) + SectionLink link = BuildSectionLink(header,filename); + var linkDestinationUrl = $"{filename}#{link.AnchorText}"; + // Add to collection, if the header has link text: + if (!string.IsNullOrWhiteSpace(header.sectionHeaderText)) { - SectionLink link = BuildSectionLink(header,filename); - var linkDestinationUrl = $"{filename}#{link.AnchorText}"; - // Add to collection, if the header has link text: - if (!string.IsNullOrWhiteSpace(header.sectionHeaderText)) - { - if (sectionLinkMap.ContainsKey(header.sectionHeaderText)) - throw new InvalidOperationException($"Duplicate section header [{header.sectionHeaderText}] found at line {lineNumber} in {filename}"); - sectionLinkMap.Add(header.sectionHeaderText, link); - } - // Build the new header line - var atxHeader = new string('#', header.level); - // Write TOC line - tocContent.AppendLine($"{new string(' ', (header.level - 1) * 2)}- {link.TOCMarkdownLink()} {header.title}"); - line = $"{atxHeader} {(isAnnexes && (header.level == 1) ? "Annex " : "")}{link.NewLinkText} {header.title}"; + if (sectionLinkMap.ContainsKey(header.sectionHeaderText)) + throw new InvalidOperationException($"Duplicate section header [{header.sectionHeaderText}] found at line {lineNumber} in {filename}"); + sectionLinkMap.Add(header.sectionHeaderText, link); } - await writeStream.WriteLineAsync(line); + // Build the new header line + var atxHeader = new string('#', header.level); + // Write TOC line + tocContent.AppendLine($"{new string(' ', (header.level - 1) * 2)}- {link.TOCMarkdownLink()} {header.title}"); + line = $"{atxHeader} {(isAnnexes && (header.level == 1) ? "Annex " : "")}{link.NewLinkText} {header.title}"; } - } - if (dryRun) - { - File.Delete(tmpFileName); - } - else - { - File.Move(tmpFileName, pathToFile, true); + await writeStream.WriteLineAsync(line); } } - - /// - /// This method signals that all main sections have been processed. - /// - /// - /// After this method is called, all sections are interpreted as annexes. - /// - public void FinishMainSection() + if (dryRun) + { + File.Delete(tmpFileName); + } + else { - isAnnexes = true; - for (int i = 0; i < headings.Length; i++) headings[i] = 0; + File.Move(tmpFileName, pathToFile, true); } + } + + /// + /// This method signals that all main sections have been processed. + /// + /// + /// After this method is called, all sections are interpreted as annexes. + /// + public void FinishMainSection() + { + isAnnexes = true; + for (int i = 0; i < headings.Length; i++) headings[i] = 0; + } - /// - /// Retrieve the text of the TOC - /// - public string Toc => tocContent.ToString(); + /// + /// Retrieve the text of the TOC + /// + public string Toc => tocContent.ToString(); - /// - /// Retrieve a readonly map of existing link text to updated link. - /// - public IReadOnlyDictionary LinkMap => sectionLinkMap; + /// + /// Retrieve a readonly map of existing link text to updated link. + /// + public IReadOnlyDictionary LinkMap => sectionLinkMap; - private SectionLink BuildSectionLink(SectionHeader header, string filename) + private SectionLink BuildSectionLink(SectionHeader header, string filename) + { + // Now, construct the mapping, add TOC, construct output line. + // Set all headings: + headings[header.level - 1]++; + for (int index = header.level; index < headings.Length; index++) + headings[index] = 0; + + // Generate the correct clause name: + string newSectionNumber = isAnnexes + ? string.Join('.', headings.Take(header.level).Select((n, index) => (index == 0) ? ((char)(n + 64)).ToString() : n.ToString())) + : string.Join('.', headings.Take(header.level).Select(n => n.ToString())); + string anchor = UrilizeAsGfm($"{newSectionNumber} {header.title}"); + + // Top-level annex references (e.g. just to "Annex D") need a leading "annex-" as that's + // in the title of the page. + if (isAnnexes && (header.level == 1)) { - // Now, construct the mapping, add TOC, construct output line. - // Set all headings: - headings[header.level - 1]++; - for (int index = header.level; index < headings.Length; index++) - headings[index] = 0; - - // Generate the correct clause name: - string newSectionNumber = isAnnexes - ? string.Join('.', headings.Take(header.level).Select((n, index) => (index == 0) ? ((char)(n + 64)).ToString() : n.ToString())) - : string.Join('.', headings.Take(header.level).Select(n => n.ToString())); - string anchor = UrilizeAsGfm($"{newSectionNumber} {header.title}"); - - // Top-level annex references (e.g. just to "Annex D") need a leading "annex-" as that's - // in the title of the page. - if (isAnnexes && (header.level == 1)) - { - anchor = $"annex-{anchor}"; - } - return new SectionLink(header.sectionHeaderText, newSectionNumber, $"{filename}#{anchor}"); + anchor = $"annex-{anchor}"; } + return new SectionLink(header.sectionHeaderText, newSectionNumber, $"{filename}#{anchor}"); + } - // Copy from https://github.com/xoofx/markdig/blob/0cfe6d7da48ea6621072eb50ade32141ea92bc35/src/Markdig/Helpers/LinkHelper.cs#L100-L113 - private static string UrilizeAsGfm(string headingText) + // Copy from https://github.com/xoofx/markdig/blob/0cfe6d7da48ea6621072eb50ade32141ea92bc35/src/Markdig/Helpers/LinkHelper.cs#L100-L113 + private static string UrilizeAsGfm(string headingText) + { + // Following https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb + var headingBuffer = new StringBuilder(); + for (int i = 0; i < headingText.Length; i++) { - // Following https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb - var headingBuffer = new StringBuilder(); - for (int i = 0; i < headingText.Length; i++) + var c = headingText[i]; + if (char.IsLetterOrDigit(c) || c == ' ' || c == '-' || c == '_') { - var c = headingText[i]; - if (char.IsLetterOrDigit(c) || c == ' ' || c == '-' || c == '_') - { - headingBuffer.Append(c == ' ' ? '-' : char.ToLowerInvariant(c)); - } + headingBuffer.Append(c == ' ' ? '-' : char.ToLowerInvariant(c)); } - return headingBuffer.ToString(); } + return headingBuffer.ToString(); + } - // A line in the standard is either a paragraph of text or - // a header. this method determines which and returns one - // of the two types. - private SectionHeader? FindHeader(string lineRead) + // A line in the standard is either a paragraph of text or + // a header. this method determines which and returns one + // of the two types. + private SectionHeader? FindHeader(string lineRead) + { + // Blank line: + if (string.IsNullOrWhiteSpace(lineRead)) { - // Blank line: - if (string.IsNullOrWhiteSpace(lineRead)) - { - return null; - } + return null; + } - var fields = lineRead.Split(' ', 3, StringSplitOptions.RemoveEmptyEntries); + var fields = lineRead.Split(' ', 3, StringSplitOptions.RemoveEmptyEntries); - int level = fields[0].All(c => c == '#') ? fields[0].Length : 0; - // input line is a paragraph of text. - if (level == 0) - { - return null; - } - SectionHeader header = new() - { - level = level - }; + int level = fields[0].All(c => c == '#') ? fields[0].Length : 0; + // input line is a paragraph of text. + if (level == 0) + { + return null; + } + SectionHeader header = new() + { + level = level + }; - // A few cases for section number: - // 1. Starts with §: represents a newly added section - if (fields[1].StartsWith("§")) - { - header.sectionHeaderText = fields[1]; - header.title = fields[2]; - return header; - } - (header.sectionHeaderText, header.title) = - (isAnnexes, level, fields[1]) switch - { - // Annex H1: "Annex B" (A-Z) - (true, 1, "Annex") => ("§" + fields[2][..1], fields[2][2..]), - // Annex H1, no section header. - (true, 1, _) => ("", fields[1] + " " + fields[2]), - // Annex, Hn: "D.1.2", or no section header: - // Main section, "12.7.2", or no section header text: - (_, _, _) => Regex.IsMatch(fields[1], (isAnnexes ? AnnexPattern : MainSectionPattern)) - ? ("§"+fields[1], fields[2]) - : ("", fields[1] + " " + fields[2]), - }; + // A few cases for section number: + // 1. Starts with §: represents a newly added section + if (fields[1].StartsWith("§")) + { + header.sectionHeaderText = fields[1]; + header.title = fields[2]; return header; } + (header.sectionHeaderText, header.title) = + (isAnnexes, level, fields[1]) switch + { + // Annex H1: "Annex B" (A-Z) + (true, 1, "Annex") => ("§" + fields[2][..1], fields[2][2..]), + // Annex H1, no section header. + (true, 1, _) => ("", fields[1] + " " + fields[2]), + // Annex, Hn: "D.1.2", or no section header: + // Main section, "12.7.2", or no section header text: + (_, _, _) => Regex.IsMatch(fields[1], (isAnnexes ? AnnexPattern : MainSectionPattern)) + ? ("§"+fields[1], fields[2]) + : ("", fields[1] + " " + fields[2]), + }; + return header; } } diff --git a/tools/Utilities/Clauses.cs b/tools/Utilities/Clauses.cs index 929ef3735..0c09b2dbe 100644 --- a/tools/Utilities/Clauses.cs +++ b/tools/Utilities/Clauses.cs @@ -1,16 +1,15 @@ -namespace Utilities +namespace Utilities; + +public class Clauses { - public class Clauses - { - public string[] FrontMatter { get; set; } = Array.Empty(); + public string[] FrontMatter { get; set; } = Array.Empty(); - public string[] ScopeAndConformance { get; set; } = Array.Empty(); + public string[] ScopeAndConformance { get; set; } = Array.Empty(); - public string[] LexicalStructure { get; set; } = Array.Empty(); - public string[] MainBody { get; set; } = Array.Empty(); + public string[] LexicalStructure { get; set; } = Array.Empty(); + public string[] MainBody { get; set; } = Array.Empty(); - // Sure, there's only one, but let's allow for possible expansion. - public string[] UnsafeClauses { get; set; } = Array.Empty(); - public string[] Annexes { get; set; } = Array.Empty(); - } + // Sure, there's only one, but let's allow for possible expansion. + public string[] UnsafeClauses { get; set; } = Array.Empty(); + public string[] Annexes { get; set; } = Array.Empty(); } \ No newline at end of file From 254ab765d9b2a9dfbd7a07a283a5bbc3f96dccd4 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Nov 2023 17:04:07 -0500 Subject: [PATCH 4/7] add editorconfig This one is consistent with the version on dotnet/docs. We can season to taste as we're ready. --- tools/.editorconfig | 210 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 tools/.editorconfig diff --git a/tools/.editorconfig b/tools/.editorconfig new file mode 100644 index 000000000..596c802dc --- /dev/null +++ b/tools/.editorconfig @@ -0,0 +1,210 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[project.json] +indent_size = 2 + +# C# and Visual Basic files +[*.{cs,vb}] +charset = utf-8-bom + +# Analyzers +dotnet_analyzer_diagnostic.category-Security.severity = error +dotnet_code_quality.ca1802.api_surface = private, internal + +# Miscellaneous style rules +dotnet_sort_system_directives_first = true +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code quality +dotnet_style_readonly_field = true:suggestion +dotnet_code_quality_unused_parameters = non_public:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring +dotnet_style_prefer_conditional_expression_over_return = true:refactoring + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = error + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +csharp_prefer_braces = true:refactoring +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:refactoring +csharp_style_expression_bodied_constructors = true:refactoring +csharp_style_expression_bodied_operators = true:refactoring +csharp_style_expression_bodied_properties = true:refactoring +csharp_style_expression_bodied_indexers = true:refactoring +csharp_style_expression_bodied_accessors = true:refactoring +csharp_style_expression_bodied_lambdas = true:refactoring +csharp_style_expression_bodied_local_functions = true:refactoring + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Namespace preference +csharp_style_namespace_declarations = file_scoped:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion + +# Visual Basic files +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf + +[*.{cmd, bat}] +end_of_line = crlf + +# Markdown files +[*.md] + # Double trailing spaces can be used for BR tags, and other instances are enforced by Markdownlint +trim_trailing_whitespace = false From 428370e0030c4278ef4e712d57cf45adb0153aad Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Nov 2023 17:06:35 -0500 Subject: [PATCH 5/7] Use the .NET 8 SDK for all our tools. --- .github/workflows/grammar-validator.yaml | 4 ++-- .github/workflows/renumber-sections.yaml | 4 ++-- .github/workflows/smart-quotes.yaml | 4 ++-- .github/workflows/test-examples.yaml | 4 ++-- .github/workflows/tools-tests.yaml | 4 ++-- .github/workflows/update-on-merge.yaml | 4 ++-- .github/workflows/word-converter.yaml | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/grammar-validator.yaml b/.github/workflows/grammar-validator.yaml index 243677862..b72fdafda 100644 --- a/.github/workflows/grammar-validator.yaml +++ b/.github/workflows/grammar-validator.yaml @@ -21,10 +21,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Set up JDK 15 uses: actions/setup-java@v1 diff --git a/.github/workflows/renumber-sections.yaml b/.github/workflows/renumber-sections.yaml index 35ef4e3ff..1cc0828c3 100644 --- a/.github/workflows/renumber-sections.yaml +++ b/.github/workflows/renumber-sections.yaml @@ -21,10 +21,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Run section renumbering dry run run: | diff --git a/.github/workflows/smart-quotes.yaml b/.github/workflows/smart-quotes.yaml index 149c15a98..551084179 100644 --- a/.github/workflows/smart-quotes.yaml +++ b/.github/workflows/smart-quotes.yaml @@ -24,10 +24,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Smarten quotes id: smarten-quote diff --git a/.github/workflows/test-examples.yaml b/.github/workflows/test-examples.yaml index 06f167655..dae5436db 100644 --- a/.github/workflows/test-examples.yaml +++ b/.github/workflows/test-examples.yaml @@ -23,10 +23,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Extract and validate tests run: | diff --git a/.github/workflows/tools-tests.yaml b/.github/workflows/tools-tests.yaml index b70e3f005..ff3790536 100644 --- a/.github/workflows/tools-tests.yaml +++ b/.github/workflows/tools-tests.yaml @@ -25,10 +25,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Run all tests run: | diff --git a/.github/workflows/update-on-merge.yaml b/.github/workflows/update-on-merge.yaml index fe361566b..e2c195ec1 100644 --- a/.github/workflows/update-on-merge.yaml +++ b/.github/workflows/update-on-merge.yaml @@ -29,10 +29,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Set up JDK 15 uses: actions/setup-java@v1 diff --git a/.github/workflows/word-converter.yaml b/.github/workflows/word-converter.yaml index 379724e52..02b38eb62 100644 --- a/.github/workflows/word-converter.yaml +++ b/.github/workflows/word-converter.yaml @@ -21,10 +21,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 6.0 + - name: Setup .NET 8.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Run converter run: | From 27ef5e3fbcc240128fcb7025a36c36bf340dd2c2 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Nov 2023 17:43:51 -0500 Subject: [PATCH 6/7] Revert the grammar validator to .net 6 I've got a PR in that repo to update it. Once approved, I'll install the NuGet package here, and update that YML file. --- .github/workflows/grammar-validator.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/grammar-validator.yaml b/.github/workflows/grammar-validator.yaml index b72fdafda..243677862 100644 --- a/.github/workflows/grammar-validator.yaml +++ b/.github/workflows/grammar-validator.yaml @@ -21,10 +21,10 @@ jobs: - name: Check out our repo uses: actions/checkout@v2 - - name: Setup .NET 8.0 + - name: Setup .NET 6.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.0.x + dotnet-version: 6.0.x - name: Set up JDK 15 uses: actions/setup-java@v1 From 7b0531f5625561e693af75ab9442aa2313315613 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 22 Nov 2023 09:23:45 -0500 Subject: [PATCH 7/7] respond to review comments - Update Roslyn packages to 4.8 - Remove coverlet - Formatting in csproj. --- tools/ExampleTester/ExampleTester.csproj | 4 ++-- .../MarkdownConverter.Tests/MarkdownConverter.Tests.csproj | 7 +------ tools/MarkdownConverter/MarkdownConverter.csproj | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tools/ExampleTester/ExampleTester.csproj b/tools/ExampleTester/ExampleTester.csproj index e8c24d392..c3e3d0851 100644 --- a/tools/ExampleTester/ExampleTester.csproj +++ b/tools/ExampleTester/ExampleTester.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj b/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj index e46882f54..3f1755b09 100644 --- a/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj +++ b/tools/MarkdownConverter.Tests/MarkdownConverter.Tests.csproj @@ -3,8 +3,7 @@ net8.0 enable - enable - + enable false @@ -21,10 +20,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - diff --git a/tools/MarkdownConverter/MarkdownConverter.csproj b/tools/MarkdownConverter/MarkdownConverter.csproj index 0d8f506f9..7104e98c5 100644 --- a/tools/MarkdownConverter/MarkdownConverter.csproj +++ b/tools/MarkdownConverter/MarkdownConverter.csproj @@ -13,7 +13,7 @@ - +