diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index 7947f6b6e..0a5208a26 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -207,7 +207,7 @@ private static void ProcessInternalLink(LinkInline link, InlineProcessor process var linkMarkdown = SetLinkData(link, processor, context, file, url); ProcessLinkText(processor, link, linkMarkdown, anchor, url, file); - UpdateLinkUrl(link, url, context, anchor); + UpdateLinkUrl(link, linkMarkdown, url, context, anchor); } private static MarkdownFile? SetLinkData(LinkInline link, InlineProcessor processor, ParserContext context, @@ -292,61 +292,72 @@ private static void ValidateAnchor(InlineProcessor processor, MarkdownFile markd processor.EmitError(link, $"`{anchor}` does not exist in {markdown.RelativePath}."); } - private static void UpdateLinkUrl(LinkInline link, string url, ParserContext context, string? anchor) + private static void UpdateLinkUrl(LinkInline link, MarkdownFile? linkMarkdown, 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)) + var newUrl = url; + if (linkMarkdown is not null) { - // eat overall path prefix since its gets appended later - var subPrefix = context.CurrentUrlPath.Length >= urlPathPrefix.Length - ? context.CurrentUrlPath[urlPathPrefix.Length..] - : urlPathPrefix; - - // 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 lastIndexPath = subPrefix.LastIndexOf('/'); - if (lastIndexPath >= 0 && markdownPath.Name != "index.md") - subPrefix = subPrefix[..lastIndexPath]; - var combined = '/' + Path.Combine(subPrefix, url).TrimStart('/'); - url = Path.GetFullPath(combined); + // if url is null it's an anchor link + if (!string.IsNullOrEmpty(url)) + newUrl = linkMarkdown.Url; } - - if (url.EndsWith(".md")) + else { - url = url.EndsWith($"{Path.DirectorySeparatorChar}index.md") - ? url.Remove(url.LastIndexOf("index.md", StringComparison.Ordinal), "index.md".Length) - : url.Remove(url.LastIndexOf(".md", StringComparison.Ordinal), ".md".Length); + // 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 (!newUrl.StartsWith('/') && !string.IsNullOrEmpty(newUrl)) + { + // eat overall path prefix since its gets appended later + var subPrefix = context.CurrentUrlPath.Length >= urlPathPrefix.Length + ? context.CurrentUrlPath[urlPathPrefix.Length..] + : urlPathPrefix; + + // 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 (newUrl.StartsWith("../") && context.DocumentationFileLookup(context.MarkdownSourcePath) is SnippetFile snippetFile) + newUrl = 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 lastIndexPath = subPrefix.LastIndexOf('/'); + if (lastIndexPath >= 0 && markdownPath.Name != "index.md") + subPrefix = subPrefix[..lastIndexPath]; + var combined = '/' + Path.Combine(subPrefix, newUrl).TrimStart('/'); + newUrl = Path.GetFullPath(combined); + } + + // When running on Windows, path traversal results must be normalized prior to being used in a URL + // Path.GetFullPath() will result in the drive letter being appended to the path, which needs to be pruned back. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + newUrl = newUrl.Replace('\\', '/'); + if (newUrl.Length > 2 && newUrl[1] == ':') + newUrl = newUrl[2..]; + } + + if (!string.IsNullOrWhiteSpace(newUrl) && !string.IsNullOrWhiteSpace(urlPathPrefix)) + newUrl = $"{urlPathPrefix.TrimEnd('/')}{newUrl}"; + } - // When running on Windows, path traversal results must be normalized prior to being used in a URL - // Path.GetFullPath() will result in the drive letter being appended to the path, which needs to be pruned back. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (newUrl.EndsWith(".md")) { - url = url.Replace('\\', '/'); - if (url.Length > 2 && url[1] == ':') - url = url[2..]; + newUrl = newUrl.EndsWith($"{Path.DirectorySeparatorChar}index.md") + ? newUrl.Remove(newUrl.LastIndexOf("index.md", StringComparison.Ordinal), "index.md".Length) + : newUrl.Remove(url.LastIndexOf(".md", StringComparison.Ordinal), ".md".Length); } - if (!string.IsNullOrWhiteSpace(url) && !string.IsNullOrWhiteSpace(urlPathPrefix)) - url = $"{urlPathPrefix.TrimEnd('/')}{url}"; - // TODO this is hardcoded should be part of extension system - if (url.EndsWith(".toml")) - url = url[..^5]; + if (newUrl.EndsWith(".toml")) + newUrl = url[..^5]; - link.Url = string.IsNullOrEmpty(anchor) ? url : $"{url}#{anchor}"; + link.Url = string.IsNullOrEmpty(anchor) ? newUrl : $"{newUrl}#{anchor}"; } diff --git a/src/docs-assembler/AssembleContext.cs b/src/docs-assembler/AssembleContext.cs index ab3aff075..5ffe1079c 100644 --- a/src/docs-assembler/AssembleContext.cs +++ b/src/docs-assembler/AssembleContext.cs @@ -71,7 +71,7 @@ public AssembleContext( throw new Exception($"Could not find environment {environment}"); Environment = env; - var contentSource = Environment.ContentSource.ToStringFast(); + var contentSource = Environment.ContentSource.ToStringFast(true); CheckoutDirectory = ReadFileSystem.DirectoryInfo.New(checkoutDirectory ?? Path.Combine(".artifacts", "checkouts", contentSource)); OutputDirectory = ReadFileSystem.DirectoryInfo.New(output ?? Path.Combine(".artifacts", "assembly")); diff --git a/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs b/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs index 35c0216c9..84de2c886 100644 --- a/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs +++ b/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs @@ -48,7 +48,7 @@ public async Task> AcquireAllLatest(Cancel ctx = d _logger.LogInformation( "Cloning all repositories for environment {EnvironmentName} using '{ContentSourceStrategy}' content sourcing strategy", PublishEnvironment.Name, - PublishEnvironment.ContentSource.ToStringFast() + PublishEnvironment.ContentSource.ToStringFast(true) ); var dict = new ConcurrentDictionary();