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
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ resharper_csharp_braces_for_foreach=required_for_multiline
resharper_csharp_braces_for_for=required_for_multiline
resharper_csharp_braces_for_fixed=required_for_multiline
resharper_csharp_braces_for_ifelse=required_for_multiline
resharper_csharp_keep_existing_attribute_arrangement=true

resharper_csharp_accessor_owner_body=expression_body

Expand Down Expand Up @@ -233,6 +234,11 @@ dotnet_diagnostic.CA1859.severity = none

dotnet_diagnostic.IDE0305.severity = none

# https://github.com/dotnet/roslyn/issues/60784
# CS8509 already warns
dotnet_diagnostic.IDE0072.severity = none



[DocumentationWebHost.cs]
dotnet_diagnostic.IL3050.severity = none
Expand Down
42 changes: 16 additions & 26 deletions src/Elastic.Markdown/CrossLinks/CrossLinkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ public static string Serialize(LinkIndex index) =>

public record LinkIndexEntry
{
[JsonPropertyName("repository")] public required string Repository { get; init; }
[JsonPropertyName("repository")]
public required string Repository { get; init; }

[JsonPropertyName("path")] public required string Path { get; init; }
[JsonPropertyName("path")]
public required string Path { get; init; }

[JsonPropertyName("branch")] public required string Branch { get; init; }
[JsonPropertyName("branch")]
public required string Branch { get; init; }

[JsonPropertyName("etag")] public required string ETag { get; init; }
[JsonPropertyName("etag")]
public required string ETag { get; init; }
}

public interface ICrossLinkResolver
Expand All @@ -38,9 +42,10 @@ public interface ICrossLinkResolver
bool TryResolve(Action<string> errorEmitter, Action<string> warningEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri);
}

