From 8eb5753777bf5d712079aa2662349ad14e843e80 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 29 Apr 2025 13:57:20 +0200 Subject: [PATCH] Refactor domain out of Elastic.Markdown part II --- .../Diagnostics/DiagnosticsChannel.cs | 114 ---------------- .../Diagnostics/DiagnosticsCollector.cs | 127 ++++++++++++++++++ .../Elastic.Documentation.csproj | 4 +- .../IDocumentationContext.cs | 28 ++++ .../Legacy/ILegacyUrlMapper.cs | 6 +- .../{ => Links}/LinkReference.cs | 2 +- ...{LinkIndex.cs => LinkReferenceRegistry.cs} | 20 +-- .../Serialization/SourceGenerationContext.cs | 4 +- docs-builder.sln | 7 + .../Assembler/AssemblyConfiguration.cs | 1 + .../Assembler/ContentSource.cs | 0 .../Assembler/GoogleTagManager.cs | 0 .../GoogleTagManagerConfiguration.cs | 0 .../Assembler/PublishEnvironment.cs | 0 .../Assembler/Repository.cs | 0 .../Builder}/ConfigurationFile.cs | 30 +---- .../Builder/EnabledExtensions.cs | 0 .../Builder/FeatureFlags.cs | 0 .../Builder}/RedirectFile.cs | 10 +- .../Builder}/TableOfContentsConfiguration.cs | 59 ++++---- ...Elastic.Documentation.Configuration.csproj | 20 +++ .../Plugins}/DetectionRules/DetectionRule.cs | 4 +- .../DetectionRulesReference.cs | 75 +++++++++++ .../TableOfContents/RuleReference.cs} | 16 +-- .../Serialization/YamlStaticContext.cs | 2 +- .../TableOfContents/ITocItem.cs | 11 +- .../YamlStreamReader.cs | 3 +- src/Elastic.Markdown/BuildContext.cs | 4 +- .../ProcessorDiagnosticExtensions.cs | 45 +------ .../DocumentationGenerator.cs | 14 +- src/Elastic.Markdown/Elastic.Markdown.csproj | 2 +- .../DetectionRules/DetectionRuleFile.cs | 6 +- .../DetectionRulesDocsBuilderExtension.cs | 76 +---------- .../Extensions/IDocsBuilderExtension.cs | 27 +--- src/Elastic.Markdown/IO/DocumentationFile.cs | 13 +- src/Elastic.Markdown/IO/DocumentationSet.cs | 81 ++++++----- src/Elastic.Markdown/IO/MarkdownFile.cs | 3 +- .../IO/Navigation/DocumentationGroup.cs | 29 ++-- .../ConfigurationCrossLinkFetcher.cs | 4 +- .../Links/CrossLinks/CrossLinkFetcher.cs | 30 ++--- .../Links/CrossLinks/CrossLinkResolver.cs | 1 + .../InboundLinks/LinkIndexCrossLinkFetcher.cs | 2 +- .../InboundLinks/LinkIndexLinkChecker.cs | 1 + src/Elastic.Markdown/Myst/ParserContext.cs | 2 +- src/Elastic.Markdown/Slices/HtmlWriter.cs | 30 ++--- src/Elastic.Markdown/Slices/_ViewModels.cs | 2 +- src/docs-assembler/AssembleContext.cs | 4 +- src/docs-assembler/AssembleSources.cs | 3 +- .../Building/AssemblerBuilder.cs | 9 +- .../Building/AssemblerCrossLinkFetcher.cs | 2 +- src/docs-assembler/Cli/RepositoryCommands.cs | 4 +- .../PageLegacyUrlMapper.cs} | 8 +- .../Links/NavigationPrefixChecker.cs | 1 + .../Navigation/GlobalNavigation.cs | 5 +- .../Navigation/GlobalNavigationFile.cs | 6 +- .../GlobalNavigationPathProvider.cs | 4 +- src/docs-assembler/docs-assembler.csproj | 2 +- ...orymapping.yml => legacy-url-mappings.yml} | 0 src/docs-builder/Http/DocumentationWebHost.cs | 2 +- .../LinkIndexProvider.cs | 28 ++-- .../LinkReferenceProvider.cs | 1 + .../docs-lambda-index-publisher/Program.cs | 4 +- .../DocSet/LinkReferenceTests.cs | 1 + .../DocSet/NavigationTestsBase.cs | 2 +- .../TestCrossLinkResolver.cs | 2 +- .../Framework/TestCrossLinkResolver.fs | 7 +- 66 files changed, 499 insertions(+), 511 deletions(-) create mode 100644 Elastic.Documentation/Diagnostics/DiagnosticsCollector.cs create mode 100644 Elastic.Documentation/IDocumentationContext.cs rename src/Elastic.Markdown/IO/HistoryMapping/HistoryMapper.cs => Elastic.Documentation/Legacy/ILegacyUrlMapper.cs (77%) rename Elastic.Documentation/{ => Links}/LinkReference.cs (98%) rename Elastic.Documentation/Links/{LinkIndex.cs => LinkReferenceRegistry.cs} (64%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Assembler/AssemblyConfiguration.cs (96%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Assembler/ContentSource.cs (100%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Assembler/GoogleTagManager.cs (100%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Assembler/GoogleTagManagerConfiguration.cs (100%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Assembler/PublishEnvironment.cs (100%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Assembler/Repository.cs (100%) rename src/{Elastic.Markdown/IO/Configuration => Elastic.Documentation.Configuration/Builder}/ConfigurationFile.cs (83%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Builder/EnabledExtensions.cs (100%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/Builder/FeatureFlags.cs (100%) rename src/{Elastic.Markdown/IO/Configuration => Elastic.Documentation.Configuration/Builder}/RedirectFile.cs (94%) rename src/{Elastic.Markdown/IO/Configuration => Elastic.Documentation.Configuration/Builder}/TableOfContentsConfiguration.cs (84%) create mode 100644 src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj rename src/{Elastic.Markdown/Extensions => Elastic.Documentation.Configuration/Plugins}/DetectionRules/DetectionRule.cs (98%) create mode 100644 src/Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/DetectionRulesReference.cs rename src/{Elastic.Markdown/Extensions/DetectionRules/DetectionRulesReference.cs => Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/RuleReference.cs} (52%) rename {Elastic.Documentation => src/Elastic.Documentation.Configuration}/Serialization/YamlStaticContext.cs (91%) rename {Elastic.Documentation/Configuration => src/Elastic.Documentation.Configuration}/TableOfContents/ITocItem.cs (71%) rename src/{Elastic.Markdown/IO/Configuration => Elastic.Documentation.Configuration}/YamlStreamReader.cs (98%) rename src/docs-assembler/{Mapping/PageHistoryMapper.cs => Legacy/PageLegacyUrlMapper.cs} (79%) rename src/docs-assembler/{historymapping.yml => legacy-url-mappings.yml} (100%) diff --git a/Elastic.Documentation/Diagnostics/DiagnosticsChannel.cs b/Elastic.Documentation/Diagnostics/DiagnosticsChannel.cs index a6e418c54..d26bb57ec 100644 --- a/Elastic.Documentation/Diagnostics/DiagnosticsChannel.cs +++ b/Elastic.Documentation/Diagnostics/DiagnosticsChannel.cs @@ -2,9 +2,7 @@ // 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.Concurrent; using System.Threading.Channels; -using Microsoft.Extensions.Hosting; namespace Elastic.Documentation.Diagnostics; @@ -51,115 +49,3 @@ public interface IDiagnosticsOutput { void Write(Diagnostic diagnostic); } - -public class DiagnosticsCollector(IReadOnlyCollection outputs) : IHostedService, IAsyncDisposable -{ - public DiagnosticsChannel Channel { get; } = new(); - - private int _errors; - private int _warnings; - private int _hints; - public int Warnings => _warnings; - public int Errors => _errors; - public int Hints => _hints; - - private Task? _started; - - public HashSet OffendingFiles { get; } = []; - - public ConcurrentDictionary InUseSubstitutionKeys { get; } = []; - - public ConcurrentBag CrossLinks { get; } = []; - - public bool NoHints { get; init; } - - public Task StartAsync(Cancel cancellationToken) - { - if (_started is not null) - return _started; - _started = Task.Run(async () => - { - _ = await Channel.WaitToWrite(cancellationToken); - while (!Channel.CancellationToken.IsCancellationRequested) - { - try - { - while (await Channel.Reader.WaitToReadAsync(Channel.CancellationToken)) - Drain(); - } - catch - { - //ignore - } - } - - Drain(); - }, cancellationToken); - return _started; - - void Drain() - { - while (Channel.Reader.TryRead(out var item)) - { - if (item.Severity == Severity.Hint && NoHints) - continue; - IncrementSeverityCount(item); - HandleItem(item); - _ = OffendingFiles.Add(item.File); - foreach (var output in outputs) - output.Write(item); - } - } - } - - private void IncrementSeverityCount(Diagnostic item) - { - if (item.Severity == Severity.Error) - _ = Interlocked.Increment(ref _errors); - else if (item.Severity == Severity.Warning) - _ = Interlocked.Increment(ref _warnings); - else if (item.Severity == Severity.Hint && !NoHints) - _ = Interlocked.Increment(ref _hints); - } - - protected virtual void HandleItem(Diagnostic diagnostic) { } - - public virtual async Task StopAsync(Cancel cancellationToken) - { - if (_started is not null) - await _started; - await Channel.Reader.Completion; - } - - public void EmitCrossLink(string link) => CrossLinks.Add(link); - - private void Emit(Severity severity, string file, string message) => - Channel.Write(new Diagnostic - { - Severity = severity, - File = file, - Message = message - }); - - public void EmitError(string file, string message, Exception? e = null) - { - message = message - + (e != null ? Environment.NewLine + e : string.Empty) - + (e?.InnerException != null ? Environment.NewLine + e.InnerException : string.Empty); - Emit(Severity.Error, file, message); - } - - public void EmitWarning(string file, string message) => Emit(Severity.Warning, file, message); - - public void EmitHint(string file, string message) => Emit(Severity.Hint, file, message); - - public async ValueTask DisposeAsync() - { - Channel.TryComplete(); - await StopAsync(CancellationToken.None); - GC.SuppressFinalize(this); - } - - public void CollectUsedSubstitutionKey(ReadOnlySpan key) => - _ = InUseSubstitutionKeys.TryAdd(key.ToString(), true); -} diff --git a/Elastic.Documentation/Diagnostics/DiagnosticsCollector.cs b/Elastic.Documentation/Diagnostics/DiagnosticsCollector.cs new file mode 100644 index 000000000..84144ba46 --- /dev/null +++ b/Elastic.Documentation/Diagnostics/DiagnosticsCollector.cs @@ -0,0 +1,127 @@ +// 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.Concurrent; +using System.IO.Abstractions; +using Microsoft.Extensions.Hosting; + +namespace Elastic.Documentation.Diagnostics; + +public class DiagnosticsCollector(IReadOnlyCollection outputs) : IHostedService, IAsyncDisposable +{ + public DiagnosticsChannel Channel { get; } = new(); + + private int _errors; + private int _warnings; + private int _hints; + public int Warnings => _warnings; + public int Errors => _errors; + public int Hints => _hints; + + private Task? _started; + + public HashSet OffendingFiles { get; } = []; + + public ConcurrentDictionary InUseSubstitutionKeys { get; } = []; + + public ConcurrentBag CrossLinks { get; } = []; + + public bool NoHints { get; init; } + + public Task StartAsync(Cancel cancellationToken) + { + if (_started is not null) + return _started; + _started = Task.Run(async () => + { + _ = await Channel.WaitToWrite(cancellationToken); + while (!Channel.CancellationToken.IsCancellationRequested) + { + try + { + while (await Channel.Reader.WaitToReadAsync(Channel.CancellationToken)) + Drain(); + } + catch + { + //ignore + } + } + + Drain(); + }, cancellationToken); + return _started; + + void Drain() + { + while (Channel.Reader.TryRead(out var item)) + { + if (item.Severity == Severity.Hint && NoHints) + continue; + IncrementSeverityCount(item); + HandleItem(item); + _ = OffendingFiles.Add(item.File); + foreach (var output in outputs) + output.Write(item); + } + } + } + + private void IncrementSeverityCount(Diagnostic item) + { + if (item.Severity == Severity.Error) + _ = Interlocked.Increment(ref _errors); + else if (item.Severity == Severity.Warning) + _ = Interlocked.Increment(ref _warnings); + else if (item.Severity == Severity.Hint && !NoHints) + _ = Interlocked.Increment(ref _hints); + } + + protected virtual void HandleItem(Diagnostic diagnostic) { } + + public virtual async Task StopAsync(Cancel cancellationToken) + { + if (_started is not null) + await _started; + await Channel.Reader.Completion; + } + + public void EmitCrossLink(string link) => CrossLinks.Add(link); + + private void Emit(Severity severity, string file, string message) => + Channel.Write(new Diagnostic + { + Severity = severity, + File = file, + Message = message + }); + + public void EmitError(string file, string message, Exception? e = null) + { + message = message + + (e != null ? Environment.NewLine + e : string.Empty) + + (e?.InnerException != null ? Environment.NewLine + e.InnerException : string.Empty); + Emit(Severity.Error, file, message); + } + + public void EmitWarning(string file, string message) => Emit(Severity.Warning, file, message); + + public void EmitHint(string file, string message) => Emit(Severity.Hint, file, message); + + public void EmitError(IFileInfo file, string message, Exception? e = null) => EmitError(file.FullName, message, e); + + public void EmitWarning(IFileInfo file, string message) => Emit(Severity.Warning, file.FullName, message); + + public void EmitHint(IFileInfo file, string message) => Emit(Severity.Hint, file.FullName, message); + + public async ValueTask DisposeAsync() + { + Channel.TryComplete(); + await StopAsync(CancellationToken.None); + GC.SuppressFinalize(this); + } + + public void CollectUsedSubstitutionKey(ReadOnlySpan key) => + _ = InUseSubstitutionKeys.TryAdd(key.ToString(), true); +} diff --git a/Elastic.Documentation/Elastic.Documentation.csproj b/Elastic.Documentation/Elastic.Documentation.csproj index d3495f776..02ac94d9b 100644 --- a/Elastic.Documentation/Elastic.Documentation.csproj +++ b/Elastic.Documentation/Elastic.Documentation.csproj @@ -8,12 +8,12 @@ + - - + diff --git a/Elastic.Documentation/IDocumentationContext.cs b/Elastic.Documentation/IDocumentationContext.cs new file mode 100644 index 000000000..c23aa0d8d --- /dev/null +++ b/Elastic.Documentation/IDocumentationContext.cs @@ -0,0 +1,28 @@ +// 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.IO.Abstractions; +using Elastic.Documentation.Diagnostics; + +namespace Elastic.Documentation; + +public interface IDocumentationContext +{ + DiagnosticsCollector Collector { get; } + IDirectoryInfo DocumentationSourceDirectory { get; } + GitCheckoutInformation Git { get; } + IFileSystem ReadFileSystem { get; } + IFileSystem WriteFileSystem { get; } + IFileInfo ConfigurationPath { get; } +} + +public static class DocumentationContextExtensions +{ + public static void EmitError(this IDocumentationContext context, IFileInfo file, string message, Exception? e = null) => + context.Collector.EmitError(file, message, e); + + public static void EmitWarning(this IDocumentationContext context, IFileInfo file, string message) => + context.Collector.EmitWarning(file, message); + +} diff --git a/src/Elastic.Markdown/IO/HistoryMapping/HistoryMapper.cs b/Elastic.Documentation/Legacy/ILegacyUrlMapper.cs similarity index 77% rename from src/Elastic.Markdown/IO/HistoryMapping/HistoryMapper.cs rename to Elastic.Documentation/Legacy/ILegacyUrlMapper.cs index af2e26b0b..f108b25f4 100644 --- a/src/Elastic.Markdown/IO/HistoryMapping/HistoryMapper.cs +++ b/Elastic.Documentation/Legacy/ILegacyUrlMapper.cs @@ -2,16 +2,16 @@ // 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.IO.HistoryMapping; +namespace Elastic.Documentation.Legacy; public record LegacyPageMapping(string Url, string Version); -public interface IHistoryMapper +public interface ILegacyUrlMapper { LegacyPageMapping? MapLegacyUrl(IReadOnlyCollection? mappedPages); } -public record BypassHistoryMapper : IHistoryMapper +public record NoopLegacyUrlMapper : ILegacyUrlMapper { public LegacyPageMapping? MapLegacyUrl(IReadOnlyCollection? mappedPages) => null; } diff --git a/Elastic.Documentation/LinkReference.cs b/Elastic.Documentation/Links/LinkReference.cs similarity index 98% rename from Elastic.Documentation/LinkReference.cs rename to Elastic.Documentation/Links/LinkReference.cs index 5e2d5a3c7..3008b9bbe 100644 --- a/Elastic.Documentation/LinkReference.cs +++ b/Elastic.Documentation/Links/LinkReference.cs @@ -6,7 +6,7 @@ using System.Text.Json.Serialization; using Elastic.Documentation.Serialization; -namespace Elastic.Documentation; +namespace Elastic.Documentation.Links; public record LinkMetadata { diff --git a/Elastic.Documentation/Links/LinkIndex.cs b/Elastic.Documentation/Links/LinkReferenceRegistry.cs similarity index 64% rename from Elastic.Documentation/Links/LinkIndex.cs rename to Elastic.Documentation/Links/LinkReferenceRegistry.cs index d32559c42..9a9237c0d 100644 --- a/Elastic.Documentation/Links/LinkIndex.cs +++ b/Elastic.Documentation/Links/LinkReferenceRegistry.cs @@ -8,21 +8,23 @@ namespace Elastic.Documentation.Links; -public record LinkIndex +public record LinkReferenceRegistry { - [JsonPropertyName("repositories")] public required Dictionary> Repositories { get; init; } + /// Map of branch to + [JsonPropertyName("repositories")] + public required Dictionary> Repositories { get; init; } - public static LinkIndex Deserialize(Stream json) => - JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkIndex)!; + public static LinkReferenceRegistry Deserialize(Stream json) => + JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkReferenceRegistry)!; - public static LinkIndex Deserialize(string json) => - JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkIndex)!; + public static LinkReferenceRegistry Deserialize(string json) => + JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkReferenceRegistry)!; - public static string Serialize(LinkIndex index) => - JsonSerializer.Serialize(index, SourceGenerationContext.Default.LinkIndex); + public static string Serialize(LinkReferenceRegistry referenceRegistry) => + JsonSerializer.Serialize(referenceRegistry, SourceGenerationContext.Default.LinkReferenceRegistry); } -public record LinkIndexEntry +public record LinkRegistryEntry { [JsonPropertyName("repository")] public required string Repository { get; init; } diff --git a/Elastic.Documentation/Serialization/SourceGenerationContext.cs b/Elastic.Documentation/Serialization/SourceGenerationContext.cs index e310e4d75..53e6b94d0 100644 --- a/Elastic.Documentation/Serialization/SourceGenerationContext.cs +++ b/Elastic.Documentation/Serialization/SourceGenerationContext.cs @@ -14,6 +14,6 @@ namespace Elastic.Documentation.Serialization; [JsonSerializable(typeof(GenerationState))] [JsonSerializable(typeof(LinkReference))] [JsonSerializable(typeof(GitCheckoutInformation))] -[JsonSerializable(typeof(LinkIndex))] -[JsonSerializable(typeof(LinkIndexEntry))] +[JsonSerializable(typeof(LinkReferenceRegistry))] +[JsonSerializable(typeof(LinkRegistryEntry))] public sealed partial class SourceGenerationContext : JsonSerializerContext; diff --git a/docs-builder.sln b/docs-builder.sln index 4790affff..49ba2852f 100644 --- a/docs-builder.sln +++ b/docs-builder.sln @@ -85,6 +85,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "validate-path-prefixes-loca EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation", "Elastic.Documentation\Elastic.Documentation.csproj", "{09CE30F6-013A-49ED-B3D6-60AFA84682AC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Configuration", "src\Elastic.Documentation.Configuration\Elastic.Documentation.Configuration.csproj", "{CD94F9E4-7FCD-4152-81F1-4288C6B75367}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -142,6 +144,10 @@ Global {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {09CE30F6-013A-49ED-B3D6-60AFA84682AC}.Release|Any CPU.Build.0 = Release|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {4D198E25-C211-41DC-9E84-B15E89BD7048} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} @@ -162,5 +168,6 @@ Global {C559D52D-100B-4B2B-BE87-2344D835761D} = {4894063D-0DEF-4B7E-97D0-0D0A5B85C608} {BB789671-B262-43DD-91DB-39F9186B8257} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7} {09CE30F6-013A-49ED-B3D6-60AFA84682AC} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} + {CD94F9E4-7FCD-4152-81F1-4288C6B75367} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} EndGlobalSection EndGlobal diff --git a/Elastic.Documentation/Configuration/Assembler/AssemblyConfiguration.cs b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs similarity index 96% rename from Elastic.Documentation/Configuration/Assembler/AssemblyConfiguration.cs rename to src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs index 7144c759f..5308eec03 100644 --- a/Elastic.Documentation/Configuration/Assembler/AssemblyConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs @@ -4,6 +4,7 @@ using Elastic.Documentation.Serialization; using YamlDotNet.Serialization; +using YamlStaticContext = Elastic.Documentation.Configuration.Serialization.YamlStaticContext; namespace Elastic.Documentation.Configuration.Assembler; diff --git a/Elastic.Documentation/Configuration/Assembler/ContentSource.cs b/src/Elastic.Documentation.Configuration/Assembler/ContentSource.cs similarity index 100% rename from Elastic.Documentation/Configuration/Assembler/ContentSource.cs rename to src/Elastic.Documentation.Configuration/Assembler/ContentSource.cs diff --git a/Elastic.Documentation/Configuration/Assembler/GoogleTagManager.cs b/src/Elastic.Documentation.Configuration/Assembler/GoogleTagManager.cs similarity index 100% rename from Elastic.Documentation/Configuration/Assembler/GoogleTagManager.cs rename to src/Elastic.Documentation.Configuration/Assembler/GoogleTagManager.cs diff --git a/Elastic.Documentation/Configuration/Assembler/GoogleTagManagerConfiguration.cs b/src/Elastic.Documentation.Configuration/Assembler/GoogleTagManagerConfiguration.cs similarity index 100% rename from Elastic.Documentation/Configuration/Assembler/GoogleTagManagerConfiguration.cs rename to src/Elastic.Documentation.Configuration/Assembler/GoogleTagManagerConfiguration.cs diff --git a/Elastic.Documentation/Configuration/Assembler/PublishEnvironment.cs b/src/Elastic.Documentation.Configuration/Assembler/PublishEnvironment.cs similarity index 100% rename from Elastic.Documentation/Configuration/Assembler/PublishEnvironment.cs rename to src/Elastic.Documentation.Configuration/Assembler/PublishEnvironment.cs diff --git a/Elastic.Documentation/Configuration/Assembler/Repository.cs b/src/Elastic.Documentation.Configuration/Assembler/Repository.cs similarity index 100% rename from Elastic.Documentation/Configuration/Assembler/Repository.cs rename to src/Elastic.Documentation.Configuration/Assembler/Repository.cs diff --git a/src/Elastic.Markdown/IO/Configuration/ConfigurationFile.cs b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs similarity index 83% rename from src/Elastic.Markdown/IO/Configuration/ConfigurationFile.cs rename to src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs index d2953ee2f..cf42f94de 100644 --- a/src/Elastic.Markdown/IO/Configuration/ConfigurationFile.cs +++ b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs @@ -4,19 +4,15 @@ using System.IO.Abstractions; using DotNet.Globbing; -using Elastic.Documentation; -using Elastic.Documentation.Configuration.Builder; using Elastic.Documentation.Configuration.TableOfContents; +using Elastic.Documentation.Links; using Elastic.Documentation.Navigation; -using Elastic.Markdown.Diagnostics; -using Elastic.Markdown.Extensions; -using Elastic.Markdown.Extensions.DetectionRules; -namespace Elastic.Markdown.IO.Configuration; +namespace Elastic.Documentation.Configuration.Builder; public record ConfigurationFile : ITableOfContentsScope { - private readonly BuildContext _context; + private readonly IDocumentationContext _context; public IFileInfo SourceFile => _context.ConfigurationPath; @@ -31,8 +27,6 @@ public record ConfigurationFile : ITableOfContentsScope public EnabledExtensions Extensions { get; } = new([]); - public IReadOnlyCollection EnabledExtensions { get; } = []; - public IReadOnlyCollection TableOfContents { get; } = []; public HashSet Files { get; } = new(StringComparer.OrdinalIgnoreCase); @@ -61,7 +55,7 @@ public record ConfigurationFile : ITableOfContentsScope Project is not null && Project.Equals("Elastic documentation", StringComparison.OrdinalIgnoreCase); - public ConfigurationFile(BuildContext context) + public ConfigurationFile(IDocumentationContext context) { _context = context; ScopeDirectory = context.ConfigurationPath.Directory!; @@ -103,7 +97,6 @@ public ConfigurationFile(BuildContext context) break; case "extensions": Extensions = new([.. YamlStreamReader.ReadStringArray(entry.Entry)]); - EnabledExtensions = InstantiateExtensions(); break; case "subs": _substitutions = reader.ReadDictionary(entry.Entry); @@ -136,19 +129,4 @@ public ConfigurationFile(BuildContext context) Globs = [.. ImplicitFolders.Select(f => Glob.Parse($"{f}{Path.DirectorySeparatorChar}*.md"))]; } - private IReadOnlyCollection InstantiateExtensions() - { - var list = new List(); - foreach (var extension in Extensions.Enabled) - { - switch (extension.ToLowerInvariant()) - { - case "detection-rules": - list.Add(new DetectionRulesDocsBuilderExtension(_context)); - continue; - } - } - - return list.AsReadOnly(); - } } diff --git a/Elastic.Documentation/Configuration/Builder/EnabledExtensions.cs b/src/Elastic.Documentation.Configuration/Builder/EnabledExtensions.cs similarity index 100% rename from Elastic.Documentation/Configuration/Builder/EnabledExtensions.cs rename to src/Elastic.Documentation.Configuration/Builder/EnabledExtensions.cs diff --git a/Elastic.Documentation/Configuration/Builder/FeatureFlags.cs b/src/Elastic.Documentation.Configuration/Builder/FeatureFlags.cs similarity index 100% rename from Elastic.Documentation/Configuration/Builder/FeatureFlags.cs rename to src/Elastic.Documentation.Configuration/Builder/FeatureFlags.cs diff --git a/src/Elastic.Markdown/IO/Configuration/RedirectFile.cs b/src/Elastic.Documentation.Configuration/Builder/RedirectFile.cs similarity index 94% rename from src/Elastic.Markdown/IO/Configuration/RedirectFile.cs rename to src/Elastic.Documentation.Configuration/Builder/RedirectFile.cs index e44439114..f9f6b4864 100644 --- a/src/Elastic.Markdown/IO/Configuration/RedirectFile.cs +++ b/src/Elastic.Documentation.Configuration/Builder/RedirectFile.cs @@ -3,18 +3,18 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; -using Elastic.Documentation; +using Elastic.Documentation.Links; using YamlDotNet.RepresentationModel; -namespace Elastic.Markdown.IO.Configuration; +namespace Elastic.Documentation.Configuration.Builder; public record RedirectFile { public Dictionary? Redirects { get; set; } - public IFileInfo Source { get; init; } - public BuildContext Context { get; init; } + private IFileInfo Source { get; init; } + private IDocumentationContext Context { get; init; } - public RedirectFile(IFileInfo source, BuildContext context) + public RedirectFile(IFileInfo source, IDocumentationContext context) { Source = source; Context = context; diff --git a/src/Elastic.Markdown/IO/Configuration/TableOfContentsConfiguration.cs b/src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs similarity index 84% rename from src/Elastic.Markdown/IO/Configuration/TableOfContentsConfiguration.cs rename to src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs index e8ef5c8fb..907ee8e6b 100644 --- a/src/Elastic.Markdown/IO/Configuration/TableOfContentsConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Builder/TableOfContentsConfiguration.cs @@ -4,17 +4,16 @@ using System.IO.Abstractions; using System.Runtime.InteropServices; -using Elastic.Documentation; +using Elastic.Documentation.Configuration.Plugins.DetectionRules.TableOfContents; using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Documentation.Navigation; -using Elastic.Markdown.Extensions.DetectionRules; using YamlDotNet.RepresentationModel; -namespace Elastic.Markdown.IO.Configuration; +namespace Elastic.Documentation.Configuration.Builder; public record TableOfContentsConfiguration : ITableOfContentsScope { - private readonly BuildContext _context; + private readonly IDocumentationContext _context; private readonly int _maxTocDepth; private readonly int _depth; private readonly string _parentPath; @@ -34,7 +33,7 @@ public TableOfContentsConfiguration( ConfigurationFile configuration, IFileInfo definitionFile, IDirectoryInfo scope, - BuildContext context, + IDocumentationContext context, int depth, string parentPath) { @@ -86,8 +85,8 @@ private IReadOnlyCollection ReadChildren() return children; if (filePaths.Length is > 1 or 0) reader.EmitError("toc with nested toc sections must only link a single file: index.md", entry.Key); - else if (!filePaths[0].Path.EndsWith("index.md")) - reader.EmitError($"toc with nested toc sections must only link a single file: 'index.md' actually linked {filePaths[0].Path}", entry.Key); + else if (!filePaths[0].RelativePath.EndsWith("index.md", StringComparison.OrdinalIgnoreCase)) + reader.EmitError($"toc with nested toc sections must only link a single file: 'index.md' actually linked {filePaths[0].RelativePath}", entry.Key); return children; } } @@ -133,8 +132,6 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV string? folder = null; string[]? detectionRules = null; TableOfContentsConfiguration? toc = null; - var fileFound = false; - var folderFound = false; var detectionRulesFound = false; var hiddenFile = false; IReadOnlyCollection? children = null; @@ -144,15 +141,15 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV switch (key) { case "toc": - toc = ReadNestedToc(reader, entry, parentPath, out fileFound); + toc = ReadNestedToc(reader, entry, parentPath); break; case "hidden": case "file": hiddenFile = key == "hidden"; - file = ReadFile(reader, entry, parentPath, out fileFound); + file = ReadFile(reader, entry, parentPath); break; case "folder": - folder = ReadFolder(reader, entry, parentPath, out folderFound); + folder = ReadFolder(reader, entry, parentPath); parentPath += $"{Path.DirectorySeparatorChar}{folder}"; break; case "detection_rules": @@ -173,7 +170,7 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV foreach (var f in toc.Files) _ = Files.Add(f); - return [new TocReference(toc.Source, toc, $"{parentPath}".TrimStart(Path.DirectorySeparatorChar), folderFound, toc.TableOfContents)]; + return [new TocReference(toc.Source, toc, $"{parentPath}".TrimStart(Path.DirectorySeparatorChar), toc.TableOfContents)]; } if (file is not null) @@ -190,16 +187,16 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV } else { - var extension = _configuration.EnabledExtensions.OfType().First(); - children = extension.CreateTableOfContentItems(_configuration, parentPath, detectionRules, Files); var overviewPath = $"{parentPath}{Path.DirectorySeparatorChar}{file}".TrimStart(Path.DirectorySeparatorChar); - var landingPage = new RuleOverviewReference(this, overviewPath, fileFound, children, detectionRules); + var landingPage = new RuleOverviewReference(this, overviewPath, parentPath, _configuration, _context, detectionRules); + foreach (var child in landingPage.Children.OfType()) + _ = Files.Add(child.RelativePath); return [landingPage]; } } var path = $"{parentPath}{Path.DirectorySeparatorChar}{file}".TrimStart(Path.DirectorySeparatorChar); - return [new FileReference(this, path, fileFound, hiddenFile, children ?? [])]; + return [new FileReference(this, path, hiddenFile, children ?? [])]; } if (folder is not null) @@ -207,24 +204,21 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV if (children is null) _ = _configuration.ImplicitFolders.Add(parentPath.TrimStart(Path.DirectorySeparatorChar)); - return [new FolderReference(this, $"{parentPath}".TrimStart(Path.DirectorySeparatorChar), folderFound, children ?? [])]; + return [new FolderReference(this, $"{parentPath}".TrimStart(Path.DirectorySeparatorChar), children ?? [])]; } return null; } - private string? ReadFolder(YamlStreamReader reader, KeyValuePair entry, string parentPath, out bool found) + private string? ReadFolder(YamlStreamReader reader, KeyValuePair entry, string parentPath) { - found = false; var folder = reader.ReadString(entry); - if (folder is not null) - { - var path = Path.Combine(_rootPath.FullName, parentPath.TrimStart(Path.DirectorySeparatorChar), folder); - if (!_context.ReadFileSystem.DirectoryInfo.New(path).Exists) - reader.EmitError($"Directory '{path}' does not exist", entry.Key); - else - found = true; - } + if (folder is null) + return folder; + + var path = Path.Combine(_rootPath.FullName, parentPath.TrimStart(Path.DirectorySeparatorChar), folder); + if (!_context.ReadFileSystem.DirectoryInfo.New(path).Exists) + reader.EmitError($"Directory '{path}' does not exist", entry.Key); return folder; } @@ -248,9 +242,8 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV return folders.Length == 0 ? null : folders; } - private string? ReadFile(YamlStreamReader reader, KeyValuePair entry, string parentPath, out bool found) + private string? ReadFile(YamlStreamReader reader, KeyValuePair entry, string parentPath) { - found = false; var file = reader.ReadString(entry); if (file is null) return null; @@ -260,16 +253,14 @@ private IReadOnlyCollection ReadChildren(YamlStreamReader reader, KeyV var path = Path.Combine(_rootPath.FullName, parentPath.TrimStart(Path.DirectorySeparatorChar), file); if (!_context.ReadFileSystem.FileInfo.New(path).Exists) reader.EmitError($"File '{path}' does not exist", entry.Key); - else - found = true; _ = Files.Add(Path.Combine(parentPath, file).TrimStart(Path.DirectorySeparatorChar)); return file; } - private TableOfContentsConfiguration? ReadNestedToc(YamlStreamReader reader, KeyValuePair entry, string parentPath, out bool found) + private TableOfContentsConfiguration? ReadNestedToc(YamlStreamReader reader, KeyValuePair entry, string parentPath) { - found = false; + var found = false; var tocPath = reader.ReadString(entry); if (tocPath is null) { diff --git a/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj new file mode 100644 index 000000000..6697a2d71 --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRule.cs b/src/Elastic.Documentation.Configuration/Plugins/DetectionRules/DetectionRule.cs similarity index 98% rename from src/Elastic.Markdown/Extensions/DetectionRules/DetectionRule.cs rename to src/Elastic.Documentation.Configuration/Plugins/DetectionRules/DetectionRule.cs index 78eaa7807..0064f22ef 100644 --- a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRule.cs +++ b/src/Elastic.Documentation.Configuration/Plugins/DetectionRules/DetectionRule.cs @@ -6,7 +6,7 @@ using Tomlet; using Tomlet.Models; -namespace Elastic.Markdown.Extensions.DetectionRules; +namespace Elastic.Documentation.Configuration.Plugins.DetectionRules; public record DetectionRuleThreat { @@ -48,7 +48,7 @@ public record DetectionRule public required string[]? Tags { get; init; } - public string? Domain => Tags?.FirstOrDefault(t => t.StartsWith("Domain:"))?[7..]?.Trim(); + public string? Domain => Tags?.FirstOrDefault(t => t.StartsWith("Domain:", StringComparison.Ordinal))?[7..]?.Trim(); public required string Severity { get; init; } diff --git a/src/Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/DetectionRulesReference.cs b/src/Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/DetectionRulesReference.cs new file mode 100644 index 000000000..69b6042c0 --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/DetectionRulesReference.cs @@ -0,0 +1,75 @@ +// 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 Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Configuration.TableOfContents; +using Elastic.Documentation.Navigation; + +namespace Elastic.Documentation.Configuration.Plugins.DetectionRules.TableOfContents; + +public record RuleOverviewReference : FileReference +{ + + public IReadOnlyCollection DetectionRuleFolders { get; init; } + + private string ParentPath { get; } + + public RuleOverviewReference( + ITableOfContentsScope tableOfContentsScope, + string overviewFilePath, + string parentPath, + ConfigurationFile configuration, + IDocumentationContext context, + IReadOnlyCollection detectionRuleFolders + ) + : base(tableOfContentsScope, overviewFilePath, false, []) + { + ParentPath = parentPath; + DetectionRuleFolders = detectionRuleFolders; + Children = CreateTableOfContentItems(configuration, context); + } + + private IReadOnlyCollection CreateTableOfContentItems(ConfigurationFile configuration, IDocumentationContext context) + { + var tocItems = new List(); + foreach (var detectionRuleFolder in DetectionRuleFolders) + { + var children = ReadDetectionRuleFolder(configuration, context, detectionRuleFolder); + tocItems.AddRange(children); + } + + return tocItems + .OrderBy(d => d is RuleReference r ? r.Rule.Name : null, StringComparer.OrdinalIgnoreCase) + .ToArray(); + } + + private IReadOnlyCollection ReadDetectionRuleFolder(ConfigurationFile configuration, IDocumentationContext context, string detectionRuleFolder) + { + var detectionRulesFolder = Path.Combine(ParentPath, detectionRuleFolder).TrimStart(Path.DirectorySeparatorChar); + var fs = context.ReadFileSystem; + var sourceDirectory = context.DocumentationSourceDirectory; + var path = fs.DirectoryInfo.New(fs.Path.GetFullPath(fs.Path.Combine(sourceDirectory.FullName, detectionRulesFolder))); + IReadOnlyCollection children = path + .EnumerateFiles("*.*", SearchOption.AllDirectories) + .Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden) && !f.Attributes.HasFlag(FileAttributes.System)) + .Where(f => !f.Directory!.Attributes.HasFlag(FileAttributes.Hidden) && !f.Directory!.Attributes.HasFlag(FileAttributes.System)) + .Where(f => f.Extension is ".md" or ".toml") + .Where(f => f.Name != "README.md") + .Where(f => !f.FullName.Contains($"{Path.DirectorySeparatorChar}_deprecated{Path.DirectorySeparatorChar}")) + .Select(f => + { + var relativePath = Path.GetRelativePath(sourceDirectory.FullName, f.FullName); + if (f.Extension == ".toml") + { + var rule = DetectionRule.From(f); + return new RuleReference(configuration, relativePath, detectionRuleFolder, true, [], rule); + } + + return new FileReference(configuration, relativePath, false, []); + }) + .ToArray(); + + return children; + } +} diff --git a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesReference.cs b/src/Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/RuleReference.cs similarity index 52% rename from src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesReference.cs rename to src/Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/RuleReference.cs index 9dd3b804b..ba37d4387 100644 --- a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesReference.cs +++ b/src/Elastic.Documentation.Configuration/Plugins/DetectionRules/TableOfContents/RuleReference.cs @@ -4,24 +4,14 @@ using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Documentation.Navigation; -using Elastic.Markdown.IO.Configuration; -namespace Elastic.Markdown.Extensions.DetectionRules; - -public record RuleOverviewReference( - ITableOfContentsScope TableOfContentsScope, - string Path, - bool Found, - IReadOnlyCollection Children, - IReadOnlyCollection DetectionRuleFolders -) - : FileReference(TableOfContentsScope, Path, Found, false, Children); +namespace Elastic.Documentation.Configuration.Plugins.DetectionRules.TableOfContents; public record RuleReference( ITableOfContentsScope TableOfContentsScope, - string Path, + string RelativePath, string SourceDirectory, bool Found, IReadOnlyCollection Children, DetectionRule Rule ) - : FileReference(TableOfContentsScope, Path, Found, true, Children); + : FileReference(TableOfContentsScope, RelativePath, true, Children); diff --git a/Elastic.Documentation/Serialization/YamlStaticContext.cs b/src/Elastic.Documentation.Configuration/Serialization/YamlStaticContext.cs similarity index 91% rename from Elastic.Documentation/Serialization/YamlStaticContext.cs rename to src/Elastic.Documentation.Configuration/Serialization/YamlStaticContext.cs index 13538fd87..283287519 100644 --- a/Elastic.Documentation/Serialization/YamlStaticContext.cs +++ b/src/Elastic.Documentation.Configuration/Serialization/YamlStaticContext.cs @@ -5,7 +5,7 @@ using Elastic.Documentation.Configuration.Assembler; using YamlDotNet.Serialization; -namespace Elastic.Documentation.Serialization; +namespace Elastic.Documentation.Configuration.Serialization; [YamlStaticContext] [YamlSerializable(typeof(AssemblyConfiguration))] diff --git a/Elastic.Documentation/Configuration/TableOfContents/ITocItem.cs b/src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs similarity index 71% rename from Elastic.Documentation/Configuration/TableOfContents/ITocItem.cs rename to src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs index c1187d87f..4046135ad 100644 --- a/Elastic.Documentation/Configuration/TableOfContents/ITocItem.cs +++ b/src/Elastic.Documentation.Configuration/TableOfContents/ITocItem.cs @@ -11,16 +11,15 @@ public interface ITocItem ITableOfContentsScope TableOfContentsScope { get; } } -public record FileReference(ITableOfContentsScope TableOfContentsScope, string Path, bool Found, bool Hidden, IReadOnlyCollection Children) +public record FileReference(ITableOfContentsScope TableOfContentsScope, string RelativePath, bool Hidden, IReadOnlyCollection Children) : ITocItem; -public record FolderReference(ITableOfContentsScope TableOfContentsScope, string Path, bool Found, IReadOnlyCollection Children) +public record FolderReference(ITableOfContentsScope TableOfContentsScope, string RelativePath, IReadOnlyCollection Children) : ITocItem; -public record TocReference(Uri Source, ITableOfContentsScope TableOfContentsScope, string Path, bool Found, IReadOnlyCollection Children) - : FolderReference(TableOfContentsScope, Path, Found, Children) - +public record TocReference(Uri Source, ITableOfContentsScope TableOfContentsScope, string RelativePath, IReadOnlyCollection Children) + : FolderReference(TableOfContentsScope, RelativePath, Children) { public IReadOnlyDictionary TocReferences { get; } = Children.OfType().ToDictionary(kv => kv.Source, kv => kv); -}; +} diff --git a/src/Elastic.Markdown/IO/Configuration/YamlStreamReader.cs b/src/Elastic.Documentation.Configuration/YamlStreamReader.cs similarity index 98% rename from src/Elastic.Markdown/IO/Configuration/YamlStreamReader.cs rename to src/Elastic.Documentation.Configuration/YamlStreamReader.cs index d691f156b..7e88b6008 100644 --- a/src/Elastic.Markdown/IO/Configuration/YamlStreamReader.cs +++ b/src/Elastic.Documentation.Configuration/YamlStreamReader.cs @@ -5,11 +5,10 @@ using System.Diagnostics.CodeAnalysis; using System.IO.Abstractions; using Elastic.Documentation.Diagnostics; -using Elastic.Markdown.Diagnostics; using YamlDotNet.Core; using YamlDotNet.RepresentationModel; -namespace Elastic.Markdown.IO.Configuration; +namespace Elastic.Documentation.Configuration; public record YamlToplevelKey { diff --git a/src/Elastic.Markdown/BuildContext.cs b/src/Elastic.Markdown/BuildContext.cs index 2f5db3170..fdb6a65ed 100644 --- a/src/Elastic.Markdown/BuildContext.cs +++ b/src/Elastic.Markdown/BuildContext.cs @@ -5,13 +5,13 @@ using System.IO.Abstractions; using Elastic.Documentation; using Elastic.Documentation.Configuration.Assembler; +using Elastic.Documentation.Configuration.Builder; using Elastic.Documentation.Diagnostics; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.Configuration; namespace Elastic.Markdown; -public record BuildContext +public record BuildContext : IDocumentationContext { public IFileSystem ReadFileSystem { get; } public IFileSystem WriteFileSystem { get; } diff --git a/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs b/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs index 136f3c605..9825671a5 100644 --- a/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs +++ b/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using Elastic.Documentation; using Elastic.Documentation.Diagnostics; using Elastic.Markdown.Myst; using Elastic.Markdown.Myst.Directives; @@ -79,50 +80,6 @@ public static void EmitWarning(this ParserContext context, int line, int column, context.Build.Collector.Channel.Write(d); } - public static void EmitError(this BuildContext context, IFileInfo file, string message, Exception? e = null) - { - var d = new Diagnostic - { - Severity = Severity.Error, - File = file.FullName, - Message = CreateExceptionMessage(message, e), - }; - context.Collector.Channel.Write(d); - } - - public static void EmitWarning(this BuildContext context, IFileInfo file, string message) - { - var d = new Diagnostic - { - Severity = Severity.Warning, - File = file.FullName, - Message = message, - }; - context.Collector.Channel.Write(d); - } - - public static void EmitError(this DiagnosticsCollector collector, IFileInfo file, string message, Exception? e = null) - { - var d = new Diagnostic - { - Severity = Severity.Error, - File = file.FullName, - Message = CreateExceptionMessage(message, e), - }; - collector.Channel.Write(d); - } - - public static void EmitWarning(this DiagnosticsCollector collector, IFileInfo file, string message) - { - var d = new Diagnostic - { - Severity = Severity.Warning, - File = file.FullName, - Message = message, - }; - collector.Channel.Write(d); - } - public static void EmitError(this IBlockExtension block, string message, Exception? e = null) => EmitDiagnostic(block, Severity.Error, message, e); public static void EmitWarning(this IBlockExtension block, string message) => EmitDiagnostic(block, Severity.Warning, message); diff --git a/src/Elastic.Markdown/DocumentationGenerator.cs b/src/Elastic.Markdown/DocumentationGenerator.cs index be029b2ba..882365cb0 100644 --- a/src/Elastic.Markdown/DocumentationGenerator.cs +++ b/src/Elastic.Markdown/DocumentationGenerator.cs @@ -5,11 +5,11 @@ using System.IO.Abstractions; using System.Reflection; using System.Text.Json; +using Elastic.Documentation.Legacy; using Elastic.Documentation.Serialization; using Elastic.Documentation.State; using Elastic.Markdown.Exporters; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.HistoryMapping; using Elastic.Markdown.Links.CrossLinks; using Elastic.Markdown.Slices; using Markdig.Syntax; @@ -47,23 +47,23 @@ public DocumentationGenerator( IDocumentationFileOutputProvider? documentationFileOutputProvider = null, IDocumentationFileExporter? documentationExporter = null, IConversionCollector? conversionCollector = null, - IHistoryMapper? historyMapper = null, + ILegacyUrlMapper? legacyUrlMapper = null, IPositionalNavigation? positionalNavigation = null ) { _documentationFileOutputProvider = documentationFileOutputProvider; _conversionCollector = conversionCollector; - _writeFileSystem = docSet.Build.WriteFileSystem; + _writeFileSystem = docSet.Context.WriteFileSystem; _logger = logger.CreateLogger(nameof(DocumentationGenerator)); DocumentationSet = docSet; - Context = docSet.Build; + Context = docSet.Context; Resolver = docSet.LinkResolver; - HtmlWriter = new HtmlWriter(DocumentationSet, _writeFileSystem, new DescriptionGenerator(), navigationHtmlWriter, historyMapper, positionalNavigation); + HtmlWriter = new HtmlWriter(DocumentationSet, _writeFileSystem, new DescriptionGenerator(), navigationHtmlWriter, legacyUrlMapper, positionalNavigation); _documentationFileExporter = documentationExporter - ?? docSet.Build.Configuration.EnabledExtensions.FirstOrDefault(e => e.FileExporter != null)?.FileExporter - ?? new DocumentationFileExporter(docSet.Build.ReadFileSystem, _writeFileSystem); + ?? docSet.EnabledExtensions.FirstOrDefault(e => e.FileExporter != null)?.FileExporter + ?? new DocumentationFileExporter(docSet.Context.ReadFileSystem, _writeFileSystem); _logger.LogInformation("Created documentation set for: {DocumentationSetName}", DocumentationSet.Name); _logger.LogInformation("Source directory: {SourcePath} Exists: {SourcePathExists}", docSet.SourceDirectory, docSet.SourceDirectory.Exists); diff --git a/src/Elastic.Markdown/Elastic.Markdown.csproj b/src/Elastic.Markdown/Elastic.Markdown.csproj index 39084cb72..32cd22044 100644 --- a/src/Elastic.Markdown/Elastic.Markdown.csproj +++ b/src/Elastic.Markdown/Elastic.Markdown.csproj @@ -52,7 +52,6 @@ - @@ -66,6 +65,7 @@ + diff --git a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRuleFile.cs b/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRuleFile.cs index fed529906..559bf4a7d 100644 --- a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRuleFile.cs +++ b/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRuleFile.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using Elastic.Documentation.Configuration.Plugins.DetectionRules; +using Elastic.Documentation.Configuration.Plugins.DetectionRules.TableOfContents; using Elastic.Markdown.IO; using Elastic.Markdown.Myst; using Markdig.Syntax; @@ -20,7 +22,7 @@ public DetectionRuleOverviewFile(IFileInfo sourceFile, IDirectoryInfo rootPath, private Dictionary Files { get; } = []; - public void AddDetectionRuleFile(DetectionRuleFile df, RuleReference ruleReference) => Files[ruleReference.Path] = df; + public void AddDetectionRuleFile(DetectionRuleFile df, RuleReference ruleReference) => Files[ruleReference.RelativePath] = df; protected override Task GetMinimalParseDocumentAsync(Cancel ctx) { @@ -61,7 +63,7 @@ private string GetMarkdown() """; foreach (var r in group.OrderBy(r => r.Rule.Name)) { - var url = Files[r.Path].Url; + var url = Files[r.RelativePath].Url; markdown += $""" [{r.Rule.Name}](!{url})
diff --git a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs index 2d89613d4..0da2b3e09 100644 --- a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs @@ -3,12 +3,11 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using Elastic.Documentation.Configuration.Plugins.DetectionRules.TableOfContents; using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Markdown.Exporters; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.Configuration; using Elastic.Markdown.IO.Navigation; -using Elastic.Markdown.Myst; namespace Elastic.Markdown.Extensions.DetectionRules; @@ -16,23 +15,8 @@ public class DetectionRulesDocsBuilderExtension(BuildContext build) : IDocsBuild { private BuildContext Build { get; } = build; - public bool InjectsIntoNavigation(ITocItem tocItem) => false; - public IDocumentationFileExporter? FileExporter { get; } = new RuleDocumentationFileExporter(build.ReadFileSystem, build.WriteFileSystem); - public void CreateNavigationItem( - DocumentationGroup? parent, - ITocItem tocItem, - NavigationLookups lookups, - List groups, - List navigationItems, - int depth, - ref int fileIndex, - int index) - { - - } - private DetectionRuleOverviewFile? _overviewFile; public void Visit(DocumentationFile file, ITocItem tocItem) { @@ -53,7 +37,7 @@ public void Visit(DocumentationFile file, ITocItem tocItem) } } - public DocumentationFile? CreateDocumentationFile(IFileInfo file, IDirectoryInfo sourceDirectory, DocumentationSet documentationSet) + public DocumentationFile? CreateDocumentationFile(IFileInfo file, DocumentationSet documentationSet) { if (file.Extension != ".toml") return null; @@ -61,12 +45,7 @@ public void Visit(DocumentationFile file, ITocItem tocItem) return new DetectionRuleFile(file, Build.DocumentationSourceDirectory, documentationSet.MarkdownParser, Build, documentationSet); } - public MarkdownFile? CreateMarkdownFile( - IFileInfo file, - IDirectoryInfo sourceDirectory, - MarkdownParser markdownParser, - BuildContext context, - DocumentationSet documentationSet) => + public MarkdownFile? CreateMarkdownFile(IFileInfo file, IDirectoryInfo sourceDirectory, DocumentationSet documentationSet) => file.Name == "index.md" ? new DetectionRuleOverviewFile(file, sourceDirectory, documentationSet.MarkdownParser, Build, documentationSet) : null; @@ -78,7 +57,6 @@ public bool TryGetDocumentationFileBySlug(DocumentationSet documentationSet, str } public IReadOnlyCollection ScanDocumentationFiles( - Func scanDocumentationFiles, Func defaultFileHandling ) { @@ -90,56 +68,10 @@ Func defaultFileHandling var sourceDirectory = Build.ReadFileSystem.DirectoryInfo.New(sourcePath); return rules.Select(r => { - var file = Build.ReadFileSystem.FileInfo.New(Path.Combine(sourceDirectory.FullName, r.Path)); + var file = Build.ReadFileSystem.FileInfo.New(Path.Combine(sourceDirectory.FullName, r.RelativePath)); return defaultFileHandling(file, sourceDirectory); }).ToArray(); } - public IReadOnlyCollection CreateTableOfContentItems(ConfigurationFile configuration, string parentPath, - string[] detectionRuleFolders, - HashSet files) - { - var tocItems = new List(); - foreach (var detectionRuleFolder in detectionRuleFolders) - { - var children = ReadDetectionRuleFolder(configuration, parentPath, files, detectionRuleFolder); - tocItems.AddRange(children); - } - - return tocItems - .OrderBy(d => d is RuleReference r ? r.Rule.Name : null, StringComparer.OrdinalIgnoreCase) - .ToArray(); - } - - private IReadOnlyCollection ReadDetectionRuleFolder(ConfigurationFile configuration, string parentPath, HashSet files, - string detectionRuleFolder) - { - var detectionRulesFolder = Path.Combine(parentPath, detectionRuleFolder).TrimStart(Path.DirectorySeparatorChar); - var fs = Build.ReadFileSystem; - var sourceDirectory = Build.DocumentationSourceDirectory; - var path = fs.DirectoryInfo.New(fs.Path.GetFullPath(fs.Path.Combine(sourceDirectory.FullName, detectionRulesFolder))); - IReadOnlyCollection children = path - .EnumerateFiles("*.*", SearchOption.AllDirectories) - .Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden) && !f.Attributes.HasFlag(FileAttributes.System)) - .Where(f => !f.Directory!.Attributes.HasFlag(FileAttributes.Hidden) && !f.Directory!.Attributes.HasFlag(FileAttributes.System)) - .Where(f => f.Extension is ".md" or ".toml") - .Where(f => f.Name != "README.md") - .Where(f => !f.FullName.Contains($"{Path.DirectorySeparatorChar}_deprecated{Path.DirectorySeparatorChar}")) - .Select(f => - { - var relativePath = Path.GetRelativePath(sourceDirectory.FullName, f.FullName); - if (f.Extension == ".toml") - { - var rule = DetectionRule.From(f); - return new RuleReference(configuration, relativePath, detectionRuleFolder, true, [], rule); - } - - _ = files.Add(relativePath); - return new FileReference(configuration, relativePath, true, false, []); - }) - .ToArray(); - - return children; - } } diff --git a/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs index 730fda0d3..d5c4fd7d5 100644 --- a/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs @@ -6,9 +6,6 @@ using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Markdown.Exporters; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.Configuration; -using Elastic.Markdown.IO.Navigation; -using Elastic.Markdown.Myst; namespace Elastic.Markdown.Extensions; @@ -16,36 +13,18 @@ public interface IDocsBuilderExtension { IDocumentationFileExporter? FileExporter { get; } - /// Inject items into the current navigation - void CreateNavigationItem( - DocumentationGroup? parent, - ITocItem tocItem, - NavigationLookups lookups, - List groups, - List navigationItems, - int depth, - ref int fileIndex, - int index - ); - - /// Return true if this extension handles Navigation injection - bool InjectsIntoNavigation(ITocItem tocItem); - /// Visit the and its equivalent void Visit(DocumentationFile file, ITocItem tocItem); /// Create an instance of if it matches the . /// Return `null` to let another extension handle this. - DocumentationFile? CreateDocumentationFile(IFileInfo file, IDirectoryInfo sourceDirectory, DocumentationSet documentationSet); + DocumentationFile? CreateDocumentationFile(IFileInfo file, DocumentationSet documentationSet); /// Attempts to locate a documentation file by slug, used to locate the document for `docs-builder serve` command bool TryGetDocumentationFileBySlug(DocumentationSet documentationSet, string slug, out DocumentationFile? documentationFile); /// Allows the extension to discover more documentation files for - IReadOnlyCollection ScanDocumentationFiles( - Func scanDocumentationFiles, - Func defaultFileHandling - ); + IReadOnlyCollection ScanDocumentationFiles(Func defaultFileHandling); - MarkdownFile? CreateMarkdownFile(IFileInfo file, IDirectoryInfo sourceDirectory, MarkdownParser markdownParser, BuildContext context, DocumentationSet documentationSet); + MarkdownFile? CreateMarkdownFile(IFileInfo file, IDirectoryInfo sourceDirectory, DocumentationSet documentationSet); } diff --git a/src/Elastic.Markdown/IO/DocumentationFile.cs b/src/Elastic.Markdown/IO/DocumentationFile.cs index 5fdb5417c..c8b657216 100644 --- a/src/Elastic.Markdown/IO/DocumentationFile.cs +++ b/src/Elastic.Markdown/IO/DocumentationFile.cs @@ -12,15 +12,19 @@ public abstract record DocumentationFile { protected DocumentationFile(IFileInfo sourceFile, IDirectoryInfo rootPath, string repository) { + RootPath = rootPath; + Repository = repository; SourceFile = sourceFile; - RelativePath = Path.GetRelativePath(rootPath.FullName, SourceFile.FullName); - RelativeFolder = Path.GetRelativePath(rootPath.FullName, SourceFile.Directory!.FullName); - CrossLink = $"{repository}://{RelativePath.Replace('\\', '/')}"; + RelativePath = Path.GetRelativePath(RootPath.FullName, SourceFile.FullName); + RelativeFolder = Path.GetRelativePath(RootPath.FullName, SourceFile.Directory!.FullName); + CrossLink = $"{Repository}://{RelativePath.Replace('\\', '/')}"; } + public IDirectoryInfo RootPath { get; } public string RelativePath { get; } public string RelativeFolder { get; } public string CrossLink { get; } + public string Repository { get; } /// Allows documentation files of non markdown origins to advertise as their markdown equivalent in links.json public virtual string LinkReferenceRelativePath => RelativePath; @@ -31,9 +35,6 @@ protected DocumentationFile(IFileInfo sourceFile, IDirectoryInfo rootPath, strin public record ImageFile(IFileInfo SourceFile, IDirectoryInfo RootPath, string Repository, string MimeType = "image/png") : DocumentationFile(SourceFile, RootPath, Repository); -public record StaticFile(IFileInfo SourceFile, IDirectoryInfo RootPath, string Repository) - : DocumentationFile(SourceFile, RootPath, Repository); - public record ExcludedFile(IFileInfo SourceFile, IDirectoryInfo RootPath, string Repository) : DocumentationFile(SourceFile, RootPath, Repository); diff --git a/src/Elastic.Markdown/IO/DocumentationSet.cs b/src/Elastic.Markdown/IO/DocumentationSet.cs index 3c3d896c5..ca8fe4093 100644 --- a/src/Elastic.Markdown/IO/DocumentationSet.cs +++ b/src/Elastic.Markdown/IO/DocumentationSet.cs @@ -6,10 +6,12 @@ using System.IO.Abstractions; using System.Runtime.InteropServices; using Elastic.Documentation; +using Elastic.Documentation.Configuration.Builder; using Elastic.Documentation.Configuration.TableOfContents; +using Elastic.Documentation.Links; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Extensions; -using Elastic.Markdown.IO.Configuration; +using Elastic.Markdown.Extensions.DetectionRules; using Elastic.Markdown.IO.Navigation; using Elastic.Markdown.Links.CrossLinks; using Elastic.Markdown.Myst; @@ -80,7 +82,7 @@ public record NavigationLookups : INavigationLookups public class DocumentationSet : INavigationLookups, IPositionalNavigation { - public BuildContext Build { get; } + public BuildContext Context { get; } public string Name { get; } public IFileInfo OutputStateFile { get; } public IFileInfo LinkReferenceFile { get; } @@ -108,24 +110,25 @@ public class DocumentationSet : INavigationLookups, IPositionalNavigation IReadOnlyCollection INavigationLookups.TableOfContents => Configuration.TableOfContents; - IReadOnlyCollection INavigationLookups.EnabledExtensions => Configuration.EnabledExtensions; - public FrozenDictionary MarkdownNavigationLookup { get; } + public IReadOnlyCollection EnabledExtensions { get; } + public DocumentationSet( - BuildContext build, + BuildContext context, ILoggerFactory logger, ICrossLinkResolver? linkResolver = null, TableOfContentsTreeCollector? treeCollector = null ) { - Build = build; - Source = ContentSourceMoniker.Create(build.Git.RepositoryName, null); - SourceDirectory = build.DocumentationSourceDirectory; - OutputDirectory = build.DocumentationOutputDirectory; + Context = context; + Source = ContentSourceMoniker.Create(context.Git.RepositoryName, null); + SourceDirectory = context.DocumentationSourceDirectory; + OutputDirectory = context.DocumentationOutputDirectory; LinkResolver = - linkResolver ?? new CrossLinkResolver(new ConfigurationCrossLinkFetcher(build.Configuration, logger)); - Configuration = build.Configuration; + linkResolver ?? new CrossLinkResolver(new ConfigurationCrossLinkFetcher(context.Configuration, logger)); + Configuration = context.Configuration; + EnabledExtensions = InstantiateExtensions(); treeCollector ??= new TableOfContentsTreeCollector(); var resolver = new ParserResolvers @@ -133,17 +136,17 @@ public DocumentationSet( CrossLinkResolver = LinkResolver, DocumentationFileLookup = DocumentationFileLookup }; - MarkdownParser = new MarkdownParser(build, resolver); + MarkdownParser = new MarkdownParser(context, resolver); - Name = Build.Git != GitCheckoutInformation.Unavailable - ? Build.Git.RepositoryName - : Build.DocumentationCheckoutDirectory?.Name ?? $"unknown-{Build.DocumentationSourceDirectory.Name}"; + Name = Context.Git != GitCheckoutInformation.Unavailable + ? Context.Git.RepositoryName + : Context.DocumentationCheckoutDirectory?.Name ?? $"unknown-{Context.DocumentationSourceDirectory.Name}"; OutputStateFile = OutputDirectory.FileSystem.FileInfo.New(Path.Combine(OutputDirectory.FullName, ".doc.state")); LinkReferenceFile = OutputDirectory.FileSystem.FileInfo.New(Path.Combine(OutputDirectory.FullName, "links.json")); - var files = ScanDocumentationFiles(build, SourceDirectory); - var additionalSources = Build.Configuration.EnabledExtensions - .SelectMany(extension => extension.ScanDocumentationFiles(ScanDocumentationFiles, DefaultFileHandling)) + var files = ScanDocumentationFiles(context, SourceDirectory); + var additionalSources = EnabledExtensions + .SelectMany(extension => extension.ScanDocumentationFiles(DefaultFileHandling)) .ToArray(); Files = files.Concat(additionalSources).Where(f => f is not ExcludedFile).ToArray(); @@ -162,18 +165,18 @@ public DocumentationSet( { FlatMappedFiles = FlatMappedFiles, TableOfContents = Configuration.TableOfContents, - EnabledExtensions = Configuration.EnabledExtensions, + EnabledExtensions = EnabledExtensions, FilesGroupedByFolder = FilesGroupedByFolder, //IndexedTableOfContents = indexedTableOfContents ?? new Dictionary().ToFrozenDictionary() }; - Tree = new TableOfContentsTree(this, Source, Build, lookups, treeCollector, ref fileIndex); + Tree = new TableOfContentsTree(this, Source, Context, lookups, treeCollector, ref fileIndex); var markdownFiles = Files.OfType().ToArray(); var excludedChildren = markdownFiles.Where(f => f.NavigationIndex == -1).ToArray(); foreach (var excludedChild in excludedChildren) - Build.EmitError(Build.ConfigurationPath, $"{excludedChild.RelativePath} is unreachable in the TOC because one of its parents matches exclusion glob"); + Context.EmitError(Context.ConfigurationPath, $"{excludedChild.RelativePath} is unreachable in the TOC because one of its parents matches exclusion glob"); MarkdownFiles = markdownFiles.Where(f => f.NavigationIndex > -1).ToDictionary(i => i.NavigationIndex, i => i).ToFrozenDictionary(); @@ -224,13 +227,13 @@ [.. build.ReadFileSystem.Directory private DocumentationFile DefaultFileHandling(IFileInfo file, IDirectoryInfo sourceDirectory) { - foreach (var extension in Configuration.EnabledExtensions) + foreach (var extension in EnabledExtensions) { - var documentationFile = extension.CreateDocumentationFile(file, sourceDirectory, this); + var documentationFile = extension.CreateDocumentationFile(file, this); if (documentationFile is not null) return documentationFile; } - return new ExcludedFile(file, sourceDirectory, Build.Git.RepositoryName); + return new ExcludedFile(file, sourceDirectory, Context.Git.RepositoryName); } private void ValidateRedirectsExists() @@ -258,14 +261,14 @@ void ValidateExists(string from, string to, IReadOnlyDictionary if (!FlatMappedFiles.TryGetValue(to, out var file)) { - Build.EmitError(Configuration.SourceFile, $"Redirect {from} points to {to} which does not exist"); + Context.EmitError(Configuration.SourceFile, $"Redirect {from} points to {to} which does not exist"); return; } if (file is not MarkdownFile markdownFile) { - Build.EmitError(Configuration.SourceFile, $"Redirect {from} points to {to} which is not a markdown file"); + Context.EmitError(Configuration.SourceFile, $"Redirect {from} points to {to} which is not a markdown file"); return; } @@ -348,9 +351,9 @@ private DocumentationFile CreateMarkDownFile(IFileInfo file, BuildContext contex MarkdownFile ExtensionOrDefaultMarkdown() { - foreach (var extension in Configuration.EnabledExtensions) + foreach (var extension in EnabledExtensions) { - var documentationFile = extension.CreateMarkdownFile(file, SourceDirectory, MarkdownParser, context, this); + var documentationFile = extension.CreateMarkdownFile(file, SourceDirectory, this); if (documentationFile is not null) return documentationFile; } @@ -361,7 +364,7 @@ MarkdownFile ExtensionOrDefaultMarkdown() public LinkReference CreateLinkReference() { var redirects = Configuration.Redirects; - var crossLinks = Build.Collector.CrossLinks.ToHashSet().ToArray(); + var crossLinks = Context.Collector.CrossLinks.ToHashSet().ToArray(); var links = MarkdownFiles.Values .Select(m => (m.LinkReferenceRelativePath, File: m)) .ToDictionary(k => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) @@ -375,8 +378,8 @@ public LinkReference CreateLinkReference() return new LinkReference { Redirects = redirects, - UrlPathPrefix = Build.UrlPathPrefix, - Origin = Build.Git, + UrlPathPrefix = Context.UrlPathPrefix, + Origin = Context.Git, Links = links, CrossLinks = crossLinks }; @@ -388,4 +391,20 @@ public void ClearOutputDirectory() OutputDirectory.Delete(true); OutputDirectory.Create(); } + + private IReadOnlyCollection InstantiateExtensions() + { + var list = new List(); + foreach (var extension in Configuration.Extensions.Enabled) + { + switch (extension.ToLowerInvariant()) + { + case "detection-rules": + list.Add(new DetectionRulesDocsBuilderExtension(Context)); + continue; + } + } + + return list.AsReadOnly(); + } } diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index a5f021cf4..d5a68ed7d 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -8,7 +8,6 @@ using Elastic.Documentation.Navigation; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Helpers; -using Elastic.Markdown.IO.Configuration; using Elastic.Markdown.IO.Navigation; using Elastic.Markdown.Links.CrossLinks; using Elastic.Markdown.Myst; @@ -281,7 +280,7 @@ public static List GetAnchors( .Concat(includedTocs) .Select(toc => subs.Count == 0 ? toc - : toc.Heading.AsSpan().ReplaceSubstitutions(subs, set.Build.Collector, out var r) + : toc.Heading.AsSpan().ReplaceSubstitutions(subs, set.Context.Collector, out var r) ? toc with { Heading = r } : toc) .ToList(); diff --git a/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs b/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs index fd5c31b6b..1c31bcf8c 100644 --- a/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs +++ b/src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs @@ -4,9 +4,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using Elastic.Documentation; using Elastic.Documentation.Configuration.TableOfContents; -using Elastic.Markdown.Diagnostics; -using Elastic.Markdown.IO.Configuration; namespace Elastic.Markdown.IO.Navigation; @@ -221,10 +220,10 @@ protected DocumentationGroup( { if (tocItem is FileReference file) { - if (!lookups.FlatMappedFiles.TryGetValue(file.Path, out var d)) + if (!lookups.FlatMappedFiles.TryGetValue(file.RelativePath, out var d)) { context.EmitError(context.ConfigurationPath, - $"The following file could not be located: {file.Path} it may be excluded from the build in docset.yml"); + $"The following file could not be located: {file.RelativePath} it may be excluded from the build in docset.yml"); continue; } @@ -245,13 +244,13 @@ protected DocumentationGroup( md.NavigationRoot = topLevelGroup; md.NavigationSource = NavigationSource; - foreach (var extension in context.Configuration.EnabledExtensions) + foreach (var extension in lookups.EnabledExtensions) extension.Visit(d, tocItem); if (file.Children.Count > 0 && d is MarkdownFile virtualIndex) { if (file.Hidden) - context.EmitError(context.ConfigurationPath, $"The following file is hidden but has children: {file.Path}"); + context.EmitError(context.ConfigurationPath, $"The following file is hidden but has children: {file.RelativePath}"); var group = new DocumentationGroup(virtualIndex.RelativePath, _treeCollector, context, lookups with { @@ -264,7 +263,7 @@ protected DocumentationGroup( } files.Add(md); - if (file.Path.EndsWith("index.md") && d is MarkdownFile i) + if (file.RelativePath.EndsWith("index.md") && d is MarkdownFile i) indexFile ??= i; // add the page to navigation items unless it's the index file @@ -277,19 +276,19 @@ protected DocumentationGroup( else if (tocItem is FolderReference folder) { var children = folder.Children; - if (children.Count == 0 && lookups.FilesGroupedByFolder.TryGetValue(folder.Path, out var documentationFiles)) + if (children.Count == 0 && lookups.FilesGroupedByFolder.TryGetValue(folder.RelativePath, out var documentationFiles)) { children = [ .. documentationFiles - .Select(d => new FileReference(folder.TableOfContentsScope, d.RelativePath, true, false, [])) + .Select(d => new FileReference(folder.TableOfContentsScope, d.RelativePath, false, [])) ]; } DocumentationGroup group; if (folder is TocReference tocReference) { - var toc = new TableOfContentsTree(tocReference.Source, folder.Path, _treeCollector, context, lookups with + var toc = new TableOfContentsTree(tocReference.Source, folder.RelativePath, _treeCollector, context, lookups with { TableOfContents = children }, ref fileIndex, depth + 1, topLevelGroup, this); @@ -299,7 +298,7 @@ .. documentationFiles } else { - group = new DocumentationGroup(folder.Path, _treeCollector, context, lookups with + group = new DocumentationGroup(folder.RelativePath, _treeCollector, context, lookups with { TableOfContents = children }, NavigationSource, ref fileIndex, depth + 1, topLevelGroup, this); @@ -308,14 +307,6 @@ .. documentationFiles groups.Add(group); } - else - { - foreach (var extension in lookups.EnabledExtensions) - { - if (extension.InjectsIntoNavigation(tocItem)) - extension.CreateNavigationItem(this, tocItem, lookups, groups, navigationItems, depth, ref fileIndex, index); - } - } } if (indexFile is not null) diff --git a/src/Elastic.Markdown/Links/CrossLinks/ConfigurationCrossLinkFetcher.cs b/src/Elastic.Markdown/Links/CrossLinks/ConfigurationCrossLinkFetcher.cs index 81a7e33e4..95705d55e 100644 --- a/src/Elastic.Markdown/Links/CrossLinks/ConfigurationCrossLinkFetcher.cs +++ b/src/Elastic.Markdown/Links/CrossLinks/ConfigurationCrossLinkFetcher.cs @@ -4,8 +4,8 @@ using System.Collections.Frozen; using Elastic.Documentation; +using Elastic.Documentation.Configuration.Builder; using Elastic.Documentation.Links; -using Elastic.Markdown.IO.Configuration; using Microsoft.Extensions.Logging; namespace Elastic.Markdown.Links.CrossLinks; @@ -15,7 +15,7 @@ public class ConfigurationCrossLinkFetcher(ConfigurationFile configuration, ILog public override async Task Fetch(Cancel ctx) { var linkReferences = new Dictionary(); - var linkIndexEntries = new Dictionary(); + var linkIndexEntries = new Dictionary(); var declaredRepositories = new HashSet(); foreach (var repository in configuration.CrossLinkRepositories) { diff --git a/src/Elastic.Markdown/Links/CrossLinks/CrossLinkFetcher.cs b/src/Elastic.Markdown/Links/CrossLinks/CrossLinkFetcher.cs index 5c3d7ccd5..f5ee11c51 100644 --- a/src/Elastic.Markdown/Links/CrossLinks/CrossLinkFetcher.cs +++ b/src/Elastic.Markdown/Links/CrossLinks/CrossLinkFetcher.cs @@ -20,14 +20,14 @@ public record FetchedCrossLinks public required bool FromConfiguration { get; init; } - public required FrozenDictionary LinkIndexEntries { get; init; } + public required FrozenDictionary LinkIndexEntries { get; init; } public static FetchedCrossLinks Empty { get; } = new() { DeclaredRepositories = [], LinkReferences = new Dictionary().ToFrozenDictionary(), FromConfiguration = false, - LinkIndexEntries = new Dictionary().ToFrozenDictionary() + LinkIndexEntries = new Dictionary().ToFrozenDictionary() }; } @@ -35,14 +35,14 @@ public abstract class CrossLinkFetcher(ILoggerFactory logger) : IDisposable { private readonly ILogger _logger = logger.CreateLogger(nameof(CrossLinkFetcher)); private readonly HttpClient _client = new(); - private LinkIndex? _linkIndex; + private LinkReferenceRegistry? _linkIndex; public static LinkReference Deserialize(string json) => JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkReference)!; public abstract Task Fetch(Cancel ctx); - protected async Task FetchLinkIndex(Cancel ctx) + protected async Task FetchLinkIndex(Cancel ctx) { if (_linkIndex is not null) { @@ -52,11 +52,11 @@ protected async Task FetchLinkIndex(Cancel ctx) var url = $"https://elastic-docs-link-index.s3.us-east-2.amazonaws.com/link-index.json"; _logger.LogInformation("Fetching {Url}", url); var json = await _client.GetStringAsync(url, ctx); - _linkIndex = LinkIndex.Deserialize(json); + _linkIndex = LinkReferenceRegistry.Deserialize(json); return _linkIndex; } - protected async Task GetLinkIndexEntry(string repository, Cancel ctx) + protected async Task GetLinkIndexEntry(string repository, Cancel ctx) { var linkIndex = await FetchLinkIndex(ctx); if (!linkIndex.Repositories.TryGetValue(repository, out var repositoryLinks)) @@ -64,7 +64,7 @@ protected async Task GetLinkIndexEntry(string repository, Cancel return GetNextContentSourceLinkIndexEntry(repositoryLinks, repository); } - protected static LinkIndexEntry GetNextContentSourceLinkIndexEntry(IDictionary repositoryLinks, string repository) + protected static LinkRegistryEntry GetNextContentSourceLinkIndexEntry(IDictionary repositoryLinks, string repository) { var linkIndexEntry = (repositoryLinks.TryGetValue("main", out var link) @@ -89,23 +89,23 @@ protected async Task Fetch(string repository, string[] keys, Canc throw new Exception($"Repository found in link index however none of: '{string.Join(", ", keys)}' branches found"); } - protected async Task FetchLinkIndexEntry(string repository, LinkIndexEntry linkIndexEntry, Cancel ctx) + protected async Task FetchLinkIndexEntry(string repository, LinkRegistryEntry linkRegistryEntry, Cancel ctx) { - var linkReference = await TryGetCachedLinkReference(repository, linkIndexEntry); + var linkReference = await TryGetCachedLinkReference(repository, linkRegistryEntry); if (linkReference is not null) return linkReference; - var url = $"https://elastic-docs-link-index.s3.us-east-2.amazonaws.com/{linkIndexEntry.Path}"; + var url = $"https://elastic-docs-link-index.s3.us-east-2.amazonaws.com/{linkRegistryEntry.Path}"; _logger.LogInformation("Fetching links.json for '{Repository}': {Url}", repository, url); var json = await _client.GetStringAsync(url, ctx); linkReference = Deserialize(json); - WriteLinksJsonCachedFile(repository, linkIndexEntry, json); + WriteLinksJsonCachedFile(repository, linkRegistryEntry, json); return linkReference; } - private void WriteLinksJsonCachedFile(string repository, LinkIndexEntry linkIndexEntry, string json) + private void WriteLinksJsonCachedFile(string repository, LinkRegistryEntry linkRegistryEntry, string json) { - var cachedFileName = $"links-elastic-{repository}-{linkIndexEntry.Branch}-{linkIndexEntry.ETag}.json"; + var cachedFileName = $"links-elastic-{repository}-{linkRegistryEntry.Branch}-{linkRegistryEntry.ETag}.json"; var cachedPath = Path.Combine(Paths.ApplicationData.FullName, "links", cachedFileName); if (File.Exists(cachedPath)) return; @@ -120,9 +120,9 @@ private void WriteLinksJsonCachedFile(string repository, LinkIndexEntry linkInde } } - private async Task TryGetCachedLinkReference(string repository, LinkIndexEntry linkIndexEntry) + private async Task TryGetCachedLinkReference(string repository, LinkRegistryEntry linkRegistryEntry) { - var cachedFileName = $"links-elastic-{repository}-main-{linkIndexEntry.ETag}.json"; + var cachedFileName = $"links-elastic-{repository}-main-{linkRegistryEntry.ETag}.json"; var cachedPath = Path.Combine(Paths.ApplicationData.FullName, "links", cachedFileName); if (File.Exists(cachedPath)) { diff --git a/src/Elastic.Markdown/Links/CrossLinks/CrossLinkResolver.cs b/src/Elastic.Markdown/Links/CrossLinks/CrossLinkResolver.cs index 82bf88003..b623a8237 100644 --- a/src/Elastic.Markdown/Links/CrossLinks/CrossLinkResolver.cs +++ b/src/Elastic.Markdown/Links/CrossLinks/CrossLinkResolver.cs @@ -5,6 +5,7 @@ using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; using Elastic.Documentation; +using Elastic.Documentation.Links; namespace Elastic.Markdown.Links.CrossLinks; diff --git a/src/Elastic.Markdown/Links/InboundLinks/LinkIndexCrossLinkFetcher.cs b/src/Elastic.Markdown/Links/InboundLinks/LinkIndexCrossLinkFetcher.cs index 0caa7917e..d7a452230 100644 --- a/src/Elastic.Markdown/Links/InboundLinks/LinkIndexCrossLinkFetcher.cs +++ b/src/Elastic.Markdown/Links/InboundLinks/LinkIndexCrossLinkFetcher.cs @@ -15,7 +15,7 @@ public class LinksIndexCrossLinkFetcher(ILoggerFactory logger) : CrossLinkFetche public override async Task Fetch(Cancel ctx) { var linkReferences = new Dictionary(); - var linkEntries = new Dictionary(); + var linkEntries = new Dictionary(); var declaredRepositories = new HashSet(); var linkIndex = await FetchLinkIndex(ctx); foreach (var (repository, value) in linkIndex.Repositories) diff --git a/src/Elastic.Markdown/Links/InboundLinks/LinkIndexLinkChecker.cs b/src/Elastic.Markdown/Links/InboundLinks/LinkIndexLinkChecker.cs index d6777b926..3a5ba457d 100644 --- a/src/Elastic.Markdown/Links/InboundLinks/LinkIndexLinkChecker.cs +++ b/src/Elastic.Markdown/Links/InboundLinks/LinkIndexLinkChecker.cs @@ -4,6 +4,7 @@ using Elastic.Documentation; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Links; using Elastic.Markdown.IO; using Elastic.Markdown.Links.CrossLinks; using Microsoft.Extensions.Logging; diff --git a/src/Elastic.Markdown/Myst/ParserContext.cs b/src/Elastic.Markdown/Myst/ParserContext.cs index 5ffa5f49e..b7edac058 100644 --- a/src/Elastic.Markdown/Myst/ParserContext.cs +++ b/src/Elastic.Markdown/Myst/ParserContext.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using Elastic.Documentation.Configuration.Builder; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.Configuration; using Elastic.Markdown.Links.CrossLinks; using Elastic.Markdown.Myst.FrontMatter; using Markdig; diff --git a/src/Elastic.Markdown/Slices/HtmlWriter.cs b/src/Elastic.Markdown/Slices/HtmlWriter.cs index f23f432ac..02d024a9a 100644 --- a/src/Elastic.Markdown/Slices/HtmlWriter.cs +++ b/src/Elastic.Markdown/Slices/HtmlWriter.cs @@ -5,9 +5,9 @@ using System.Collections.Concurrent; using System.IO.Abstractions; using Elastic.Documentation; +using Elastic.Documentation.Legacy; using Elastic.Markdown.Extensions.DetectionRules; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.HistoryMapping; using Elastic.Markdown.IO.Navigation; using Markdig.Syntax; using RazorSlices; @@ -55,7 +55,7 @@ private NavigationViewModel CreateNavigationModel(INavigationGroup navigation) return new NavigationViewModel { Title = tree.Index?.NavigationTitle ?? "Docs", - TitleUrl = tree.Index?.Url ?? Set.Build.UrlPathPrefix ?? "/", + TitleUrl = tree.Index?.Url ?? Set.Context.UrlPathPrefix ?? "/", Tree = tree, IsPrimaryNavEnabled = Set.Configuration.Features.IsPrimaryNavEnabled, IsGlobalAssemblyBuild = false, @@ -69,14 +69,14 @@ public class HtmlWriter( IFileSystem writeFileSystem, IDescriptionGenerator descriptionGenerator, INavigationHtmlWriter? navigationHtmlWriter = null, - IHistoryMapper? historyMapper = null, + ILegacyUrlMapper? legacyUrlMapper = null, IPositionalNavigation? positionalNavigation = null ) { private DocumentationSet DocumentationSet { get; } = documentationSet; public INavigationHtmlWriter NavigationHtmlWriter { get; } = navigationHtmlWriter ?? new IsolatedBuildNavigationHtmlWriter(documentationSet); - private StaticFileContentHashProvider StaticFileContentHashProvider { get; } = new(new EmbeddedOrPhysicalFileProvider(documentationSet.Build)); - private IHistoryMapper HistoryMapper { get; } = historyMapper ?? new BypassHistoryMapper(); + private StaticFileContentHashProvider StaticFileContentHashProvider { get; } = new(new EmbeddedOrPhysicalFileProvider(documentationSet.Context)); + private ILegacyUrlMapper LegacyUrlMapper { get; } = legacyUrlMapper ?? new NoopLegacyUrlMapper(); private IPositionalNavigation PositionalNavigation { get; } = positionalNavigation ?? documentationSet; public async Task RenderLayout(MarkdownFile markdown, Cancel ctx = default) @@ -96,24 +96,24 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDocument var next = PositionalNavigation.GetNext(markdown); var parents = PositionalNavigation.GetParentMarkdownFiles(markdown); - var remote = DocumentationSet.Build.Git.RepositoryName; - var branch = DocumentationSet.Build.Git.Branch; + var remote = DocumentationSet.Context.Git.RepositoryName; + var branch = DocumentationSet.Context.Git.Branch; string? editUrl = null; - if (DocumentationSet.Build.Git != GitCheckoutInformation.Unavailable && DocumentationSet.Build.DocumentationCheckoutDirectory is { } checkoutDirectory) + if (DocumentationSet.Context.Git != GitCheckoutInformation.Unavailable && DocumentationSet.Context.DocumentationCheckoutDirectory is { } checkoutDirectory) { - var relativeSourcePath = Path.GetRelativePath(checkoutDirectory.FullName, DocumentationSet.Build.DocumentationSourceDirectory.FullName); + var relativeSourcePath = Path.GetRelativePath(checkoutDirectory.FullName, DocumentationSet.Context.DocumentationSourceDirectory.FullName); var path = Path.Combine(relativeSourcePath, markdown.RelativePath); editUrl = $"https://github.com/elastic/{remote}/edit/{branch}/{path}"; } Uri? reportLinkParameter = null; - if (DocumentationSet.Build.CanonicalBaseUrl is not null) - reportLinkParameter = new Uri(DocumentationSet.Build.CanonicalBaseUrl, Path.Combine(DocumentationSet.Build.UrlPathPrefix ?? string.Empty, markdown.Url)); + if (DocumentationSet.Context.CanonicalBaseUrl is not null) + reportLinkParameter = new Uri(DocumentationSet.Context.CanonicalBaseUrl, Path.Combine(DocumentationSet.Context.UrlPathPrefix ?? string.Empty, markdown.Url)); var reportUrl = $"https://github.com/elastic/docs-content/issues/new?template=issue-report.yaml&link={reportLinkParameter}&labels=source:web"; var siteName = DocumentationSet.Tree.Index?.Title ?? "Elastic Documentation"; - var legacyPage = HistoryMapper.MapLegacyUrl(markdown.YamlFrontMatter?.MappedPages); + var legacyPage = LegacyUrlMapper.MapLegacyUrl(markdown.YamlFrontMatter?.MappedPages); var slice = Index.Create(new IndexViewModel { @@ -133,9 +133,9 @@ private async Task RenderLayout(MarkdownFile markdown, MarkdownDocument UrlPathPrefix = markdown.UrlPathPrefix, AppliesTo = markdown.YamlFrontMatter?.AppliesTo, GithubEditUrl = editUrl, - AllowIndexing = DocumentationSet.Build.AllowIndexing && (markdown is DetectionRuleFile || !markdown.Hidden), - CanonicalBaseUrl = DocumentationSet.Build.CanonicalBaseUrl, - GoogleTagManager = DocumentationSet.Build.GoogleTagManager, + AllowIndexing = DocumentationSet.Context.AllowIndexing && (markdown is DetectionRuleFile || !markdown.Hidden), + CanonicalBaseUrl = DocumentationSet.Context.CanonicalBaseUrl, + GoogleTagManager = DocumentationSet.Context.GoogleTagManager, Features = DocumentationSet.Configuration.Features, StaticFileContentHashProvider = StaticFileContentHashProvider, ReportIssueUrl = reportUrl, diff --git a/src/Elastic.Markdown/Slices/_ViewModels.cs b/src/Elastic.Markdown/Slices/_ViewModels.cs index cc845c2be..740da3d4b 100644 --- a/src/Elastic.Markdown/Slices/_ViewModels.cs +++ b/src/Elastic.Markdown/Slices/_ViewModels.cs @@ -4,8 +4,8 @@ using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Legacy; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.HistoryMapping; using Elastic.Markdown.IO.Navigation; using Elastic.Markdown.Myst.FrontMatter; diff --git a/src/docs-assembler/AssembleContext.cs b/src/docs-assembler/AssembleContext.cs index 5cc98e192..8f8402001 100644 --- a/src/docs-assembler/AssembleContext.cs +++ b/src/docs-assembler/AssembleContext.cs @@ -62,9 +62,9 @@ public AssembleContext( ExtractAssemblerConfiguration(navigationPath, "navigation.yml"); NavigationPath = ReadFileSystem.FileInfo.New(navigationPath); - var historyMappingPath = Path.Combine(Paths.WorkingDirectoryRoot.FullName, "src", "docs-assembler", "historymapping.yml"); + var historyMappingPath = Path.Combine(Paths.WorkingDirectoryRoot.FullName, "src", "docs-assembler", "legacy-url-mappings.yml"); if (!ReadFileSystem.File.Exists(historyMappingPath)) - ExtractAssemblerConfiguration(historyMappingPath, "historymapping.yml"); + ExtractAssemblerConfiguration(historyMappingPath, "legacy-url-mappings.yml"); HistoryMappingPath = ReadFileSystem.FileInfo.New(historyMappingPath); if (!Configuration.Environments.TryGetValue(environment, out var env)) diff --git a/src/docs-assembler/AssembleSources.cs b/src/docs-assembler/AssembleSources.cs index c31868bc5..939a5413e 100644 --- a/src/docs-assembler/AssembleSources.cs +++ b/src/docs-assembler/AssembleSources.cs @@ -8,8 +8,9 @@ using Documentation.Assembler.Navigation; using Documentation.Assembler.Sourcing; using Elastic.Documentation; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Assembler; -using Elastic.Markdown.IO.Configuration; +using Elastic.Documentation.Configuration.Builder; using Elastic.Markdown.IO.Navigation; using Elastic.Markdown.Links.CrossLinks; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/docs-assembler/Building/AssemblerBuilder.cs b/src/docs-assembler/Building/AssemblerBuilder.cs index 0ee958fe3..c4e42c7c3 100644 --- a/src/docs-assembler/Building/AssemblerBuilder.cs +++ b/src/docs-assembler/Building/AssemblerBuilder.cs @@ -4,8 +4,8 @@ using System.Collections.Frozen; using Documentation.Assembler.Navigation; +using Elastic.Documentation.Legacy; using Elastic.Markdown; -using Elastic.Markdown.IO.HistoryMapping; using Microsoft.Extensions.Logging; namespace Documentation.Assembler.Building; @@ -16,11 +16,12 @@ public class AssemblerBuilder( GlobalNavigation navigation, GlobalNavigationHtmlWriter writer, GlobalNavigationPathProvider pathProvider, - IHistoryMapper? historyMapper) + ILegacyUrlMapper? legacyUrlMapper +) { private GlobalNavigationHtmlWriter HtmlWriter { get; } = writer; - private IHistoryMapper? HistoryMapper { get; } = historyMapper; + private ILegacyUrlMapper? LegacyUrlMapper { get; } = legacyUrlMapper; public async Task BuildAllAsync(FrozenDictionary assembleSets, Cancel ctx) { @@ -62,7 +63,7 @@ private async Task BuildAsync(AssemblerDocumentationSet set, Cancel ctx) set.DocumentationSet, logger, HtmlWriter, pathProvider, - historyMapper: HistoryMapper, + legacyUrlMapper: LegacyUrlMapper, positionalNavigation: navigation ); await generator.GenerateAll(ctx); diff --git a/src/docs-assembler/Building/AssemblerCrossLinkFetcher.cs b/src/docs-assembler/Building/AssemblerCrossLinkFetcher.cs index 624c5beb0..b8a1661a8 100644 --- a/src/docs-assembler/Building/AssemblerCrossLinkFetcher.cs +++ b/src/docs-assembler/Building/AssemblerCrossLinkFetcher.cs @@ -16,7 +16,7 @@ public class AssemblerCrossLinkFetcher(ILoggerFactory logger, AssemblyConfigurat public override async Task Fetch(Cancel ctx) { var linkReferences = new Dictionary(); - var linkIndexEntries = new Dictionary(); + var linkIndexEntries = new Dictionary(); var declaredRepositories = new HashSet(); var repositories = configuration.ReferenceRepositories.Values.Concat([configuration.Narrative]); diff --git a/src/docs-assembler/Cli/RepositoryCommands.cs b/src/docs-assembler/Cli/RepositoryCommands.cs index 85e757150..703684489 100644 --- a/src/docs-assembler/Cli/RepositoryCommands.cs +++ b/src/docs-assembler/Cli/RepositoryCommands.cs @@ -12,7 +12,7 @@ using Amazon.S3.Model; using ConsoleAppFramework; using Documentation.Assembler.Building; -using Documentation.Assembler.Mapping; +using Documentation.Assembler.Legacy; using Documentation.Assembler.Navigation; using Documentation.Assembler.Sourcing; using Elastic.Documentation.Configuration.Assembler; @@ -114,7 +114,7 @@ public async Task BuildAll( var pathProvider = new GlobalNavigationPathProvider(navigationFile, assembleSources, assembleContext); var htmlWriter = new GlobalNavigationHtmlWriter(navigationFile, assembleContext, navigation, assembleSources); - var historyMapper = new PageHistoryMapper(assembleSources.HistoryMappings); + var historyMapper = new PageLegacyUrlMapper(assembleSources.HistoryMappings); var builder = new AssemblerBuilder(logger, assembleContext, navigation, htmlWriter, pathProvider, historyMapper); await builder.BuildAllAsync(assembleSources.AssembleSets, ctx); diff --git a/src/docs-assembler/Mapping/PageHistoryMapper.cs b/src/docs-assembler/Legacy/PageLegacyUrlMapper.cs similarity index 79% rename from src/docs-assembler/Mapping/PageHistoryMapper.cs rename to src/docs-assembler/Legacy/PageLegacyUrlMapper.cs index 9a9817c7c..3a147d651 100644 --- a/src/docs-assembler/Mapping/PageHistoryMapper.cs +++ b/src/docs-assembler/Legacy/PageLegacyUrlMapper.cs @@ -2,15 +2,15 @@ // 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 Elastic.Markdown.IO.HistoryMapping; +using Elastic.Documentation.Legacy; -namespace Documentation.Assembler.Mapping; +namespace Documentation.Assembler.Legacy; -public record PageHistoryMapper : IHistoryMapper +public record PageLegacyUrlMapper : ILegacyUrlMapper { private IReadOnlyDictionary PreviousUrls { get; } - public PageHistoryMapper(IReadOnlyDictionary previousUrls) => PreviousUrls = previousUrls; + public PageLegacyUrlMapper(IReadOnlyDictionary previousUrls) => PreviousUrls = previousUrls; public LegacyPageMapping? MapLegacyUrl(IReadOnlyCollection? mappedPages) { diff --git a/src/docs-assembler/Links/NavigationPrefixChecker.cs b/src/docs-assembler/Links/NavigationPrefixChecker.cs index c693e2326..4e9ed1f1a 100644 --- a/src/docs-assembler/Links/NavigationPrefixChecker.cs +++ b/src/docs-assembler/Links/NavigationPrefixChecker.cs @@ -7,6 +7,7 @@ using Documentation.Assembler.Navigation; using Elastic.Documentation; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Links; using Elastic.Markdown.IO; using Elastic.Markdown.Links.CrossLinks; using Elastic.Markdown.Links.InboundLinks; diff --git a/src/docs-assembler/Navigation/GlobalNavigation.cs b/src/docs-assembler/Navigation/GlobalNavigation.cs index 5560e09f4..5324019f9 100644 --- a/src/docs-assembler/Navigation/GlobalNavigation.cs +++ b/src/docs-assembler/Navigation/GlobalNavigation.cs @@ -5,7 +5,6 @@ using System.Collections.Frozen; using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.Configuration; using Elastic.Markdown.IO.Navigation; namespace Documentation.Assembler.Navigation; @@ -118,7 +117,7 @@ private IReadOnlyCollection BuildNavigation(IReadOnlyCollection { FlatMappedFiles = new Dictionary().ToFrozenDictionary(), TableOfContents = [], - EnabledExtensions = documentationSet.Configuration.EnabledExtensions, + EnabledExtensions = documentationSet.EnabledExtensions, FilesGroupedByFolder = new Dictionary().ToFrozenDictionary(), }; @@ -126,7 +125,7 @@ private IReadOnlyCollection BuildNavigation(IReadOnlyCollection tree = new TableOfContentsTree( documentationSet, toc.Source, - documentationSet.Build, + documentationSet.Context, lookups, _assembleSources.TreeCollector, ref fileIndex); } diff --git a/src/docs-assembler/Navigation/GlobalNavigationFile.cs b/src/docs-assembler/Navigation/GlobalNavigationFile.cs index 1af4eae69..7afb05de0 100644 --- a/src/docs-assembler/Navigation/GlobalNavigationFile.cs +++ b/src/docs-assembler/Navigation/GlobalNavigationFile.cs @@ -5,10 +5,10 @@ using System.Collections.Immutable; using System.IO.Abstractions; using Elastic.Documentation; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.TableOfContents; using Elastic.Documentation.Navigation; -using Elastic.Markdown.IO.Configuration; using YamlDotNet.RepresentationModel; namespace Documentation.Assembler.Navigation; @@ -210,7 +210,7 @@ private IReadOnlyCollection ReadChildren(string key, YamlStreamRea if (source != null && !source.Contains("://")) source = ContentSourceMoniker.CreateString(NarrativeRepository.RepositoryName, source); var sourceUri = new Uri(source!); - var tocReference = new TocReference(sourceUri, this, "", true, []); + var tocReference = new TocReference(sourceUri, this, "", []); return tocReference; } } @@ -254,7 +254,7 @@ private IReadOnlyCollection ReadChildren(string key, YamlStreamRea var rootConfig = mapping.RepositoryConfigurationFile.SourceFile.Directory!; var path = Path.GetRelativePath(rootConfig.FullName, mapping.TableOfContentsConfiguration.ScopeDirectory.FullName); - var tocReference = new TocReference(sourceUri, mapping.TableOfContentsConfiguration, path, true, navigationItems); + var tocReference = new TocReference(sourceUri, mapping.TableOfContentsConfiguration, path, navigationItems); return tocReference; } diff --git a/src/docs-assembler/Navigation/GlobalNavigationPathProvider.cs b/src/docs-assembler/Navigation/GlobalNavigationPathProvider.cs index a5cd4f60b..3bd9da14c 100644 --- a/src/docs-assembler/Navigation/GlobalNavigationPathProvider.cs +++ b/src/docs-assembler/Navigation/GlobalNavigationPathProvider.cs @@ -55,13 +55,13 @@ public GlobalNavigationPathProvider(GlobalNavigationFile navigationFile, Assembl - var repositoryName = documentationSet.Build.Git.RepositoryName; + var repositoryName = documentationSet.Context.Git.RepositoryName; var outputDirectory = documentationSet.OutputDirectory; var fs = defaultOutputFile.FileSystem; if (repositoryName == "detection-rules") { - var output = DetectionRuleFile.OutputPath(defaultOutputFile, documentationSet.Build); + var output = DetectionRuleFile.OutputPath(defaultOutputFile, documentationSet.Context); var md = fs.FileInfo.New(Path.ChangeExtension(output.FullName, "md")); relativePath = Path.GetRelativePath(documentationSet.OutputDirectory.FullName, md.FullName); } diff --git a/src/docs-assembler/docs-assembler.csproj b/src/docs-assembler/docs-assembler.csproj index eca28182e..0f7bc6138 100644 --- a/src/docs-assembler/docs-assembler.csproj +++ b/src/docs-assembler/docs-assembler.csproj @@ -34,7 +34,7 @@ - + diff --git a/src/docs-assembler/historymapping.yml b/src/docs-assembler/legacy-url-mappings.yml similarity index 100% rename from src/docs-assembler/historymapping.yml rename to src/docs-assembler/legacy-url-mappings.yml diff --git a/src/docs-builder/Http/DocumentationWebHost.cs b/src/docs-builder/Http/DocumentationWebHost.cs index ca55d1545..6c14b154a 100644 --- a/src/docs-builder/Http/DocumentationWebHost.cs +++ b/src/docs-builder/Http/DocumentationWebHost.cs @@ -162,7 +162,7 @@ private static async Task ServeDocumentationFile(ReloadableGeneratorSta s = Path.GetExtension(slug) == string.Empty ? slug + ".md" : s.Replace($"{Path.DirectorySeparatorChar}index.md", ".md"); if (!generator.DocumentationSet.FlatMappedFiles.TryGetValue(s, out documentationFile)) { - foreach (var extension in generator.Context.Configuration.EnabledExtensions) + foreach (var extension in holder.Generator.DocumentationSet.EnabledExtensions) { if (extension.TryGetDocumentationFileBySlug(generator.DocumentationSet, slug, out documentationFile)) break; diff --git a/src/infra/docs-lambda-index-publisher/LinkIndexProvider.cs b/src/infra/docs-lambda-index-publisher/LinkIndexProvider.cs index 3f7a3e9b5..d6965ebc7 100644 --- a/src/infra/docs-lambda-index-publisher/LinkIndexProvider.cs +++ b/src/infra/docs-lambda-index-publisher/LinkIndexProvider.cs @@ -11,16 +11,16 @@ namespace Elastic.Documentation.Lambda.LinkIndexUploader; /// /// Gets the link index from S3 once. -/// You can then update the link index with and save it with . +/// You can then update the link index with and save it with . /// If the link index changed in the meantime, will throw an exception, /// thus all the messages from the queue will be sent back to the queue. /// public class LinkIndexProvider(IAmazonS3 s3Client, ILambdaLogger logger, string bucketName, string key) { private string? _etag; - private LinkIndex? _linkIndex; + private LinkReferenceRegistry? _linkIndex; - private async Task GetLinkIndex() + private async Task GetLinkIndex() { var getObjectRequest = new GetObjectRequest { @@ -32,31 +32,31 @@ private async Task GetLinkIndex() await using var stream = getObjectResponse.ResponseStream; _etag = getObjectResponse.ETag; logger.LogInformation("Successfully got link index from s3://{bucketName}/{key}", bucketName, key); - _linkIndex = LinkIndex.Deserialize(stream); + _linkIndex = LinkReferenceRegistry.Deserialize(stream); return _linkIndex; } - public async Task UpdateLinkIndexEntry(LinkIndexEntry linkIndexEntry) + public async Task UpdateLinkIndexEntry(LinkRegistryEntry linkRegistryEntry) { _linkIndex ??= await GetLinkIndex(); - if (_linkIndex.Repositories.TryGetValue(linkIndexEntry.Repository, out var existingEntry)) + if (_linkIndex.Repositories.TryGetValue(linkRegistryEntry.Repository, out var existingEntry)) { - var newEntryIsNewer = DateTime.Compare(linkIndexEntry.UpdatedAt, existingEntry[linkIndexEntry.Branch].UpdatedAt) > 0; + var newEntryIsNewer = DateTime.Compare(linkRegistryEntry.UpdatedAt, existingEntry[linkRegistryEntry.Branch].UpdatedAt) > 0; if (newEntryIsNewer) { - existingEntry[linkIndexEntry.Branch] = linkIndexEntry; - logger.LogInformation("Updated existing entry for {repository}@{branch}", linkIndexEntry.Repository, linkIndexEntry.Branch); + existingEntry[linkRegistryEntry.Branch] = linkRegistryEntry; + logger.LogInformation("Updated existing entry for {repository}@{branch}", linkRegistryEntry.Repository, linkRegistryEntry.Branch); } else - logger.LogInformation("Skipping update for {repository}@{branch} because the existing entry is newer", linkIndexEntry.Repository, linkIndexEntry.Branch); + logger.LogInformation("Skipping update for {repository}@{branch} because the existing entry is newer", linkRegistryEntry.Repository, linkRegistryEntry.Branch); } else { - _linkIndex.Repositories.Add(linkIndexEntry.Repository, new Dictionary + _linkIndex.Repositories.Add(linkRegistryEntry.Repository, new Dictionary { - { linkIndexEntry.Branch, linkIndexEntry } + { linkRegistryEntry.Branch, linkRegistryEntry } }); - logger.LogInformation("Added new entry for {repository}@{branch}", linkIndexEntry.Repository, linkIndexEntry.Branch); + logger.LogInformation("Added new entry for {repository}@{branch}", linkRegistryEntry.Repository, linkRegistryEntry.Branch); } } @@ -64,7 +64,7 @@ public async Task Save() { if (_etag == null || _linkIndex == null) throw new InvalidOperationException("You must call UpdateLinkIndexEntry() before Save()"); - var json = LinkIndex.Serialize(_linkIndex); + var json = LinkReferenceRegistry.Serialize(_linkIndex); logger.LogInformation("Saving link index to s3://{bucketName}/{key}", bucketName, key); var putObjectRequest = new PutObjectRequest { diff --git a/src/infra/docs-lambda-index-publisher/LinkReferenceProvider.cs b/src/infra/docs-lambda-index-publisher/LinkReferenceProvider.cs index 8be53f2d5..abfdfa8d5 100644 --- a/src/infra/docs-lambda-index-publisher/LinkReferenceProvider.cs +++ b/src/infra/docs-lambda-index-publisher/LinkReferenceProvider.cs @@ -5,6 +5,7 @@ using Amazon.Lambda.Core; using Amazon.S3; using Amazon.S3.Model; +using Elastic.Documentation.Links; namespace Elastic.Documentation.Lambda.LinkIndexUploader; diff --git a/src/infra/docs-lambda-index-publisher/Program.cs b/src/infra/docs-lambda-index-publisher/Program.cs index ab434ab16..77213d4f0 100644 --- a/src/infra/docs-lambda-index-publisher/Program.cs +++ b/src/infra/docs-lambda-index-publisher/Program.cs @@ -71,13 +71,13 @@ static async Task Handler(SQSEvent ev, ILambdaContext context) } } -static LinkIndexEntry ConvertToLinkIndexEntry(S3EventNotification.S3EventNotificationRecord record, LinkReference linkReference) +static LinkRegistryEntry ConvertToLinkIndexEntry(S3EventNotification.S3EventNotificationRecord record, LinkReference linkReference) { var s3Object = record.S3.Object; var keyTokens = s3Object.Key.Split('/'); var repository = keyTokens[1]; var branch = keyTokens[2]; - return new LinkIndexEntry + return new LinkRegistryEntry { Repository = repository, Branch = branch, diff --git a/tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs b/tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs index 18e67b44c..b2b88bf6f 100644 --- a/tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/LinkReferenceTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using Elastic.Documentation; +using Elastic.Documentation.Links; using Elastic.Markdown.IO; using FluentAssertions; diff --git a/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs b/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs index edbc53e49..0b1755cae 100644 --- a/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs @@ -4,8 +4,8 @@ using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; +using Elastic.Documentation.Configuration.Builder; using Elastic.Markdown.IO; -using Elastic.Markdown.IO.Configuration; using FluentAssertions; using Microsoft.Extensions.Logging; diff --git a/tests/Elastic.Markdown.Tests/TestCrossLinkResolver.cs b/tests/Elastic.Markdown.Tests/TestCrossLinkResolver.cs index b359b415a..56a439dfa 100644 --- a/tests/Elastic.Markdown.Tests/TestCrossLinkResolver.cs +++ b/tests/Elastic.Markdown.Tests/TestCrossLinkResolver.cs @@ -50,7 +50,7 @@ public Task FetchLinks(Cancel ctx) LinkReferences.Add("kibana", reference); DeclaredRepositories.AddRange(["docs-content", "kibana", "elasticsearch"]); - var indexEntries = LinkReferences.ToDictionary(e => e.Key, e => new LinkIndexEntry + var indexEntries = LinkReferences.ToDictionary(e => e.Key, e => new LinkRegistryEntry { Repository = e.Key, Path = $"elastic/asciidocalypse/{e.Key}/links.json", diff --git a/tests/authoring/Framework/TestCrossLinkResolver.fs b/tests/authoring/Framework/TestCrossLinkResolver.fs index 9e645e92a..4b9c9d6d4 100644 --- a/tests/authoring/Framework/TestCrossLinkResolver.fs +++ b/tests/authoring/Framework/TestCrossLinkResolver.fs @@ -10,10 +10,9 @@ open System.Collections.Frozen open System.Runtime.InteropServices open System.Threading.Tasks open System.Linq -open Elastic.Documentation +open Elastic.Documentation.Configuration.Builder open Elastic.Documentation.Links open Elastic.Markdown.Links.CrossLinks -open Elastic.Markdown.IO.Configuration type TestCrossLinkResolver (config: ConfigurationFile) = @@ -70,7 +69,7 @@ type TestCrossLinkResolver (config: ConfigurationFile) = this.DeclaredRepositories.Add("elasticsearch") |> ignore let indexEntries = - this.LinkReferences.ToDictionary(_.Key, fun (e : KeyValuePair) -> LinkIndexEntry( + this.LinkReferences.ToDictionary(_.Key, fun (e : KeyValuePair) -> LinkRegistryEntry( Repository = e.Key, Path = $"elastic/asciidocalypse/{e.Key}/links.json", Branch = "main", @@ -89,7 +88,7 @@ type TestCrossLinkResolver (config: ConfigurationFile) = member this.TryResolve(errorEmitter, warningEmitter, crossLinkUri, []resolvedUri : byref) = let indexEntries = - this.LinkReferences.ToDictionary(_.Key, fun (e : KeyValuePair) -> LinkIndexEntry( + this.LinkReferences.ToDictionary(_.Key, fun (e : KeyValuePair) -> LinkRegistryEntry( Repository = e.Key, Path = $"elastic/asciidocalypse/{e.Key}/links.json", Branch = "main",