Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}";

}

Expand Down
2 changes: 1 addition & 1 deletion src/docs-assembler/AssembleContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));

Expand Down
2 changes: 1 addition & 1 deletion src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public async Task<IReadOnlyCollection<Checkout>> 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<string, Stopwatch>();
Expand Down
Loading