public class CrossLinkResolver(CrossLinkFetcher fetcher) : ICrossLinkResolver
public class CrossLinkResolver(CrossLinkFetcher fetcher, IUriEnvironmentResolver? uriResolver = null) : ICrossLinkResolver
{
private FetchedCrossLinks _crossLinks = FetchedCrossLinks.Empty;
private readonly IUriEnvironmentResolver _uriResolver = uriResolver ?? new PreviewEnvironmentUriResolver();

public async Task<FetchedCrossLinks> FetchLinks()
{
Expand All @@ -49,9 +54,7 @@ public async Task<FetchedCrossLinks> FetchLinks()
}

public bool TryResolve(Action<string> errorEmitter, Action<string> warningEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri) =>
TryResolve(errorEmitter, warningEmitter, _crossLinks, crossLinkUri, out resolvedUri);

private static Uri BaseUri { get; } = new("https://docs-v3-preview.elastic.dev");
TryResolve(errorEmitter, warningEmitter, _crossLinks, _uriResolver, crossLinkUri, out resolvedUri);

public FetchedCrossLinks UpdateLinkReference(string repository, LinkReference linkReference)
{
Expand All @@ -68,14 +71,15 @@ public static bool TryResolve(
Action<string> errorEmitter,
Action<string> warningEmitter,
FetchedCrossLinks fetchedCrossLinks,
IUriEnvironmentResolver uriResolver,
Uri crossLinkUri,
[NotNullWhen(true)] out Uri? resolvedUri
)
{
resolvedUri = null;
var lookup = fetchedCrossLinks.LinkReferences;
if (crossLinkUri.Scheme != "asciidocalypse" && lookup.TryGetValue(crossLinkUri.Scheme, out var linkReference))
return TryFullyValidate(errorEmitter, linkReference, crossLinkUri, out resolvedUri);
return TryFullyValidate(errorEmitter, uriResolver, linkReference, crossLinkUri, out resolvedUri);

// TODO this is temporary while we wait for all links.json to be published
// Here we just silently rewrite the cross_link to the url
Expand All @@ -95,13 +99,13 @@ public static bool TryResolve(
if (!string.IsNullOrEmpty(crossLinkUri.Fragment))
path += crossLinkUri.Fragment;

var branch = GetBranch(crossLinkUri);
resolvedUri = new Uri(BaseUri, $"elastic/{crossLinkUri.Scheme}/tree/{branch}/{path}");
resolvedUri = uriResolver.Resolve(crossLinkUri, path);
return true;
}

private static bool TryFullyValidate(
Action<string> errorEmitter,
IUriEnvironmentResolver uriResolver,
LinkReference linkReference,
Uri crossLinkUri,
[NotNullWhen(true)] out Uri? resolvedUri
Expand Down Expand Up @@ -134,8 +138,7 @@ private static bool TryFullyValidate(
path += "#" + lookupFragment.TrimStart('#');
}

var branch = GetBranch(crossLinkUri);
resolvedUri = new Uri(BaseUri, $"elastic/{crossLinkUri.Scheme}/tree/{branch}/{path}");
resolvedUri = uriResolver.Resolve(crossLinkUri, path);
return true;
}

Expand Down Expand Up @@ -224,19 +227,6 @@ private static bool ResolveLinkRedirect(
return false;
}

/// Hardcoding these for now, we'll have an index.json pointing to all links.json files
/// at some point from which we can query the branch soon.
private static string GetBranch(Uri crossLinkUri)
{
var branch = crossLinkUri.Scheme switch
{
"docs-content" => "main",
_ => "main"
};
return branch;
}


private static string ToTargetUrlPath(string lookupPath)
{
//https://docs-v3-preview.elastic.dev/elastic/docs-content/tree/main/cloud-account/change-your-password
Expand Down
35 changes: 35 additions & 0 deletions src/Elastic.Markdown/CrossLinks/IUriEnvironmentResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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

namespace Elastic.Markdown.CrossLinks;

public interface IUriEnvironmentResolver
{
Uri Resolve(Uri crossLinkUri, string path);
}

public class PreviewEnvironmentUriResolver : IUriEnvironmentResolver
{
private static Uri BaseUri { get; } = new("https://docs-v3-preview.elastic.dev");

public Uri Resolve(Uri crossLinkUri, string path)
{
var branch = GetBranch(crossLinkUri);
return new Uri(BaseUri, $"elastic/{crossLinkUri.Scheme}/tree/{branch}/{path}");
}

/// Hardcoding these for now, we'll have an index.json pointing to all links.json files
/// at some point from which we can query the branch soon.
private static string GetBranch(Uri crossLinkUri)
{
var branch = crossLinkUri.Scheme switch
{
"docs-content" => "main",
_ => "main"
};
return branch;
}


}
6 changes: 4 additions & 2 deletions src/docs-assembler/Building/AssemblerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ public class AssemblerBuilder(ILoggerFactory logger, AssembleContext context)
{
private readonly ILogger<AssemblerBuilder> _logger = logger.CreateLogger<AssemblerBuilder>();

public async Task BuildAllAsync(IReadOnlyCollection<Checkout> checkouts, Cancel ctx)
public async Task BuildAllAsync(IReadOnlyCollection<Checkout> checkouts, string environment, Cancel ctx)
{
var crossLinkResolver = new CrossLinkResolver(new AssemblerCrossLinkFetcher(logger, context.Configuration));
var crossLinkFetcher = new AssemblerCrossLinkFetcher(logger, context.Configuration);
var uriResolver = new PublishEnvironmentUriResolver(context.Configuration, environment);
var crossLinkResolver = new CrossLinkResolver(crossLinkFetcher, uriResolver);

foreach (var checkout in checkouts)
{
Expand Down
57 changes: 57 additions & 0 deletions src/docs-assembler/Building/PublishEnvironmentUriResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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 Documentation.Assembler.Configuration;
using Elastic.Markdown.CrossLinks;

namespace Documentation.Assembler.Building;

public class PublishEnvironmentUriResolver : IUriEnvironmentResolver
{
private Uri BaseUri { get; }

private PublishEnvironment PublishEnvironment { get; }

private PreviewEnvironmentUriResolver PreviewResolver { get; }

private FrozenDictionary<string, Repository> AllRepositories { get; }

public PublishEnvironmentUriResolver(AssemblyConfiguration configuration, string environment)
{
if (!configuration.Environments.TryGetValue(environment, out var e))
throw new Exception($"Could not find environment {environment}");
if (!Uri.TryCreate(e.Uri, UriKind.Absolute, out var uri))
throw new Exception($"Could not parse uri {e.Uri} in environment {environment}");

BaseUri = uri;
PublishEnvironment = e;
PreviewResolver = new PreviewEnvironmentUriResolver();
AllRepositories = configuration.ReferenceRepositories.Values.Concat<Repository>([configuration.Narrative])
.ToFrozenDictionary(e => e.Name, e => e);
RepositoryLookup = AllRepositories.GetAlternateLookup<ReadOnlySpan<char>>();
}

private FrozenDictionary<string, Repository>.AlternateLookup<ReadOnlySpan<char>> RepositoryLookup { get; }

public Uri Resolve(Uri crossLinkUri, string path)
{
if (PublishEnvironment.Name == "preview")
return PreviewResolver.Resolve(crossLinkUri, path);

var repositoryPath = crossLinkUri.Scheme;
if (RepositoryLookup.TryGetValue(crossLinkUri.Scheme, out var repository))
repositoryPath = repository.PathPrefix;

var fullPath = (PublishEnvironment.PathPrefix, repositoryPath) switch
{
(null or "", null or "") => path,
(null or "", var p) => $"{p}/{path}",
(var p, null or "") => $"{p}/{path}",
var (p, pp) => $"{p}/{pp}/{path}"
};

return new Uri(BaseUri, fullPath);
}
}
6 changes: 5 additions & 1 deletion src/docs-assembler/Cli/RepositoryCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,19 @@ public async Task<int> CloneAll(bool? strict = null, Cancel ctx = default)
/// <param name="force"> Force a full rebuild of the destination folder</param>
/// <param name="strict"> Treat warnings as errors and fail the build on warnings</param>
/// <param name="allowIndexing"> Allow indexing and following of html files</param>
/// <param name="environment"> The environment to resolve links to</param>
/// <param name="ctx"></param>
[Command("build-all")]
public async Task<int> BuildAll(
bool? force = null,
bool? strict = null,
bool? allowIndexing = null,
string? environment = null,
Cancel ctx = default)
{
AssignOutputLogger();
var githubEnvironmentInput = githubActionsService.GetInput("environment");
environment ??= !string.IsNullOrEmpty(githubEnvironmentInput) ? githubEnvironmentInput : "production";

await using var collector = new ConsoleDiagnosticsCollector(logger, githubActionsService);
_ = collector.StartAsync(ctx);
Expand All @@ -72,7 +76,7 @@ public async Task<int> BuildAll(
throw new Exception("No checkouts found");

var builder = new AssemblerBuilder(logger, assembleContext);
await builder.BuildAllAsync(checkouts, ctx);
await builder.BuildAllAsync(checkouts, environment, ctx);

if (strict ?? false)
return collector.Errors + collector.Warnings;
Expand Down
18 changes: 18 additions & 0 deletions src/docs-assembler/Configuration/AssemblyConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Documentation.Assembler.Configuration;
[YamlSerializable(typeof(AssemblyConfiguration))]
[YamlSerializable(typeof(Repository))]
[YamlSerializable(typeof(NarrativeRepository))]
[YamlSerializable(typeof(PublishEnvironment))]
public partial class YamlStaticContext;

public record AssemblyConfiguration
Expand All @@ -30,6 +31,8 @@ public static AssemblyConfiguration Deserialize(string yaml)
var repository = RepositoryDefaults(r, name);
config.ReferenceRepositories[name] = repository;
}
foreach (var (name, env) in config.Environments)
env.Name = name;
config.Narrative = RepositoryDefaults(config.Narrative, NarrativeRepository.RepositoryName);
return config;
}
Expand Down Expand Up @@ -71,4 +74,19 @@ private static TRepository RepositoryDefaults<TRepository>(TRepository r, string

[YamlMember(Alias = "references")]
public Dictionary<string, Repository> ReferenceRepositories { get; set; } = [];

[YamlMember(Alias = "environments")]
public Dictionary<string, PublishEnvironment> Environments { get; set; } = [];
}

public record PublishEnvironment
{
[YamlIgnore]
public string Name { get; set; } = string.Empty;

[YamlMember(Alias = "uri")]
public string Uri { get; set; } = string.Empty;

[YamlMember(Alias = "path_prefix")]
public string? PathPrefix { get; set; } = string.Empty;
}
10 changes: 10 additions & 0 deletions src/docs-assembler/assembler.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
environments:
production:
uri: https://elastic.co
path_prefix: docs
staging:
uri: https://staging-website.elastic.co
path_prefix: docs
preview:
uri: https://docs-v3-preview.elastic.dev
path_prefix:
narrative:
checkout_strategy: full
references:
Expand Down
3 changes: 2 additions & 1 deletion tests/Elastic.Markdown.Tests/TestCrossLinkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Elastic.Markdown.Tests;

public class TestCrossLinkResolver : ICrossLinkResolver
{
private readonly IUriEnvironmentResolver _uriResolver = new PreviewEnvironmentUriResolver();
private FetchedCrossLinks _crossLinks = FetchedCrossLinks.Empty;
private Dictionary<string, LinkReference> LinkReferences { get; } = [];
private HashSet<string> DeclaredRepositories { get; } = [];
Expand Down Expand Up @@ -56,5 +57,5 @@ public Task<FetchedCrossLinks> FetchLinks()
}

public bool TryResolve(Action<string> errorEmitter, Action<string> warningEmitter, Uri crossLinkUri, [NotNullWhen(true)] out Uri? resolvedUri) =>
CrossLinkResolver.TryResolve(errorEmitter, warningEmitter, _crossLinks, crossLinkUri, out resolvedUri);
CrossLinkResolver.TryResolve(errorEmitter, warningEmitter, _crossLinks, _uriResolver, crossLinkUri, out resolvedUri);
}
3 changes: 2 additions & 1 deletion tests/authoring/Framework/TestCrossLinkResolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type TestCrossLinkResolver (config: ConfigurationFile) =

let references = Dictionary<string, LinkReference>()
let declared = HashSet<string>()
let uriResolver = PreviewEnvironmentUriResolver()

member this.LinkReferences = references
member this.DeclaredRepositories = declared
Expand Down Expand Up @@ -77,6 +78,6 @@ type TestCrossLinkResolver (config: ConfigurationFile) =
LinkReferences=this.LinkReferences.ToFrozenDictionary(),
FromConfiguration=true
)
CrossLinkResolver.TryResolve(errorEmitter, warningEmitter, crossLinks, crossLinkUri, &resolvedUri);
CrossLinkResolver.TryResolve(errorEmitter, warningEmitter, crossLinks, uriResolver, crossLinkUri, &resolvedUri);