From a173759f6b15ca84a896b51340db70b6c9ab16a1 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 31 Mar 2025 19:39:58 +0200 Subject: [PATCH 1/2] Fix CurrentUrlPath being empty on deeply nested snippets --- src/Elastic.Markdown/IO/DocumentationSet.cs | 6 ++---- src/Elastic.Markdown/IO/MarkdownFile.cs | 4 ++++ .../IO/Navigation/DocumentationGroup.cs | 3 +++ .../Myst/Directives/DirectiveHtmlRenderer.cs | 4 +++- .../InlineParsers/DiagnosticLinkInlineParser.cs | 17 ++++++++++++----- src/Elastic.Markdown/Myst/ParserContext.cs | 6 ++++-- .../Directives/DirectiveBaseTests.cs | 2 +- .../Inline/InlneBaseTests.cs | 2 +- 8 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Elastic.Markdown/IO/DocumentationSet.cs b/src/Elastic.Markdown/IO/DocumentationSet.cs index 01d2d936e..566aca153 100644 --- a/src/Elastic.Markdown/IO/DocumentationSet.cs +++ b/src/Elastic.Markdown/IO/DocumentationSet.cs @@ -213,12 +213,10 @@ void ValidateExists(string from, string to, IReadOnlyDictionary public FrozenDictionary MarkdownFiles { get; } - public MarkdownFile? DocumentationFileLookup(IFileInfo sourceFile) + public DocumentationFile? DocumentationFileLookup(IFileInfo sourceFile) { var relativePath = Path.GetRelativePath(SourceDirectory.FullName, sourceFile.FullName); - if (FlatMappedFiles.TryGetValue(relativePath, out var file) && file is MarkdownFile markdownFile) - return markdownFile; - return null; + return FlatMappedFiles.GetValueOrDefault(relativePath); } public MarkdownFile? GetPrevious(MarkdownFile current) diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index a2ee702ef..3db1e1bf6 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -41,6 +41,8 @@ DocumentationSet set { FileName = sourceFile.Name; FilePath = sourceFile.FullName; + IsIndex = FileName == "index.md"; + UrlPathPrefix = build.UrlPathPrefix; MarkdownParser = parser; Collector = build.Collector; @@ -76,6 +78,8 @@ public DocumentationGroup? Parent public YamlFrontMatter? YamlFrontMatter { get; private set; } public string? TitleRaw { get; protected set; } + public bool IsIndex { get; internal set; } + public string? Title { get => _title; diff --git a/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs b/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs index a283a075c..6fcfbe46d 100644 --- a/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs +++ b/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs @@ -315,6 +315,9 @@ .. documentationFiles } } + if (indexFile is not null) + indexFile.IsIndex = true; + return indexFile ?? files.FirstOrDefault(); } diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index ac991ee24..0d281cee3 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -227,8 +227,10 @@ private static void WriteIncludeBlock(HtmlRenderer renderer, IncludeBlock block, return; var snippet = block.Build.ReadFileSystem.FileInfo.New(block.IncludePath); - var parentPath = block.Context.MarkdownSourcePath; + + var parentPath = block.Context.MarkdownParentPath ?? block.Context.MarkdownSourcePath; var document = parser.ParseSnippetAsync(snippet, parentPath, block.Context.YamlFrontMatter, default).GetAwaiter().GetResult(); + var html = document.ToHtml(parser.Pipeline); _ = renderer.Write(html); } diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index 78984c203..b307c7bb9 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -289,11 +289,14 @@ private static IFileInfo ResolveFile(ParserContext context, string url) => private static void ValidateAnchor(InlineProcessor processor, MarkdownFile markdown, string anchor, LinkInline link) { if (!markdown.Anchors.Contains(anchor)) - processor.EmitError(link, $"`{anchor}` does not exist in {markdown.FileName}."); + processor.EmitError(link, $"`{anchor}` does not exist in {markdown.RelativePath}."); } private static void UpdateLinkUrl(LinkInline link, string url, ParserContext context, string? anchor) { + // TODO revisit when we refactor our documentation set graph + // This method grew too complex, we need to revisit our documentation set graph generation so we can ask these questions + // on `DocumentationFile` that are mostly precomputed var urlPathPrefix = context.Build.UrlPathPrefix ?? string.Empty; if (!url.StartsWith('/') && !string.IsNullOrEmpty(url)) @@ -303,16 +306,19 @@ private static void UpdateLinkUrl(LinkInline link, string url, ParserContext con ? context.CurrentUrlPath[urlPathPrefix.Length..] : urlPathPrefix; - var markdownPath = context.MarkdownSourcePath.Name; + // if we are trying to resolve a relative url from a _snippet folder ensure we eat the _snippet folder + // as it's not part of url by chopping of the extra parent navigation + if (url.StartsWith("../") && context.DocumentationFileLookup(context.MarkdownSourcePath) is SnippetFile snippetFile) + url = url.Substring(3); + // TODO check through context.DocumentationFileLookup if file is index vs "index.md" check + var markdownPath = context.MarkdownSourcePath; // if the current path is an index e.g /reference/cloud-k8s/ // './' current path lookups should be relative to sub-path. // If it's not e.g /reference/cloud-k8s/api-docs/ these links should resolve on folder up. - var siblingsGoToCurrent = url.StartsWith("./") && markdownPath == "index.md"; var lastIndexPath = subPrefix.LastIndexOf('/'); - if (lastIndexPath >= 0 && !siblingsGoToCurrent) + if (url.StartsWith("./") && lastIndexPath >= 0 && markdownPath.Name != "index.md") subPrefix = subPrefix[..lastIndexPath]; - var combined = '/' + Path.Combine(subPrefix, url).TrimStart('/'); url = Path.GetFullPath(combined); } @@ -341,6 +347,7 @@ private static void UpdateLinkUrl(LinkInline link, string url, ParserContext con url = url[..^5]; link.Url = string.IsNullOrEmpty(anchor) ? url : $"{url}#{anchor}"; + } private static bool IsCrossLink([NotNullWhen(true)] Uri? uri) => diff --git a/src/Elastic.Markdown/Myst/ParserContext.cs b/src/Elastic.Markdown/Myst/ParserContext.cs index eefd87dcd..5ffa5f49e 100644 --- a/src/Elastic.Markdown/Myst/ParserContext.cs +++ b/src/Elastic.Markdown/Myst/ParserContext.cs @@ -53,6 +53,7 @@ public class ParserContext : MarkdownParserContext, IParserResolvers public ConfigurationFile Configuration { get; } public ICrossLinkResolver CrossLinkResolver { get; } public IFileInfo MarkdownSourcePath { get; } + public IFileInfo? MarkdownParentPath { get; } public string CurrentUrlPath { get; } public YamlFrontMatter? YamlFrontMatter { get; } public BuildContext Build { get; } @@ -67,15 +68,16 @@ public ParserContext(ParserState state) Configuration = state.Configuration; YamlFrontMatter = state.YamlFrontMatter; SkipValidation = state.SkipValidation; + MarkdownParentPath = state.ParentMarkdownPath; CrossLinkResolver = state.CrossLinkResolver; MarkdownSourcePath = state.MarkdownSourcePath; DocumentationFileLookup = state.DocumentationFileLookup; - var parentPath = state.ParentMarkdownPath; - CurrentUrlPath = DocumentationFileLookup(parentPath ?? MarkdownSourcePath) is MarkdownFile md + CurrentUrlPath = DocumentationFileLookup(state.ParentMarkdownPath ?? MarkdownSourcePath) is MarkdownFile md ? md.Url : string.Empty; + if (SkipValidation && string.IsNullOrEmpty(CurrentUrlPath)) { //TODO investigate this deeper. diff --git a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs index 56417613b..4b900ffdf 100644 --- a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs @@ -70,7 +70,7 @@ protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown") var context = new BuildContext(Collector, FileSystem); var linkResolver = new TestCrossLinkResolver(); Set = new DocumentationSet(context, logger, linkResolver); - File = Set.DocumentationFileLookup(FileSystem.FileInfo.New("docs/index.md")) ?? throw new NullReferenceException(); + File = Set.DocumentationFileLookup(FileSystem.FileInfo.New("docs/index.md")) as MarkdownFile ?? throw new NullReferenceException(); Html = default!; //assigned later Document = default!; } diff --git a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs index 99d5412c2..4229a7c80 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs @@ -117,7 +117,7 @@ protected InlineTest( }; var linkResolver = new TestCrossLinkResolver(); Set = new DocumentationSet(context, logger, linkResolver); - File = Set.DocumentationFileLookup(FileSystem.FileInfo.New("docs/index.md")) ?? throw new NullReferenceException(); + File = Set.DocumentationFileLookup(FileSystem.FileInfo.New("docs/index.md")) as MarkdownFile ?? throw new NullReferenceException(); Html = default!; //assigned later Document = default!; } From 227e3ae426b3f96b077d193f574c71fe8c8316e6 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 31 Mar 2025 19:52:49 +0200 Subject: [PATCH 2/2] fix tests --- .../Myst/InlineParsers/DiagnosticLinkInlineParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index b307c7bb9..7947f6b6e 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -317,7 +317,7 @@ private static void UpdateLinkUrl(LinkInline link, string url, ParserContext con // './' current path lookups should be relative to sub-path. // If it's not e.g /reference/cloud-k8s/api-docs/ these links should resolve on folder up. var lastIndexPath = subPrefix.LastIndexOf('/'); - if (url.StartsWith("./") && lastIndexPath >= 0 && markdownPath.Name != "index.md") + if (lastIndexPath >= 0 && markdownPath.Name != "index.md") subPrefix = subPrefix[..lastIndexPath]; var combined = '/' + Path.Combine(subPrefix, url).TrimStart('/'); url = Path.GetFullPath(combined);