Skip to content

Commit 02f8cbb

Browse files
authored
Resolve links in link parser using MarkdownFile.Url if available (#941)
* Resolve links in link parser using MarkdownFile.Url if available * Fix anchor only edgecase * bump windows filepath fix above joining with urlPathPrefix again
1 parent 85ec3bd commit 02f8cbb

File tree

3 files changed

+58
-47
lines changed

3 files changed

+58
-47
lines changed

src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ private static void ProcessInternalLink(LinkInline link, InlineProcessor process
207207
var linkMarkdown = SetLinkData(link, processor, context, file, url);
208208

209209
ProcessLinkText(processor, link, linkMarkdown, anchor, url, file);
210-
UpdateLinkUrl(link, url, context, anchor);
210+
UpdateLinkUrl(link, linkMarkdown, url, context, anchor);
211211
}
212212

213213
private static MarkdownFile? SetLinkData(LinkInline link, InlineProcessor processor, ParserContext context,
@@ -292,61 +292,72 @@ private static void ValidateAnchor(InlineProcessor processor, MarkdownFile markd
292292
processor.EmitError(link, $"`{anchor}` does not exist in {markdown.RelativePath}.");
293293
}
294294

295-
private static void UpdateLinkUrl(LinkInline link, string url, ParserContext context, string? anchor)
295+
private static void UpdateLinkUrl(LinkInline link, MarkdownFile? linkMarkdown, string url, ParserContext context, string? anchor)
296296
{
297-
// TODO revisit when we refactor our documentation set graph
298-
// This method grew too complex, we need to revisit our documentation set graph generation so we can ask these questions
299-
// on `DocumentationFile` that are mostly precomputed
300-
var urlPathPrefix = context.Build.UrlPathPrefix ?? string.Empty;
301-
302-
if (!url.StartsWith('/') && !string.IsNullOrEmpty(url))
297+
var newUrl = url;
298+
if (linkMarkdown is not null)
303299
{
304-
// eat overall path prefix since its gets appended later
305-
var subPrefix = context.CurrentUrlPath.Length >= urlPathPrefix.Length
306-
? context.CurrentUrlPath[urlPathPrefix.Length..]
307-
: urlPathPrefix;
308-
309-
// if we are trying to resolve a relative url from a _snippet folder ensure we eat the _snippet folder
310-
// as it's not part of url by chopping of the extra parent navigation
311-
if (url.StartsWith("../") && context.DocumentationFileLookup(context.MarkdownSourcePath) is SnippetFile snippetFile)
312-
url = url.Substring(3);
313-
314-
// TODO check through context.DocumentationFileLookup if file is index vs "index.md" check
315-
var markdownPath = context.MarkdownSourcePath;
316-
// if the current path is an index e.g /reference/cloud-k8s/
317-
// './' current path lookups should be relative to sub-path.
318-
// If it's not e.g /reference/cloud-k8s/api-docs/ these links should resolve on folder up.
319-
var lastIndexPath = subPrefix.LastIndexOf('/');
320-
if (lastIndexPath >= 0 && markdownPath.Name != "index.md")
321-
subPrefix = subPrefix[..lastIndexPath];
322-
var combined = '/' + Path.Combine(subPrefix, url).TrimStart('/');
323-
url = Path.GetFullPath(combined);
300+
// if url is null it's an anchor link
301+
if (!string.IsNullOrEmpty(url))
302+
newUrl = linkMarkdown.Url;
324303
}
325-
326-
if (url.EndsWith(".md"))
304+
else
327305
{
328-
url = url.EndsWith($"{Path.DirectorySeparatorChar}index.md")
329-
? url.Remove(url.LastIndexOf("index.md", StringComparison.Ordinal), "index.md".Length)
330-
: url.Remove(url.LastIndexOf(".md", StringComparison.Ordinal), ".md".Length);
306+
// TODO revisit when we refactor our documentation set graph
307+
// This method grew too complex, we need to revisit our documentation set graph generation so we can ask these questions
308+
// on `DocumentationFile` that are mostly precomputed
309+
var urlPathPrefix = context.Build.UrlPathPrefix ?? string.Empty;
310+
311+
if (!newUrl.StartsWith('/') && !string.IsNullOrEmpty(newUrl))
312+
{
313+
// eat overall path prefix since its gets appended later
314+
var subPrefix = context.CurrentUrlPath.Length >= urlPathPrefix.Length
315+
? context.CurrentUrlPath[urlPathPrefix.Length..]
316+
: urlPathPrefix;
317+
318+
// if we are trying to resolve a relative url from a _snippet folder ensure we eat the _snippet folder
319+
// as it's not part of url by chopping of the extra parent navigation
320+
if (newUrl.StartsWith("../") && context.DocumentationFileLookup(context.MarkdownSourcePath) is SnippetFile snippetFile)
321+
newUrl = url.Substring(3);
322+
323+
// TODO check through context.DocumentationFileLookup if file is index vs "index.md" check
324+
var markdownPath = context.MarkdownSourcePath;
325+
// if the current path is an index e.g /reference/cloud-k8s/
326+
// './' current path lookups should be relative to sub-path.
327+
// If it's not e.g /reference/cloud-k8s/api-docs/ these links should resolve on folder up.
328+
var lastIndexPath = subPrefix.LastIndexOf('/');
329+
if (lastIndexPath >= 0 && markdownPath.Name != "index.md")
330+
subPrefix = subPrefix[..lastIndexPath];
331+
var combined = '/' + Path.Combine(subPrefix, newUrl).TrimStart('/');
332+
newUrl = Path.GetFullPath(combined);
333+
}
334+
335+
// When running on Windows, path traversal results must be normalized prior to being used in a URL
336+
// Path.GetFullPath() will result in the drive letter being appended to the path, which needs to be pruned back.
337+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
338+
{
339+
newUrl = newUrl.Replace('\\', '/');
340+
if (newUrl.Length > 2 && newUrl[1] == ':')
341+
newUrl = newUrl[2..];
342+
}
343+
344+
if (!string.IsNullOrWhiteSpace(newUrl) && !string.IsNullOrWhiteSpace(urlPathPrefix))
345+
newUrl = $"{urlPathPrefix.TrimEnd('/')}{newUrl}";
346+
331347
}
332348

333-
// When running on Windows, path traversal results must be normalized prior to being used in a URL
334-
// Path.GetFullPath() will result in the drive letter being appended to the path, which needs to be pruned back.
335-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
349+
if (newUrl.EndsWith(".md"))
336350
{
337-
url = url.Replace('\\', '/');
338-
if (url.Length > 2 && url[1] == ':')
339-
url = url[2..];
351+
newUrl = newUrl.EndsWith($"{Path.DirectorySeparatorChar}index.md")
352+
? newUrl.Remove(newUrl.LastIndexOf("index.md", StringComparison.Ordinal), "index.md".Length)
353+
: newUrl.Remove(url.LastIndexOf(".md", StringComparison.Ordinal), ".md".Length);
340354
}
341355

342-
if (!string.IsNullOrWhiteSpace(url) && !string.IsNullOrWhiteSpace(urlPathPrefix))
343-
url = $"{urlPathPrefix.TrimEnd('/')}{url}";
344-
345356
// TODO this is hardcoded should be part of extension system
346-
if (url.EndsWith(".toml"))
347-
url = url[..^5];
357+
if (newUrl.EndsWith(".toml"))
358+
newUrl = url[..^5];
348359

349-
link.Url = string.IsNullOrEmpty(anchor) ? url : $"{url}#{anchor}";
360+
link.Url = string.IsNullOrEmpty(anchor) ? newUrl : $"{newUrl}#{anchor}";
350361

351362
}
352363

src/docs-assembler/AssembleContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public AssembleContext(
7171
throw new Exception($"Could not find environment {environment}");
7272
Environment = env;
7373

74-
var contentSource = Environment.ContentSource.ToStringFast();
74+
var contentSource = Environment.ContentSource.ToStringFast(true);
7575
CheckoutDirectory = ReadFileSystem.DirectoryInfo.New(checkoutDirectory ?? Path.Combine(".artifacts", "checkouts", contentSource));
7676
OutputDirectory = ReadFileSystem.DirectoryInfo.New(output ?? Path.Combine(".artifacts", "assembly"));
7777

src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public async Task<IReadOnlyCollection<Checkout>> AcquireAllLatest(Cancel ctx = d
4848
_logger.LogInformation(
4949
"Cloning all repositories for environment {EnvironmentName} using '{ContentSourceStrategy}' content sourcing strategy",
5050
PublishEnvironment.Name,
51-
PublishEnvironment.ContentSource.ToStringFast()
51+
PublishEnvironment.ContentSource.ToStringFast(true)
5252
);
5353

5454
var dict = new ConcurrentDictionary<string, Stopwatch>();

0 commit comments

Comments
 (0)