diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index e72b91e65..7a322817f 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -89,7 +89,7 @@ public MarkdownFile[] YieldParents() return parents.ToArray(); } - public async Task MinimalParse(Cancel ctx) + public async Task MinimalParseAsync(Cancel ctx) { var document = await MarkdownParser.MinimalParseAsync(SourceFile, ctx); ReadDocumentInstructions(document); @@ -99,7 +99,7 @@ public async Task MinimalParse(Cancel ctx) public async Task ParseFullAsync(Cancel ctx) { if (!_instructionsParsed) - await MinimalParse(ctx); + await MinimalParseAsync(ctx); var document = await MarkdownParser.ParseAsync(SourceFile, YamlFrontMatter, ctx); return document; diff --git a/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs b/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs index 3a80550ab..3dd78363f 100644 --- a/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs +++ b/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs @@ -146,10 +146,10 @@ public async Task Resolve(Cancel ctx = default) if (_resolved) return; - await Parallel.ForEachAsync(FilesInOrder, ctx, async (file, token) => await file.MinimalParse(token)); + await Parallel.ForEachAsync(FilesInOrder, ctx, async (file, token) => await file.MinimalParseAsync(token)); await Parallel.ForEachAsync(GroupsInOrder, ctx, async (group, token) => await group.Resolve(token)); - await (Index?.MinimalParse(ctx) ?? Task.CompletedTask); + await (Index?.MinimalParseAsync(ctx) ?? Task.CompletedTask); _resolved = true; } diff --git a/src/Elastic.Markdown/Myst/MarkdownParser.cs b/src/Elastic.Markdown/Myst/MarkdownParser.cs index 3f6a956ea..1aa89a12c 100644 --- a/src/Elastic.Markdown/Myst/MarkdownParser.cs +++ b/src/Elastic.Markdown/Myst/MarkdownParser.cs @@ -14,6 +14,7 @@ using Elastic.Markdown.Myst.Substitution; using Markdig; using Markdig.Extensions.EmphasisExtras; +using Markdig.Parsers; using Markdig.Syntax; namespace Elastic.Markdown.Myst; @@ -28,34 +29,59 @@ public class MarkdownParser( private BuildContext Context { get; } = context; - public static MarkdownPipeline MinimalPipeline { get; } = - new MarkdownPipelineBuilder() - .UseYamlFrontMatter() - .UseInlineAnchors() - .UseHeadingsWithSlugs() - .UseDirectives() - .Build(); + // ReSharper disable once InconsistentNaming + private static MarkdownPipeline? _minimalPipeline; + public static MarkdownPipeline MinimalPipeline + { + get + { + if (_minimalPipeline is not null) + return _minimalPipeline; + var builder = new MarkdownPipelineBuilder() + .UseYamlFrontMatter() + .UseInlineAnchors() + .UseHeadingsWithSlugs() + .UseDirectives(); + + builder.BlockParsers.TryRemove(); + _minimalPipeline = builder.Build(); + return _minimalPipeline; + + } + } - public static MarkdownPipeline Pipeline { get; } = - new MarkdownPipelineBuilder() - .EnableTrackTrivia() - .UseInlineAnchors() - .UsePreciseSourceLocation() - .UseDiagnosticLinks() - .UseHeadingsWithSlugs() - .UseEmphasisExtras(EmphasisExtraOptions.Default) - .UseSoftlineBreakAsHardlineBreak() - .UseSubstitution() - .UseComments() - .UseYamlFrontMatter() - .UseGridTables() - .UsePipeTables() - .UseDirectives() - .UseDefinitionLists() - .UseEnhancedCodeBlocks() - .DisableHtml() - .UseHardBreaks() - .Build(); + // ReSharper disable once InconsistentNaming + private static MarkdownPipeline? _pipeline; + public static MarkdownPipeline Pipeline + { + get + { + if (_pipeline is not null) + return _pipeline; + + var builder = new MarkdownPipelineBuilder() + .EnableTrackTrivia() + .UseInlineAnchors() + .UsePreciseSourceLocation() + .UseDiagnosticLinks() + .UseHeadingsWithSlugs() + .UseEmphasisExtras(EmphasisExtraOptions.Default) + .UseSoftlineBreakAsHardlineBreak() + .UseSubstitution() + .UseComments() + .UseYamlFrontMatter() + .UseGridTables() + .UsePipeTables() + .UseDirectives() + .UseDefinitionLists() + .UseEnhancedCodeBlocks() + .DisableHtml() + .UseHardBreaks(); + builder.BlockParsers.TryRemove(); + _pipeline = builder.Build(); + return _pipeline; + } + } public ConfigurationFile Configuration { get; } = configuration; diff --git a/tests/authoring/Framework/ErrorCollectorAssertions.fs b/tests/authoring/Framework/ErrorCollectorAssertions.fs index 9ceff469b..1366f159d 100644 --- a/tests/authoring/Framework/ErrorCollectorAssertions.fs +++ b/tests/authoring/Framework/ErrorCollectorAssertions.fs @@ -16,7 +16,8 @@ module DiagnosticsCollectorAssertions = [] let hasNoErrors (actual: Lazy) = let actual = actual.Value - test <@ actual.Context.Collector.Errors = 0 @> + let errors = actual.Context.Collector.Errors + test <@ errors = 0 @> [] let hasError (expected: string) (actual: Lazy) = diff --git a/tests/authoring/Framework/HtmlAssertions.fs b/tests/authoring/Framework/HtmlAssertions.fs index dc55543a3..e9a1836ed 100644 --- a/tests/authoring/Framework/HtmlAssertions.fs +++ b/tests/authoring/Framework/HtmlAssertions.fs @@ -14,6 +14,7 @@ open AngleSharp.Html.Parser open DiffPlex.DiffBuilder open DiffPlex.DiffBuilder.Model open JetBrains.Annotations +open Swensen.Unquote open Xunit.Sdk [] @@ -87,7 +88,7 @@ actual: {actual} use sw = new StringWriter() document.Body.Children |> Seq.iter _.ToHtml(sw, PrettyMarkupFormatter()) - sw.ToString() + sw.ToString().TrimStart('\n') let private createDiff expected actual = let diffs = @@ -110,15 +111,37 @@ actual: {actual} """ raise (XunitException(msg)) + [] + let toHtml ([]expected: string) (actual: MarkdownResult) = + createDiff expected actual.Html + [] let convertsToHtml ([]expected: string) (actual: Lazy) = let actual = actual.Value let defaultFile = actual.MarkdownResults |> Seq.head - createDiff expected defaultFile.Html + defaultFile |> toHtml expected [] - let toHtml ([]expected: string) (actual: MarkdownResult) = - createDiff expected actual.Html + let containsHtml ([]expected: string) (actual: MarkdownResult) = + + let prettyExpected = prettyHtml expected + let prettyActual = prettyHtml actual.Html + + if not <| prettyActual.Contains prettyExpected then + let msg = $"""Expected html to contain: +{prettyExpected} +But was not found in: +{prettyActual} +""" + raise (XunitException(msg)) + + + [] + let convertsToContainingHtml ([]expected: string) (actual: Lazy) = + let actual = actual.Value + + let defaultFile = actual.MarkdownResults |> Seq.head + defaultFile |> containsHtml expected diff --git a/tests/authoring/Framework/MarkdownDocumentAssertions.fs b/tests/authoring/Framework/MarkdownDocumentAssertions.fs new file mode 100644 index 000000000..c5d3c3a77 --- /dev/null +++ b/tests/authoring/Framework/MarkdownDocumentAssertions.fs @@ -0,0 +1,25 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace authoring + +open System.Diagnostics +open Markdig.Syntax +open Xunit.Sdk + +module MarkdownDocumentAssertions = + + [] + let parses<'element when 'element :> MarkdownObject> (actual: MarkdownResult) = + let unsupportedBlocks = actual.Document.Descendants<'element>() |> Array.ofSeq + if unsupportedBlocks.Length = 0 then + raise (XunitException($"Could not find {typedefof<'element>.Name} in fully parsed document")) + unsupportedBlocks; + + [] + let parsesMinimal<'element when 'element :> MarkdownObject> (actual: MarkdownResult) = + let unsupportedBlocks = actual.MinimalParse.Descendants<'element>() |> Array.ofSeq + if unsupportedBlocks.Length = 0 then + raise (XunitException($"Could not find {typedefof<'element>.Name} in minimally parsed document")) + unsupportedBlocks; diff --git a/tests/authoring/Framework/TestValues.fs b/tests/authoring/Framework/TestValues.fs index 2bffd76c6..6fbd89da9 100644 --- a/tests/authoring/Framework/TestValues.fs +++ b/tests/authoring/Framework/TestValues.fs @@ -61,6 +61,7 @@ type TestLoggerFactory () = type MarkdownResult = { File: MarkdownFile + MinimalParse: MarkdownDocument Document: MarkdownDocument Html: string Context: MarkdownTestContext @@ -89,8 +90,9 @@ and MarkdownTestContext = |> Seq.map (fun (f: MarkdownFile) -> task { // technically we do this work twice since generate all also does it let! document = f.ParseFullAsync(ctx) + let! minimal = f.MinimalParseAsync(ctx) let html = f.CreateHtml(document) - return { File = f; Document = document; Html = html; Context = this } + return { File = f; Document = document; MinimalParse = minimal; Html = html; Context = this } }) // this is not great code, refactor or depend on FSharp.Control.TaskSeq // for now this runs without issue diff --git a/tests/authoring/Inline/InlineAnchors.fs b/tests/authoring/Inline/InlineAnchors.fs index 73f9552b5..e068f4f54 100644 --- a/tests/authoring/Inline/InlineAnchors.fs +++ b/tests/authoring/Inline/InlineAnchors.fs @@ -4,8 +4,13 @@ module ``inline elements``.``anchors DEPRECATED`` +open Elastic.Markdown.Myst.InlineParsers +open Markdig.Syntax +open Swensen.Unquote +open System.Linq open Xunit open authoring +open authoring.MarkdownDocumentAssertions type ``inline anchor in the middle`` () = @@ -22,3 +27,83 @@ this is *regular* text and this $$$is-an-inline-anchor$$$ and this continues to """ [] let ``has no errors`` () = markdown |> hasNoErrors + +type ``inline anchors embedded in definition lists`` () = + + static let markdown = Setup.Generate [ + Index """# Testing nested inline anchors + +$$$search-type$$$ + +`search_type` +: (Optional, string) How distributed term frequencies are calculated for relevance scoring. + + ::::{dropdown} Valid values for `search_type` + `query_then_fetch` + : (Default) Distributed term frequencies are calculated locally for each shard running the search. We recommend this option for faster searches with potentially less accurate scoring. + + $$$dfs-query-then-fetch$$$ + + `dfs_query_then_fetch` + : Distributed term frequencies are calculated globally, using information gathered from all shards running the search. + + :::: +""" + Markdown "file.md" """ + [Link to first](index.md#search-type) + [Link to second](index.md#dfs-query-then-fetch) + """ + ] + + [] + let ``emits nested inline anchor`` () = + markdown |> convertsToContainingHtml """""" + + [] + let ``emits definition list block anchor`` () = + markdown |> convertsToContainingHtml """""" + + [] + let ``has no errors`` () = markdown |> hasNoErrors + + [] + let ``minimal parse sees two inline anchors`` () = + let inlineAnchors = markdown |> converts "index.md" |> parsesMinimal + test <@ inlineAnchors.Length = 2 @> + + + +type ``inline anchors embedded in indented code`` () = + + static let markdown = Setup.Generate [ + Index """# Testing nested inline anchors + +$$$search-type$$$ + + indented codeblock + + $$$dfs-query-then-fetch$$$ + + block +""" + Markdown "file.md" """ + [Link to first](index.md#search-type) + [Link to second](index.md#dfs-query-then-fetch) + """ + ] + + [] + let ``emits nested inline anchor`` () = + markdown |> convertsToContainingHtml """""" + + [] + let ``emits definition list block anchor`` () = + markdown |> convertsToContainingHtml """""" + + [] + let ``has no errors`` () = markdown |> hasNoErrors + + [] + let ``minimal parse sees two inline anchors`` () = + let inlineAnchors = markdown |> converts "index.md" |> parsesMinimal + test <@ inlineAnchors.Length = 2 @> diff --git a/tests/authoring/authoring.fsproj b/tests/authoring/authoring.fsproj index fcfe76be1..663ca37f4 100644 --- a/tests/authoring/authoring.fsproj +++ b/tests/authoring/authoring.fsproj @@ -29,6 +29,7 @@ +