-
Notifications
You must be signed in to change notification settings - Fork 30
Initial support for cross link resolving #491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a248321
d834cba
366c8ee
1504f6e
4f9979c
383e6e6
8b9f322
0824bc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
# Cross Links | ||
|
||
[Elasticsearch](elasticsearch://index.md) | ||
[Elasticsearch](docs-content://index.md) | ||
|
||
[Kibana][1] | ||
|
||
[1]: kibana://index.md | ||
[1]: docs-content://index.md |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// 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 | ||
|
||
using System.Collections.Frozen; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text.Json; | ||
using Elastic.Markdown.IO.Configuration; | ||
using Elastic.Markdown.IO.State; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Elastic.Markdown.CrossLinks; | ||
|
||
public interface ICrossLinkResolver | ||
{ | ||
Task FetchLinks(); | ||
bool TryResolve(Action<string> errorEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri); | ||
} | ||
|
||
public class CrossLinkResolver(ConfigurationFile configuration, ILoggerFactory logger) : ICrossLinkResolver | ||
{ | ||
private readonly string[] _links = configuration.CrossLinkRepositories; | ||
private FrozenDictionary<string, LinkReference> _linkReferences = new Dictionary<string, LinkReference>().ToFrozenDictionary(); | ||
private readonly ILogger _logger = logger.CreateLogger(nameof(CrossLinkResolver)); | ||
|
||
public static LinkReference Deserialize(string json) => | ||
JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkReference)!; | ||
|
||
public async Task FetchLinks() | ||
{ | ||
using var client = new HttpClient(); | ||
var dictionary = new Dictionary<string, LinkReference>(); | ||
foreach (var link in _links) | ||
{ | ||
var url = $"https://elastic-docs-link-index.s3.us-east-2.amazonaws.com/elastic/{link}/main/links.json"; | ||
_logger.LogInformation($"Fetching {url}"); | ||
var json = await client.GetStringAsync(url); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if the url doesn't exit yet? While we onboard repositories, not all of them might be available from the start. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now I'm only going to make All others we'll blindly transform to urls for now. (will follow up with another PR to introduce this behavior). |
||
var linkReference = Deserialize(json); | ||
dictionary.Add(link, linkReference); | ||
} | ||
_linkReferences = dictionary.ToFrozenDictionary(); | ||
} | ||
|
||
public bool TryResolve(Action<string> errorEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri) => | ||
TryResolve(errorEmitter, _linkReferences, crossLinkUri, out resolvedUri); | ||
|
||
private static Uri BaseUri { get; } = new Uri("https://docs-v3-preview.elastic.dev"); | ||
|
||
public static bool TryResolve(Action<string> errorEmitter, IDictionary<string, LinkReference> lookup, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri) | ||
{ | ||
resolvedUri = null; | ||
if (!lookup.TryGetValue(crossLinkUri.Scheme, out var linkReference)) | ||
{ | ||
errorEmitter($"'{crossLinkUri.Scheme}' is not declared as valid cross link repository in docset.yml under cross_links"); | ||
return false; | ||
} | ||
var lookupPath = crossLinkUri.AbsolutePath.TrimStart('/'); | ||
if (string.IsNullOrEmpty(lookupPath) && crossLinkUri.Host.EndsWith(".md")) | ||
lookupPath = crossLinkUri.Host; | ||
|
||
if (!linkReference.Links.TryGetValue(lookupPath, out var link)) | ||
{ | ||
errorEmitter($"'{lookupPath}' is not a valid link in the '{crossLinkUri.Scheme}' cross link repository."); | ||
return false; | ||
} | ||
|
||
//https://docs-v3-preview.elastic.dev/elastic/docs-content/tree/main/cloud-account/change-your-password | ||
var path = lookupPath.Replace(".md", ""); | ||
if (path.EndsWith("/index")) | ||
path = path.Substring(0, path.Length - 6); | ||
if (path == "index") | ||
path = string.Empty; | ||
|
||
if (!string.IsNullOrEmpty(crossLinkUri.Fragment)) | ||
{ | ||
if (link.Anchors is null) | ||
{ | ||
errorEmitter($"'{lookupPath}' does not have any anchors so linking to '{crossLinkUri.Fragment}' is impossible."); | ||
return false; | ||
} | ||
|
||
if (!link.Anchors.Contains(crossLinkUri.Fragment.TrimStart('#'))) | ||
{ | ||
errorEmitter($"'{lookupPath}' has no anchor named: '{crossLinkUri.Fragment}'."); | ||
return false; | ||
} | ||
path += crossLinkUri.Fragment; | ||
} | ||
|
||
resolvedUri = new Uri(BaseUri, $"elastic/{crossLinkUri.Scheme}/tree/main/{path}"); | ||
return true; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be
master
in some casesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aye, that's why we need a links index file that points to all current index files.