diff --git a/src/Elastic.Markdown/DocumentationGenerator.cs b/src/Elastic.Markdown/DocumentationGenerator.cs index 46fe8516f..a348f104c 100644 --- a/src/Elastic.Markdown/DocumentationGenerator.cs +++ b/src/Elastic.Markdown/DocumentationGenerator.cs @@ -9,12 +9,19 @@ using Elastic.Markdown.IO; using Elastic.Markdown.IO.State; using Elastic.Markdown.Slices; +using Markdig.Syntax; using Microsoft.Extensions.Logging; namespace Elastic.Markdown; +public interface IConversionCollector +{ + void Collect(MarkdownFile file, MarkdownDocument document, string html); +} + public class DocumentationGenerator { + private readonly IConversionCollector? _conversionCollector; private readonly IFileSystem _readFileSystem; private readonly ILogger _logger; private readonly IFileSystem _writeFileSystem; @@ -26,9 +33,11 @@ public class DocumentationGenerator public DocumentationGenerator( DocumentationSet docSet, - ILoggerFactory logger + ILoggerFactory logger, + IConversionCollector? conversionCollector = null ) { + _conversionCollector = conversionCollector; _readFileSystem = docSet.Context.ReadFileSystem; _writeFileSystem = docSet.Context.WriteFileSystem; _logger = logger.CreateLogger(nameof(DocumentationGenerator)); @@ -161,7 +170,7 @@ private async Task ProcessFile(HashSet offendingFiles, DocumentationFile _logger.LogTrace("--> {FileFullPath}", file.SourceFile.FullName); var outputFile = OutputFile(file.RelativePath); if (file is MarkdownFile markdown) - await HtmlWriter.WriteAsync(outputFile, markdown, token); + await HtmlWriter.WriteAsync(outputFile, markdown, _conversionCollector, token); else { if (outputFile.Directory is { Exists: false }) diff --git a/src/Elastic.Markdown/IO/DocumentationFile.cs b/src/Elastic.Markdown/IO/DocumentationFile.cs index bc869678f..5bc44b80a 100644 --- a/src/Elastic.Markdown/IO/DocumentationFile.cs +++ b/src/Elastic.Markdown/IO/DocumentationFile.cs @@ -2,6 +2,9 @@ // 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 using System.IO.Abstractions; +using Elastic.Markdown.Myst; +using Elastic.Markdown.Myst.FrontMatter; +using Elastic.Markdown.Slices; namespace Elastic.Markdown.IO; @@ -22,4 +25,32 @@ public record ExcludedFile(IFileInfo SourceFile, IDirectoryInfo RootPath) : DocumentationFile(SourceFile, RootPath); public record SnippetFile(IFileInfo SourceFile, IDirectoryInfo RootPath) - : DocumentationFile(SourceFile, RootPath); + : DocumentationFile(SourceFile, RootPath) +{ + private SnippetAnchors? Anchors { get; set; } + private bool _parsed; + + public SnippetAnchors? GetAnchors( + DocumentationSet set, + MarkdownParser parser, + YamlFrontMatter? frontMatter + ) + { + if (_parsed) + return Anchors; + if (!SourceFile.Exists) + { + _parsed = true; + return null; + } + + var document = parser.MinimalParseAsync(SourceFile, default).GetAwaiter().GetResult(); + var toc = MarkdownFile.GetAnchors(set, parser, frontMatter, document, new Dictionary(), out var anchors); + Anchors = new SnippetAnchors(anchors, toc); + _parsed = true; + return Anchors; + } +} + +public record SnippetAnchors(string[] Anchors, IReadOnlyCollection TableOfContentItems); + diff --git a/src/Elastic.Markdown/IO/DocumentationSet.cs b/src/Elastic.Markdown/IO/DocumentationSet.cs index 4ecbca534..cef23342c 100644 --- a/src/Elastic.Markdown/IO/DocumentationSet.cs +++ b/src/Elastic.Markdown/IO/DocumentationSet.cs @@ -187,16 +187,16 @@ private DocumentationFile CreateMarkDownFile(IFileInfo file, BuildContext contex if (Configuration.Exclude.Any(g => g.IsMatch(relativePath))) return new ExcludedFile(file, SourcePath); - if (Configuration.Files.Contains(relativePath)) - return new MarkdownFile(file, SourcePath, MarkdownParser, context); - - if (Configuration.Globs.Any(g => g.IsMatch(relativePath))) - return new MarkdownFile(file, SourcePath, MarkdownParser, context); - // we ignore files in folders that start with an underscore if (relativePath.Contains("_snippets")) return new SnippetFile(file, SourcePath); + if (Configuration.Files.Contains(relativePath)) + return new MarkdownFile(file, SourcePath, MarkdownParser, context, this); + + if (Configuration.Globs.Any(g => g.IsMatch(relativePath))) + return new MarkdownFile(file, SourcePath, MarkdownParser, context, this); + // we ignore files in folders that start with an underscore if (relativePath.IndexOf("/_", StringComparison.Ordinal) > 0 || relativePath.StartsWith('_')) return new ExcludedFile(file, SourcePath); diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 3c75a179a..5bac78c60 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -21,7 +21,15 @@ public record MarkdownFile : DocumentationFile { private string? _navigationTitle; - public MarkdownFile(IFileInfo sourceFile, IDirectoryInfo rootPath, MarkdownParser parser, BuildContext context) + private readonly DocumentationSet _set; + + public MarkdownFile( + IFileInfo sourceFile, + IDirectoryInfo rootPath, + MarkdownParser parser, + BuildContext context, + DocumentationSet set + ) : base(sourceFile, rootPath) { FileName = sourceFile.Name; @@ -30,6 +38,7 @@ public MarkdownFile(IFileInfo sourceFile, IDirectoryInfo rootPath, MarkdownParse MarkdownParser = parser; Collector = context.Collector; _configurationFile = context.Configuration.SourceFile; + _set = set; } public string Id { get; } = Guid.NewGuid().ToString("N")[..8]; @@ -191,36 +200,78 @@ private void ReadDocumentInstructions(MarkdownDocument document) else if (Title.AsSpan().ReplaceSubstitutions(subs, out var replacement)) Title = replacement; - var contents = document + if (RelativePath.Contains("esql-functions-operators")) + { + + } + var toc = GetAnchors(_set, MarkdownParser, YamlFrontMatter, document, subs, out var anchors); + + _tableOfContent.Clear(); + foreach (var t in toc) + _tableOfContent[t.Slug] = t; + + + foreach (var label in anchors) + _ = _anchors.Add(label); + + _instructionsParsed = true; + } + + public static List GetAnchors( + DocumentationSet set, + MarkdownParser parser, + YamlFrontMatter? frontMatter, + MarkdownDocument document, + IReadOnlyDictionary subs, + out string[] anchors) + { + var includeBlocks = document.Descendants().ToArray(); + var includes = includeBlocks + .Where(i => i.Found) + .Select(i => + { + var path = i.IncludePathFromSourceDirectory; + if (path is null + || !set.FlatMappedFiles.TryGetValue(path, out var file) + || file is not SnippetFile snippet) + return null; + + return snippet.GetAnchors(set, parser, frontMatter); + }) + .Where(i => i is not null) + .ToArray(); + + var includedTocs = includes.SelectMany(i => i!.TableOfContentItems).ToArray(); + var toc = document .Descendants() .Where(block => block is { Level: >= 2 }) .Select(h => (h.GetData("header") as string, h.GetData("anchor") as string, h.Level)) .Select(h => { var header = h.Item1!.StripMarkdown(); - if (header.AsSpan().ReplaceSubstitutions(subs, out var replacement)) - header = replacement; return new PageTocItem { Heading = header, Slug = (h.Item2 ?? header).Slugify(), Level = h.Level }; }) + .Concat(includedTocs) + .Select(toc => subs.Count == 0 + ? toc + : toc.Heading.AsSpan().ReplaceSubstitutions(subs, out var r) + ? toc with { Heading = r } + : toc) .ToList(); - _tableOfContent.Clear(); - foreach (var t in contents) - _tableOfContent[t.Slug] = t; - - var anchors = document.Descendants() - .Select(b => b.CrossReferenceName) - .Where(l => !string.IsNullOrWhiteSpace(l)) - .Select(s => s.Slugify()) - .Concat(document.Descendants().Select(a => a.Anchor)) - .Concat(_tableOfContent.Values.Select(t => t.Slug)) - .Where(anchor => !string.IsNullOrEmpty(anchor)) - .ToArray(); - - foreach (var label in anchors) - _ = _anchors.Add(label); - - _instructionsParsed = true; + var includedAnchors = includes.SelectMany(i => i!.Anchors).ToArray(); + anchors = + [ + ..document.Descendants() + .Select(b => b.CrossReferenceName) + .Where(l => !string.IsNullOrWhiteSpace(l)) + .Select(s => s.Slugify()) + .Concat(document.Descendants().Select(a => a.Anchor)) + .Concat(toc.Select(t => t.Slug)) + .Where(anchor => !string.IsNullOrEmpty(anchor)) + .Concat(includedAnchors) + ]; + return toc; } private YamlFrontMatter ProcessYamlFrontMatter(MarkdownDocument document) diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index 420fff70f..ae2a2315b 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -231,8 +231,6 @@ private static void WriteIncludeBlock(HtmlRenderer renderer, IncludeBlock block) var document = parser.ParseAsync(file, block.FrontMatter, default).GetAwaiter().GetResult(); var html = document.ToHtml(MarkdownParser.Pipeline); _ = renderer.Write(html); - //var slice = Include.Create(new IncludeViewModel { Html = html }); - //RenderRazorSlice(slice, renderer, block); } private static void WriteSettingsBlock(HtmlRenderer renderer, SettingsBlock block) diff --git a/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs b/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs index 30564430a..da258b790 100644 --- a/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs @@ -35,6 +35,7 @@ public class IncludeBlock(DirectiveBlockParser parser, ParserContext context) : public YamlFrontMatter? FrontMatter { get; } = context.FrontMatter; public string? IncludePath { get; private set; } + public string? IncludePathFromSourceDirectory { get; private set; } public bool Found { get; private set; } @@ -69,6 +70,7 @@ private void ExtractInclusionPath(ParserContext context) includeFrom = DocumentationSourcePath.FullName; IncludePath = Path.Combine(includeFrom, includePath.TrimStart('/')); + IncludePathFromSourceDirectory = Path.GetRelativePath(DocumentationSourcePath.FullName, IncludePath); if (FileSystem.File.Exists(IncludePath)) Found = true; else diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index 8910abe1e..5a4008b71 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -190,7 +190,7 @@ private static void ValidateInternalUrl(InlineProcessor processor, string url, s if (string.IsNullOrWhiteSpace(url)) return; - var pathOnDisk = Path.Combine(includeFrom, url.TrimStart('/')); + var pathOnDisk = Path.GetFullPath(Path.Combine(includeFrom, url.TrimStart('/'))); if (!context.Build.ReadFileSystem.File.Exists(pathOnDisk)) processor.EmitError(link, $"`{url}` does not exist. resolved to `{pathOnDisk}"); } diff --git a/src/Elastic.Markdown/Slices/HtmlWriter.cs b/src/Elastic.Markdown/Slices/HtmlWriter.cs index 6128d3bfd..988c74b27 100644 --- a/src/Elastic.Markdown/Slices/HtmlWriter.cs +++ b/src/Elastic.Markdown/Slices/HtmlWriter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; using Elastic.Markdown.IO; +using Markdig.Syntax; using RazorSlices; namespace Elastic.Markdown.Slices; @@ -26,6 +27,11 @@ private async Task RenderNavigation(MarkdownFile markdown, Cancel ctx = public async Task RenderLayout(MarkdownFile markdown, Cancel ctx = default) { var document = await markdown.ParseFullAsync(ctx); + return await RenderLayout(markdown, document, ctx); + } + + public async Task RenderLayout(MarkdownFile markdown, MarkdownDocument document, Cancel ctx = default) + { var html = MarkdownFile.CreateHtml(document); await DocumentationSet.Tree.Resolve(ctx); _renderedNavigation ??= await RenderNavigation(markdown, ctx); @@ -57,7 +63,7 @@ public async Task RenderLayout(MarkdownFile markdown, Cancel ctx = defau return await slice.RenderAsync(cancellationToken: ctx); } - public async Task WriteAsync(IFileInfo outputFile, MarkdownFile markdown, Cancel ctx = default) + public async Task WriteAsync(IFileInfo outputFile, MarkdownFile markdown, IConversionCollector? collector, Cancel ctx = default) { if (outputFile.Directory is { Exists: false }) outputFile.Directory.Create(); @@ -79,7 +85,9 @@ public async Task WriteAsync(IFileInfo outputFile, MarkdownFile markdown, Cancel : Path.Combine(dir, "index.html"); } - var rendered = await RenderLayout(markdown, ctx); + var document = await markdown.ParseFullAsync(ctx); + var rendered = await RenderLayout(markdown, document, ctx); + collector?.Collect(markdown, document, rendered); await writeFileSystem.File.WriteAllTextAsync(path, rendered, ctx); } diff --git a/src/Elastic.Markdown/Slices/_ViewModels.cs b/src/Elastic.Markdown/Slices/_ViewModels.cs index e6e54ddd1..f1193d1f5 100644 --- a/src/Elastic.Markdown/Slices/_ViewModels.cs +++ b/src/Elastic.Markdown/Slices/_ViewModels.cs @@ -69,7 +69,7 @@ public string Link(string path) } } -public class PageTocItem +public record PageTocItem { public required string Heading { get; init; } public required string Slug { get; init; } diff --git a/tests/authoring/Directives/IncludeBlocks.fs b/tests/authoring/Directives/IncludeBlocks.fs new file mode 100644 index 000000000..6577abb69 --- /dev/null +++ b/tests/authoring/Directives/IncludeBlocks.fs @@ -0,0 +1,84 @@ +// 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 + +module ``directive elements``.``include directive`` + +open Swensen.Unquote +open Xunit +open authoring +open authoring.MarkdownDocumentAssertions + +type ``include hoists anchors and table of contents`` () = + + static let generator = Setup.Generate [ + Index """ +# A Document that lives at the root + +:::{include} _snippets/my-snippet.md +::: +""" + Snippet "_snippets/my-snippet.md" """ +## header from snippet [aa] + + """ + Markdown "test-links.md" """ +# parent.md + +## some header +[link to root with included anchor](index.md#aa) + """ + ] + + [] + let ``validate index.md HTML includes snippet`` () = + generator |> converts "index.md" |> toHtml """ +

A Document that lives at the root

+ + """ + + [] + let ``validate test-links.md HTML includes snippet`` () = + generator |> converts "test-links.md" |> toHtml """ +

parent.md

+ +

link to root with included anchor

+ """ + + [] + let ``validate index.md includes table of contents`` () = + let page = generator |> converts "index.md" |> markdownFile + test <@ page.TableOfContents.Count = 1 @> + test <@ page.TableOfContents.ContainsKey("aa") @> + + [] + let ``has no errors`` () = generator |> hasNoErrors + + +type ``include can contain links to parent page's includes`` () = + + static let generator = Setup.Generate [ + Index """ +# A Document that lives at the root + +:::{include} _snippets/my-snippet.md +::: + +:::{include} _snippets/my-other-snippet.md +::: +""" + Snippet "_snippets/my-snippet.md" """ +## header from snippet [aa] + """ + + Snippet "_snippets/my-other-snippet.md" """ +[link to root with included anchor](../index.md#aa) + """ + ] + + [] + let ``has no errors`` () = generator |> hasNoErrors diff --git a/tests/authoring/Framework/HtmlAssertions.fs b/tests/authoring/Framework/HtmlAssertions.fs index e9a1836ed..7fa7afe61 100644 --- a/tests/authoring/Framework/HtmlAssertions.fs +++ b/tests/authoring/Framework/HtmlAssertions.fs @@ -9,6 +9,7 @@ open System.Diagnostics open System.IO open AngleSharp.Diffing open AngleSharp.Diffing.Core +open AngleSharp.Dom open AngleSharp.Html open AngleSharp.Html.Parser open DiffPlex.DiffBuilder @@ -82,12 +83,31 @@ actual: {actual} ) |> String.concat "\n" - let private prettyHtml (html:string) = + let private prettyHtml (html:string) (querySelector: string option) = let parser = HtmlParser() let document = parser.ParseDocument(html) + let element = + match querySelector with + | Some q -> document.QuerySelector q + | None -> document.Body + + let links = element.QuerySelectorAll("a") + links + |> Seq.iter(fun l -> + l.RemoveAttribute "hx-select-oob" |> ignore + l.RemoveAttribute "hx-swap" |> ignore + l.RemoveAttribute "hx-indicator" |> ignore + l.RemoveAttribute "hx-push-url" |> ignore + l.RemoveAttribute "preload" |> ignore + ) + use sw = new StringWriter() - document.Body.Children - |> Seq.iter _.ToHtml(sw, PrettyMarkupFormatter()) + let formatter = PrettyMarkupFormatter() + element.Children + |> Seq.indexed + |> Seq.filter (fun (i, c) -> (not <| (i = 0 && c.TagName = "H1"))) + |> Seq.map(fun (_, c) -> c) + |> Seq.iter _.ToHtml(sw, formatter) sw.ToString().TrimStart('\n') let private createDiff expected actual = @@ -101,32 +121,34 @@ actual: {actual} match deepComparision with | s when String.IsNullOrEmpty s -> () | s -> - let expectedHtml = prettyHtml expected - let actualHtml = prettyHtml actual - let textDiff = diff expectedHtml actualHtml + let textDiff = diff expected actual let msg = $"""Html was not equal +-- DIFF -- {textDiff} +-- Comparison -- {deepComparision} """ raise (XunitException(msg)) [] let toHtml ([]expected: string) (actual: MarkdownResult) = - createDiff expected actual.Html + let expectedHtml = prettyHtml expected None + let actualHtml = prettyHtml actual.Html (Some "section#elastic-docs-v3") + createDiff expectedHtml actualHtml [] let convertsToHtml ([]expected: string) (actual: Lazy) = let actual = actual.Value - let defaultFile = actual.MarkdownResults |> Seq.head + let defaultFile = actual.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") defaultFile |> toHtml expected [] let containsHtml ([]expected: string) (actual: MarkdownResult) = - let prettyExpected = prettyHtml expected - let prettyActual = prettyHtml actual.Html + let prettyExpected = prettyHtml expected None + let prettyActual = prettyHtml actual.Html (Some "section#elastic-docs-v3") if not <| prettyActual.Contains prettyExpected then let msg = $"""Expected html to contain: @@ -143,5 +165,5 @@ But was not found in: let convertsToContainingHtml ([]expected: string) (actual: Lazy) = let actual = actual.Value - let defaultFile = actual.MarkdownResults |> Seq.head + let defaultFile = actual.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") defaultFile |> containsHtml expected diff --git a/tests/authoring/Framework/MarkdownDocumentAssertions.fs b/tests/authoring/Framework/MarkdownDocumentAssertions.fs index 781d5e8cb..d5be69881 100644 --- a/tests/authoring/Framework/MarkdownDocumentAssertions.fs +++ b/tests/authoring/Framework/MarkdownDocumentAssertions.fs @@ -30,7 +30,7 @@ module MarkdownDocumentAssertions = [] let appliesTo (expectedAvailability: ApplicableTo) (actual: Lazy) = let actual = actual.Value - let result = actual.MarkdownResults |> Seq.head + let result = actual.MarkdownResults |> Seq.find (fun r -> r.File.RelativePath = "index.md") let matter = result.File.YamlFrontMatter match matter with | NonNull m -> @@ -48,3 +48,7 @@ module MarkdownDocumentAssertions = test <@ apply = expectedAvailability @> | _ -> failwithf "Could not locate an AppliesToDirective" + [] + let markdownFile (actual: MarkdownResult) = + actual.File + diff --git a/tests/authoring/Framework/Setup.fs b/tests/authoring/Framework/Setup.fs index c2850add7..fe2813dc9 100644 --- a/tests/authoring/Framework/Setup.fs +++ b/tests/authoring/Framework/Setup.fs @@ -25,6 +25,7 @@ type Markdown = string type TestFile = | File of name: string * contents: string | MarkdownFile of name: string * markdown: Markdown + | SnippetFile of name: string * markdown: Markdown static member Index ([] m) = MarkdownFile("index.md" , m) @@ -32,6 +33,9 @@ type TestFile = static member Markdown path ([] m) = MarkdownFile(path , m) + static member Snippet path ([] m) = + SnippetFile(path , m) + type Setup = static let GenerateDocSetYaml( @@ -82,6 +86,7 @@ type Setup = |> Seq.map (fun f -> match f with | File(name, contents) -> ($"docs/{name}", MockFileData(contents)) + | SnippetFile(name, markdown) -> ($"docs/{name}", MockFileData(markdown)) | MarkdownFile(name, markdown) -> ($"docs/{name}", MockFileData(markdown)) ) |> Map.ofSeq @@ -94,28 +99,14 @@ type Setup = let collector = TestDiagnosticsCollector(); let context = BuildContext(collector, fileSystem) let logger = new TestLoggerFactory() + let conversionCollector = TestConversionCollector() let linkResolver = TestCrossLinkResolver(context.Configuration) let set = DocumentationSet(context, logger, linkResolver); - let generator = DocumentationGenerator(set, logger) - - let markdownFiles = - files - |> Seq.map (fun f -> - match f with - | File _ -> None - | MarkdownFile(name, _) -> Some $"docs/{name}" - ) - |> Seq.choose id - |> Seq.map (fun f -> - match set.GetMarkdownFile(fileSystem.FileInfo.New(f)) with - | NonNull m -> Some m - | _ -> None - ) - |> Seq.choose id + let generator = DocumentationGenerator(set, logger, conversionCollector) let context = { - MarkdownFiles = markdownFiles Collector = collector + ConversionCollector= conversionCollector Set = set Generator = generator ReadFileSystem = fileSystem diff --git a/tests/authoring/Framework/TestValues.fs b/tests/authoring/Framework/TestValues.fs index 8399a7f25..7dc33ad07 100644 --- a/tests/authoring/Framework/TestValues.fs +++ b/tests/authoring/Framework/TestValues.fs @@ -5,6 +5,7 @@ namespace authoring open System +open System.Collections.Concurrent open System.IO.Abstractions open Elastic.Markdown open Elastic.Markdown.Diagnostics @@ -59,6 +60,20 @@ type TestLoggerFactory () = member this.Dispose() = () +type ConversionResult = { + File: MarkdownFile + Document: MarkdownDocument + Html: string +} + +type TestConversionCollector () = + let x = ConcurrentDictionary() + member this.Results = x + interface IConversionCollector with + member this.Collect(file, document, html) = + this.Results.TryAdd(file.RelativePath, { File= file; Document=document;Html=html}) |> ignore + + type MarkdownResult = { File: MarkdownFile MinimalParse: MarkdownDocument @@ -73,8 +88,8 @@ and GeneratorResults = { and MarkdownTestContext = { - MarkdownFiles: MarkdownFile seq Collector: TestDiagnosticsCollector + ConversionCollector: TestConversionCollector Set: DocumentationSet Generator: DocumentationGenerator ReadFileSystem: IFileSystem @@ -86,13 +101,13 @@ and MarkdownTestContext = do! this.Generator.GenerateAll(ctx) let results = - this.MarkdownFiles - |> 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 = MarkdownFile.CreateHtml(document) - return { File = f; Document = document; MinimalParse = minimal; Html = html; Context = this } + this.ConversionCollector.Results + |> Seq.map (fun kv -> task { + let file = kv.Value.File + let document = kv.Value.Document + let html = kv.Value.Html + let! minimal = kv.Value.File.MinimalParseAsync(ctx) + return { File = file; 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/RelativeLinks.fs b/tests/authoring/Inline/RelativeLinks.fs new file mode 100644 index 000000000..3e38a3441 --- /dev/null +++ b/tests/authoring/Inline/RelativeLinks.fs @@ -0,0 +1,52 @@ +// 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 + +module ``inline elemenents``.``relative links`` + +open Xunit +open authoring + +type ``two pages with anchors end up in artifact`` () = + + static let generator = Setup.Generate [ + Index """ +# A Document that lives at the root + +*Welcome* to this documentation + +## This anchor is autogenerated + +### Several pages can be created [#and-anchored] + +Through various means $$$including-this-inline-syntax$$$ +""" + Markdown "deeply/nested/file.md" """ +# file.md + +[link to root](../../index.md#and-anchored) + +[link to parent](../parent.md) + +[link to parent](../parent.md#some-header) + """ + Markdown "deeply/parent.md" """ +# parent.md + +## some header +[link to root](../index.md) + """ + ] + + [] + let ``validate index.md HTML`` () = + generator |> converts "deeply/nested/file.md" |> toHtml """ +

link to root

+

link to parent

+

link to parent

+ """ + + [] + let ``has no errors`` () = generator |> hasNoErrors + + diff --git a/tests/authoring/authoring.fsproj b/tests/authoring/authoring.fsproj index 339647ad9..34ef73f6e 100644 --- a/tests/authoring/authoring.fsproj +++ b/tests/authoring/authoring.fsproj @@ -51,6 +51,7 @@ + @@ -66,4 +67,8 @@ + + + +