Skip to content

Commit 307f253

Browse files
Mpdreamzreakaleek
andauthored
feature/assembler global toc (#770)
* Add global navigation and improve diagnostic handling This commit introduces a GlobalNavigation feature, parsing the new `navigation.yml` file to handle global navigation references. It also replaces `BuildContext` with `DiagnosticsCollector` for diagnostics, ensuring clearer separation of concerns. Additionally, it includes new tests and updates project files for better testing and navigation handling. * Use navigation.yml to control url path prefixes for resolving * path_prefix is mandatory except for top level docs-content references * stage commit * stage commit * File copy now in a decent place * temporary home for in flux paths * fix failing test * remove not referenced test project * Restrict docset.yml configs that define toc.yml sections to ONLY link to sub toc.yml files (#767) * Restrict docset.yml configs that define toc.yml sections to ONLY link to sub toc.yml files * relax check for narrative docs too, plays by different rules * dotnet format * reference Project directly * Fix layout and horizontal scrollable tables (#772) * Fix layout and horizontal scrollable tables mend * Use spacing var * Add custom scrollbar * tweaks * Add table of contents scope management and refactor diagnostics (#774) * Add table of contents scope management and refactor diagnostics Introduced `ITableOfContentsScope` to manage scope boundaries, improving link validation and table of contents processing. Updated diagnostics to emit hints for images outside the ToC scope and refactored functions for better reusability. Adjusted related tests and internal structures accordingly. * Add TODO to make this an error * post merge build fixes * good enough state --------- Co-authored-by: Jan Calanog <[email protected]>
1 parent de857f7 commit 307f253

40 files changed

+1141
-129
lines changed

docs-builder.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "update-reference-index", "u
7373
EndProject
7474
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs-lambda-index-publisher", "src\docs-lambda-index-publisher\docs-lambda-index-publisher.csproj", "{C559D52D-100B-4B2B-BE87-2344D835761D}"
7575
EndProject
76+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs-assembler.Tests", "tests\docs-assembler.Tests\src\docs-assembler.Tests\docs-assembler.Tests.csproj", "{CDC0ECF4-6597-4FBA-8D25-5C244F0877E3}"
77+
EndProject
7678
Global
7779
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7880
Debug|Any CPU = Debug|Any CPU
@@ -122,6 +124,10 @@ Global
122124
{C559D52D-100B-4B2B-BE87-2344D835761D}.Debug|Any CPU.Build.0 = Debug|Any CPU
123125
{C559D52D-100B-4B2B-BE87-2344D835761D}.Release|Any CPU.ActiveCfg = Release|Any CPU
124126
{C559D52D-100B-4B2B-BE87-2344D835761D}.Release|Any CPU.Build.0 = Release|Any CPU
127+
{CDC0ECF4-6597-4FBA-8D25-5C244F0877E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
128+
{CDC0ECF4-6597-4FBA-8D25-5C244F0877E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
129+
{CDC0ECF4-6597-4FBA-8D25-5C244F0877E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
130+
{CDC0ECF4-6597-4FBA-8D25-5C244F0877E3}.Release|Any CPU.Build.0 = Release|Any CPU
125131
EndGlobalSection
126132
GlobalSection(NestedProjects) = preSolution
127133
{4D198E25-C211-41DC-9E84-B15E89BD7048} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
@@ -138,5 +144,6 @@ Global
138144
{6554F917-73CE-4B3D-9101-F28EAA762C6B} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
139145
{9FEC15F6-13F8-40B1-A66A-EB054E49E680} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
140146
{C559D52D-100B-4B2B-BE87-2344D835761D} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
147+
{CDC0ECF4-6597-4FBA-8D25-5C244F0877E3} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
141148
EndGlobalSection
142149
EndGlobal

src/Elastic.Documentation.Tooling/Diagnostics/Console/ConsoleDiagnosticsCollector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ protected override void HandleItem(Diagnostic diagnostic)
2424
_errors.Add(diagnostic);
2525
else if (diagnostic.Severity == Severity.Warning)
2626
_warnings.Add(diagnostic);
27-
else
27+
else if (!NoHints)
2828
_hints.Add(diagnostic);
2929
}
3030

src/Elastic.Markdown/BuildContext.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public record BuildContext
2929

3030
public bool Force { get; init; }
3131

32+
public bool SkipMetadata { get; init; }
33+
3234
// This property is used to determine if the site should be indexed by search engines
3335
public bool AllowIndexing { get; init; }
3436

@@ -39,15 +41,6 @@ public string? UrlPathPrefix
3941
init => _urlPathPrefix = value;
4042
}
4143

42-
private readonly string? _staticUrlPathPrefix;
43-
public string? StaticUrlPathPrefix
44-
{
45-
get => !string.IsNullOrWhiteSpace(_staticUrlPathPrefix)
46-
? $"/{_staticUrlPathPrefix.Trim('/')}"
47-
: UrlPathPrefix;
48-
init => _staticUrlPathPrefix = value;
49-
}
50-
5144
public BuildContext(IFileSystem fileSystem)
5245
: this(new DiagnosticsCollector([]), fileSystem, fileSystem, null, null) { }
5346

src/Elastic.Markdown/CrossLinks/CrossLinkResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public interface ICrossLinkResolver
4545
public class CrossLinkResolver(CrossLinkFetcher fetcher, IUriEnvironmentResolver? uriResolver = null) : ICrossLinkResolver
4646
{
4747
private FetchedCrossLinks _crossLinks = FetchedCrossLinks.Empty;
48-
private readonly IUriEnvironmentResolver _uriResolver = uriResolver ?? new PreviewEnvironmentUriResolver();
48+
private readonly IUriEnvironmentResolver _uriResolver = uriResolver ?? new IsolatedBuildEnvironmentUriResolver();
4949

5050
public async Task<FetchedCrossLinks> FetchLinks()
5151
{

src/Elastic.Markdown/CrossLinks/IUriEnvironmentResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public interface IUriEnvironmentResolver
99
Uri Resolve(Uri crossLinkUri, string path);
1010
}
1111

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

src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ public class DiagnosticsCollector(IReadOnlyCollection<IDiagnosticsOutput> output
8585

8686
public HashSet<string> OffendingFiles { get; } = [];
8787

88-
public HashSet<string> InUseSubstitutionKeys { get; } = [];
88+
public ConcurrentDictionary<string, bool> InUseSubstitutionKeys { get; } = [];
8989

9090
public ConcurrentBag<string> CrossLinks { get; } = [];
9191

92+
public bool NoHints { get; init; }
93+
9294
public Task StartAsync(Cancel cancellationToken)
9395
{
9496
if (_started is not null)
@@ -117,6 +119,8 @@ void Drain()
117119
{
118120
while (Channel.Reader.TryRead(out var item))
119121
{
122+
if (item.Severity == Severity.Hint && NoHints)
123+
continue;
120124
IncrementSeverityCount(item);
121125
HandleItem(item);
122126
_ = OffendingFiles.Add(item.File);
@@ -132,7 +136,7 @@ private void IncrementSeverityCount(Diagnostic item)
132136
_ = Interlocked.Increment(ref _errors);
133137
else if (item.Severity == Severity.Warning)
134138
_ = Interlocked.Increment(ref _warnings);
135-
else if (item.Severity == Severity.Hint)
139+
else if (item.Severity == Severity.Hint && !NoHints)
136140
_ = Interlocked.Increment(ref _hints);
137141
}
138142

@@ -175,5 +179,5 @@ public async ValueTask DisposeAsync()
175179
}
176180

177181
public void CollectUsedSubstitutionKey(ReadOnlySpan<char> key) =>
178-
_ = InUseSubstitutionKeys.Add(key.ToString());
182+
_ = InUseSubstitutionKeys.TryAdd(key.ToString(), true);
179183
}

src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,28 @@ public static void EmitWarning(this BuildContext context, IFileInfo file, string
9898
context.Collector.Channel.Write(d);
9999
}
100100

101+
public static void EmitError(this DiagnosticsCollector collector, IFileInfo file, string message, Exception? e = null)
102+
{
103+
var d = new Diagnostic
104+
{
105+
Severity = Severity.Error,
106+
File = file.FullName,
107+
Message = message + (e != null ? Environment.NewLine + e : string.Empty),
108+
};
109+
collector.Channel.Write(d);
110+
}
111+
112+
public static void EmitWarning(this DiagnosticsCollector collector, IFileInfo file, string message)
113+
{
114+
var d = new Diagnostic
115+
{
116+
Severity = Severity.Warning,
117+
File = file.FullName,
118+
Message = message,
119+
};
120+
collector.Channel.Write(d);
121+
}
122+
101123
public static void EmitError(this IBlockExtension block, string message, Exception? e = null)
102124
{
103125
if (block.SkipValidation)

src/Elastic.Markdown/DocumentationGenerator.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@ public interface IConversionCollector
2020
void Collect(MarkdownFile file, MarkdownDocument document, string html);
2121
}
2222

23+
public interface IDocumentationFileOutputProvider
24+
{
25+
IFileInfo? OutputFile(DocumentationSet documentationSet, IFileInfo defaultOutputFile, string relativePath);
26+
}
27+
2328
public class DocumentationGenerator
2429
{
30+
private readonly IDocumentationFileOutputProvider? _documentationFileOutputProvider;
2531
private readonly ILogger _logger;
2632
private readonly IFileSystem _writeFileSystem;
2733
private readonly IDocumentationFileExporter _documentationFileExporter;
@@ -34,10 +40,12 @@ public class DocumentationGenerator
3440
public DocumentationGenerator(
3541
DocumentationSet docSet,
3642
ILoggerFactory logger,
43+
IDocumentationFileOutputProvider? documentationFileOutputProvider = null,
3744
IDocumentationFileExporter? documentationExporter = null,
3845
IConversionCollector? conversionCollector = null
3946
)
4047
{
48+
_documentationFileOutputProvider = documentationFileOutputProvider;
4149
_writeFileSystem = docSet.Build.WriteFileSystem;
4250
_logger = logger.CreateLogger(nameof(DocumentationGenerator));
4351

@@ -74,7 +82,7 @@ public async Task ResolveDirectoryTree(Cancel ctx)
7482
public async Task GenerateAll(Cancel ctx)
7583
{
7684
var generationState = GetPreviousGenerationState();
77-
if (Context.Force || generationState == null)
85+
if (!Context.SkipMetadata && (Context.Force || generationState == null))
7886
DocumentationSet.ClearOutputDirectory();
7987

8088
if (CompilationNotNeeded(generationState, out var offendingFiles, out var outputSeenChanges))
@@ -91,17 +99,22 @@ public async Task GenerateAll(Cancel ctx)
9199

92100
await ExtractEmbeddedStaticResources(ctx);
93101

94-
_logger.LogInformation($"Completing diagnostics channel");
95-
Context.Collector.Channel.TryComplete();
102+
if (Context.SkipMetadata)
103+
return;
96104

97105
_logger.LogInformation($"Generating documentation compilation state");
98106
await GenerateDocumentationState(ctx);
99107

100108
_logger.LogInformation($"Generating links.json");
101109
await GenerateLinkReference(ctx);
110+
}
102111

112+
public async Task StopDiagnosticCollection(Cancel ctx)
113+
{
103114
_logger.LogInformation($"Completing diagnostics channel");
115+
Context.Collector.Channel.TryComplete();
104116

117+
_logger.LogInformation($"Stopping diagnostics collector");
105118
await Context.Collector.StopAsync(ctx);
106119

107120
_logger.LogInformation($"Completed diagnostics channel");
@@ -140,7 +153,8 @@ await Parallel.ForEachAsync(DocumentationSet.Files, ctx, async (file, token) =>
140153
private void HintUnusedSubstitutionKeys()
141154
{
142155
var definedKeys = new HashSet<string>(Context.Configuration.Substitutions.Keys.ToArray());
143-
var keysNotInUse = definedKeys.Except(Context.Collector.InUseSubstitutionKeys).ToArray();
156+
var inUse = new HashSet<string>(Context.Collector.InUseSubstitutionKeys.Keys);
157+
var keysNotInUse = definedKeys.Except(inUse).ToArray();
144158
// If we have less than 20 unused keys emit them separately
145159
// Otherwise emit one hint with all of them for brevity
146160
if (keysNotInUse.Length >= 20)
@@ -170,6 +184,8 @@ private async Task ExtractEmbeddedStaticResources(Cancel ctx)
170184
var path = a.Replace("Elastic.Markdown.", "").Replace("_static.", $"_static{Path.DirectorySeparatorChar}");
171185

172186
var outputFile = OutputFile(path);
187+
if (outputFile is null)
188+
continue;
173189
await _documentationFileExporter.CopyEmbeddedResource(outputFile, resourceStream, ctx);
174190
_logger.LogDebug("Copied static embedded resource {Path}", path);
175191
}
@@ -186,14 +202,21 @@ private async Task ProcessFile(HashSet<string> offendingFiles, DocumentationFile
186202
}
187203

188204
_logger.LogTrace("--> {FileFullPath}", file.SourceFile.FullName);
205+
//TODO send file to OutputFile() so we can validate its scope is defined in navigation.yml
189206
var outputFile = OutputFile(file.RelativePath);
190-
await _documentationFileExporter.ProcessFile(file, outputFile, token);
207+
if (outputFile is not null)
208+
await _documentationFileExporter.ProcessFile(file, outputFile, token);
191209
}
192210

193-
private IFileInfo OutputFile(string relativePath)
211+
private IFileInfo? OutputFile(string relativePath)
194212
{
195213
var outputFile = _writeFileSystem.FileInfo.New(Path.Combine(DocumentationSet.OutputDirectory.FullName, relativePath));
196-
return outputFile;
214+
if (relativePath.StartsWith("_static"))
215+
return outputFile;
216+
217+
return _documentationFileOutputProvider is not null
218+
? _documentationFileOutputProvider.OutputFile(DocumentationSet, outputFile, relativePath)
219+
: outputFile;
197220
}
198221

199222
private bool CompilationNotNeeded(GenerationState? generationState, out HashSet<string> offendingFiles,

src/Elastic.Markdown/IO/Configuration/ConfigurationFile.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public ConfigurationFile(BuildContext context)
7474
var redirectFile = new RedirectFile(redirectFileInfo, _context);
7575
Redirects = redirectFile.Redirects;
7676

77-
var reader = new YamlStreamReader(sourceFile, _context);
77+
var reader = new YamlStreamReader(sourceFile, _context.Collector);
7878
try
7979
{
8080
foreach (var entry in reader.Read())
@@ -120,7 +120,7 @@ public ConfigurationFile(BuildContext context)
120120
}
121121

122122
//we read it twice to ensure we read 'toc' last
123-
reader = new YamlStreamReader(sourceFile, _context);
123+
reader = new YamlStreamReader(sourceFile, _context.Collector);
124124
foreach (var entry in reader.Read())
125125
{
126126
switch (entry.Key)

src/Elastic.Markdown/IO/Configuration/RedirectFile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public RedirectFile(IFileInfo source, BuildContext context)
2222
if (!source.Exists)
2323
return;
2424

25-
var reader = new YamlStreamReader(Source, Context);
25+
var reader = new YamlStreamReader(Source, Context.Collector);
2626
try
2727
{
2828
foreach (var entry in reader.Read())

src/Elastic.Markdown/IO/Configuration/TableOfContentsConfiguration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ public IReadOnlyCollection<ITocItem> ReadChildren(YamlStreamReader reader, KeyVa
239239
if (!found)
240240
return null;
241241

242-
var tocYamlReader = new YamlStreamReader(source, _context);
242+
var tocYamlReader = new YamlStreamReader(source, _context.Collector);
243243
foreach (var kv in tocYamlReader.Read())
244244
{
245245
switch (kv.Key)

src/Elastic.Markdown/IO/Configuration/YamlStreamReader.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ public record YamlToplevelKey
1616
public required KeyValuePair<YamlNode, YamlNode> Entry { get; init; }
1717
}
1818

19-
public class YamlStreamReader(IFileInfo source, BuildContext context)
19+
public class YamlStreamReader(IFileInfo source, DiagnosticsCollector collector)
2020
{
21-
public IFileInfo Source { get; init; } = source;
22-
public BuildContext Context { get; init; } = context;
21+
private IFileInfo Source { get; init; } = source;
22+
private DiagnosticsCollector Collector { get; init; } = collector;
2323

2424
public IEnumerable<YamlToplevelKey> Read()
2525
{
@@ -30,7 +30,7 @@ public IEnumerable<YamlToplevelKey> Read()
3030

3131
if (yaml.Documents.Count == 0)
3232
{
33-
Context.EmitWarning(Source, "empty redirect file");
33+
Collector.EmitWarning(Source, "empty redirect file");
3434
yield break;
3535
}
3636
// Examine the stream
@@ -171,7 +171,7 @@ public void EmitWarning(string message, YamlNode? node) =>
171171
EmitWarning(message, node?.Start, node?.End, (node as YamlScalarNode)?.Value?.Length);
172172

173173
public void EmitError(string message, Exception e) =>
174-
Context.Collector.EmitError(Source.FullName, message, e);
174+
Collector.EmitError(Source.FullName, message, e);
175175

176176
private void EmitError(string message, Mark? start = null, Mark? end = null, int? length = null)
177177
{
@@ -185,7 +185,7 @@ private void EmitError(string message, Mark? start = null, Mark? end = null, int
185185
Column = start.HasValue ? (int)start.Value.Column : null,
186186
Length = length
187187
};
188-
Context.Collector.Channel.Write(d);
188+
Collector.Channel.Write(d);
189189
}
190190
public void EmitWarning(string message, Mark? start = null, Mark? end = null, int? length = null)
191191
{
@@ -199,6 +199,6 @@ public void EmitWarning(string message, Mark? start = null, Mark? end = null, in
199199
Column = start.HasValue ? (int)start.Value.Column : null,
200200
Length = length
201201
};
202-
Context.Collector.Channel.Write(d);
202+
Collector.Channel.Write(d);
203203
}
204204
}

src/Elastic.Markdown/IO/Discovery/GitCheckoutInformation.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,25 @@ public static GitCheckoutInformation Create(IDirectoryInfo source, IFileSystem f
4646
}
4747

4848
var fakeRef = Guid.NewGuid().ToString()[..16];
49-
var gitConfig = Git(source, ".git/config");
49+
var gitConfig = Git(source, Path.Combine(".git", "config"));
5050
if (!gitConfig.Exists)
5151
{
52-
logger?.LogInformation("Git checkout information not available.");
53-
return Unavailable;
52+
gitConfig = Git(source, Path.Combine("..", ".git", "config"));
53+
if (!gitConfig.Exists)
54+
{
55+
logger?.LogInformation("Git checkout information not available.");
56+
return Unavailable;
57+
}
5458
}
5559

56-
var head = Read(source, ".git/HEAD") ?? fakeRef;
60+
var head = Read(source, Path.Combine(".git", "HEAD")) ?? fakeRef;
5761
var gitRef = head;
5862
var branch = head.Replace("refs/heads/", string.Empty);
5963
//not detached HEAD
6064
if (head.StartsWith("ref:"))
6165
{
6266
head = head.Replace("ref: ", string.Empty);
63-
gitRef = Read(source, ".git/" + head) ?? fakeRef;
67+
gitRef = Read(source, Path.Combine(".git", head)) ?? fakeRef;
6468
branch = branch.Replace("ref: ", string.Empty);
6569
}
6670
else

0 commit comments

Comments
 (0)