Skip to content

Commit 4e3a416

Browse files
committed
Move registry to documentation set
1 parent a92e38e commit 4e3a416

File tree

16 files changed

+217
-179
lines changed

16 files changed

+217
-179
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PackageVersion Include="AWSSDK.Core" Version="4.0.0.2" />
1717
<PackageVersion Include="AWSSDK.SQS" Version="4.0.0.1" />
1818
<PackageVersion Include="AWSSDK.S3" Version="4.0.0.1" />
19+
<PackageVersion Include="Fake.Core.Context" Version="6.1.3" />
1920
<PackageVersion Include="FakeItEasy" Version="8.3.0" />
2021
<PackageVersion Include="Elastic.Ingest.Elasticsearch" Version="0.11.3" />
2122
<PackageVersion Include="Microsoft.OpenApi" Version="2.0.0-preview9" />

src/Elastic.Documentation.Configuration/BuildContext.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ public string? UrlPathPrefix
6565
init => _urlPathPrefix = value;
6666
}
6767

68-
public DiagramRegistry DiagramRegistry { get; }
69-
7068
public BuildContext(IDiagnosticsCollector collector, IFileSystem fileSystem, VersionsConfiguration versionsConfig)
7169
: this(collector, fileSystem, fileSystem, versionsConfig, null, null)
7270
{
@@ -108,6 +106,5 @@ public BuildContext(
108106
{
109107
Enabled = false
110108
};
111-
DiagramRegistry = new DiagramRegistry(writeFileSystem);
112109
}
113110
}

src/Elastic.Documentation.Configuration/Diagram/DiagramRegistry.cs

Lines changed: 66 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,49 @@
44

55
using System.Collections.Concurrent;
66
using System.IO.Abstractions;
7+
using Elastic.Documentation.Extensions;
78
using Microsoft.Extensions.Logging;
89

910
namespace Elastic.Documentation.Configuration.Diagram;
1011

1112
/// <summary>
1213
/// Information about a diagram that needs to be cached
1314
/// </summary>
14-
/// <param name="LocalSvgPath">Local SVG path relative to output directory</param>
15+
/// <param name="OutputFile">The intended cache output file location</param>
1516
/// <param name="EncodedUrl">Encoded Kroki URL for downloading</param>
16-
/// <param name="OutputDirectory">Full path to output directory</param>
17-
public record DiagramCacheInfo(string LocalSvgPath, string EncodedUrl, string OutputDirectory);
17+
public record DiagramCacheInfo(IFileInfo OutputFile, string EncodedUrl);
1818

19-
/// <summary>
2019
/// Registry to track active diagrams and manage cleanup of outdated cached files
21-
/// </summary>
22-
/// <param name="writeFileSystem">File system for write/delete operations during cleanup</param>
23-
public class DiagramRegistry(IFileSystem writeFileSystem) : IDisposable
20+
public class DiagramRegistry(ILoggerFactory logFactory, BuildContext context) : IDisposable
2421
{
25-
private readonly ConcurrentDictionary<string, bool> _activeDiagrams = new();
22+
private readonly ILogger<DiagramRegistry> _logger = logFactory.CreateLogger<DiagramRegistry>();
2623
private readonly ConcurrentDictionary<string, DiagramCacheInfo> _diagramsToCache = new();
27-
private readonly IFileSystem _writeFileSystem = writeFileSystem;
24+
private readonly IFileSystem _writeFileSystem = context.WriteFileSystem;
25+
private readonly IFileSystem _readFileSystem = context.ReadFileSystem;
2826
private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(30) };
2927

3028
/// <summary>
3129
/// Register a diagram for caching (collects info for later batch processing)
3230
/// </summary>
33-
/// <param name="localSvgPath">The local SVG path relative to output directory</param>
31+
/// <param name="localSvgPath">The local SVG path relative to the output directory</param>
3432
/// <param name="encodedUrl">The encoded Kroki URL for downloading</param>
35-
/// <param name="outputDirectory">The full path to output directory</param>
36-
public void RegisterDiagramForCaching(string localSvgPath, string encodedUrl, string outputDirectory)
33+
/// <param name="outputDirectory">The full path to the output directory</param>
34+
public void RegisterDiagramForCaching(IFileInfo outputFile, string encodedUrl)
3735
{
38-
if (string.IsNullOrEmpty(localSvgPath) || string.IsNullOrEmpty(encodedUrl))
36+
if (string.IsNullOrEmpty(encodedUrl))
3937
return;
4038

41-
_ = _activeDiagrams.TryAdd(localSvgPath, true);
42-
_ = _diagramsToCache.TryAdd(localSvgPath, new DiagramCacheInfo(localSvgPath, encodedUrl, outputDirectory));
43-
}
39+
if (!outputFile.IsSubPathOf(context.DocumentationOutputDirectory))
40+
return;
4441

45-
/// <summary>
46-
/// Clear all registered diagrams (called at start of build)
47-
/// </summary>
48-
public void Clear()
49-
{
50-
_activeDiagrams.Clear();
51-
_diagramsToCache.Clear();
42+
_ = _diagramsToCache.TryAdd(outputFile.FullName, new DiagramCacheInfo(outputFile, encodedUrl));
5243
}
5344

5445
/// <summary>
5546
/// Create cached diagram files by downloading from Kroki in parallel
5647
/// </summary>
57-
/// <param name="logger">Logger for reporting download activity</param>
58-
/// <param name="readFileSystem">File system for checking existing files</param>
5948
/// <returns>Number of diagrams downloaded</returns>
60-
public async Task<int> CreateDiagramCachedFiles(ILogger logger, IFileSystem readFileSystem)
49+
public async Task<int> CreateDiagramCachedFiles(Cancel ctx)
6150
{
6251
if (_diagramsToCache.IsEmpty)
6352
return 0;
@@ -67,87 +56,87 @@ public async Task<int> CreateDiagramCachedFiles(ILogger logger, IFileSystem read
6756
await Parallel.ForEachAsync(_diagramsToCache.Values, new ParallelOptions
6857
{
6958
MaxDegreeOfParallelism = Environment.ProcessorCount,
70-
CancellationToken = CancellationToken.None
59+
CancellationToken = ctx
7160
}, async (diagramInfo, ct) =>
7261
{
62+
var localPath = _readFileSystem.Path.GetRelativePath(context.DocumentationOutputDirectory.FullName, diagramInfo.OutputFile.FullName);
63+
7364
try
7465
{
75-
var fullPath = _writeFileSystem.Path.Combine(diagramInfo.OutputDirectory, diagramInfo.LocalSvgPath);
66+
if (!diagramInfo.OutputFile.IsSubPathOf(context.DocumentationOutputDirectory))
67+
return;
7668

77-
// Skip if file already exists
78-
if (readFileSystem.File.Exists(fullPath))
69+
// Skip if the file already exists
70+
if (_readFileSystem.File.Exists(diagramInfo.OutputFile.FullName))
7971
return;
8072

81-
// Create directory if needed
82-
var directory = _writeFileSystem.Path.GetDirectoryName(fullPath);
73+
// Create the directory if needed
74+
var directory = _writeFileSystem.Path.GetDirectoryName(diagramInfo.OutputFile.FullName);
8375
if (directory != null && !_writeFileSystem.Directory.Exists(directory))
84-
{
8576
_ = _writeFileSystem.Directory.CreateDirectory(directory);
86-
}
8777

8878
// Download SVG content
8979
var svgContent = await _httpClient.GetStringAsync(diagramInfo.EncodedUrl, ct);
9080

9181
// Validate SVG content
9282
if (string.IsNullOrWhiteSpace(svgContent) || !svgContent.Contains("<svg", StringComparison.OrdinalIgnoreCase))
9383
{
94-
logger.LogWarning("Invalid SVG content received for diagram {LocalPath}", diagramInfo.LocalSvgPath);
84+
_logger.LogWarning("Invalid SVG content received for diagram {LocalPath}", localPath);
9585
return;
9686
}
9787

98-
// Write atomically using temp file
99-
var tempPath = fullPath + ".tmp";
88+
// Write atomically using a temp file
89+
var tempPath = $"{diagramInfo.OutputFile.FullName}.tmp";
10090
await _writeFileSystem.File.WriteAllTextAsync(tempPath, svgContent, ct);
101-
_writeFileSystem.File.Move(tempPath, fullPath);
91+
_writeFileSystem.File.Move(tempPath, diagramInfo.OutputFile.FullName);
10292

10393
_ = Interlocked.Increment(ref downloadCount);
104-
logger.LogDebug("Downloaded diagram: {LocalPath}", diagramInfo.LocalSvgPath);
94+
_logger.LogDebug("Downloaded diagram: {LocalPath}", localPath);
10595
}
10696
catch (HttpRequestException ex)
10797
{
108-
logger.LogWarning("Failed to download diagram {LocalPath}: {Error}", diagramInfo.LocalSvgPath, ex.Message);
98+
_logger.LogWarning("Failed to download diagram {LocalPath}: {Error}", localPath, ex.Message);
10999
}
110100
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
111101
{
112-
logger.LogWarning("Timeout downloading diagram {LocalPath}", diagramInfo.LocalSvgPath);
102+
_logger.LogWarning("Timeout downloading diagram {LocalPath}", localPath);
113103
}
114104
catch (Exception ex)
115105
{
116-
logger.LogWarning("Unexpected error downloading diagram {LocalPath}: {Error}", diagramInfo.LocalSvgPath, ex.Message);
106+
_logger.LogWarning("Unexpected error downloading diagram {LocalPath}: {Error}", localPath, ex.Message);
117107
}
118108
});
119109

120110
if (downloadCount > 0)
121-
{
122-
logger.LogInformation("Downloaded {DownloadCount} diagram files from Kroki", downloadCount);
123-
}
111+
_logger.LogInformation("Downloaded {DownloadCount} diagram files from Kroki", downloadCount);
124112

125113
return downloadCount;
126114
}
127115

128116
/// <summary>
129117
/// Clean up unused diagram files from the cache directory
130118
/// </summary>
131-
/// <param name="outputDirectory">The output directory containing cached diagrams</param>
132119
/// <returns>Number of files cleaned up</returns>
133-
public int CleanupUnusedDiagrams(IDirectoryInfo outputDirectory)
120+
public int CleanupUnusedDiagrams()
134121
{
135-
var graphsDir = _writeFileSystem.Path.Combine(outputDirectory.FullName, "images", "generated-graphs");
136-
if (!_writeFileSystem.Directory.Exists(graphsDir))
122+
if (!_readFileSystem.Directory.Exists(context.DocumentationOutputDirectory.FullName))
123+
return 0;
124+
var folders = _writeFileSystem.Directory.GetDirectories(context.DocumentationOutputDirectory.FullName, "generated-graphs", SearchOption.AllDirectories);
125+
var existingFiles = folders
126+
.Select(f => (Folder: f, Files: _writeFileSystem.Directory.GetFiles(f, "*.svg", SearchOption.TopDirectoryOnly)))
127+
.ToArray();
128+
if (existingFiles.Length == 0)
137129
return 0;
138-
139-
var existingFiles = _writeFileSystem.Directory.GetFiles(graphsDir, "*.svg", SearchOption.AllDirectories);
140130
var cleanedCount = 0;
141131

142132
try
143133
{
144-
foreach (var file in existingFiles)
134+
foreach (var (folder, files) in existingFiles)
145135
{
146-
var relativePath = _writeFileSystem.Path.GetRelativePath(outputDirectory.FullName, file);
147-
var normalizedPath = relativePath.Replace(_writeFileSystem.Path.DirectorySeparatorChar, '/');
148-
149-
if (!_activeDiagrams.ContainsKey(normalizedPath))
136+
foreach (var file in files)
150137
{
138+
if (_diagramsToCache.ContainsKey(file))
139+
continue;
151140
try
152141
{
153142
_writeFileSystem.File.Delete(file);
@@ -158,10 +147,9 @@ public int CleanupUnusedDiagrams(IDirectoryInfo outputDirectory)
158147
// Silent failure - cleanup is opportunistic
159148
}
160149
}
150+
// Clean up empty directories
151+
CleanupEmptyDirectories(folder);
161152
}
162-
163-
// Clean up empty directories
164-
CleanupEmptyDirectories(graphsDir);
165153
}
166154
catch
167155
{
@@ -175,22 +163,26 @@ private void CleanupEmptyDirectories(string directory)
175163
{
176164
try
177165
{
178-
foreach (var subDir in _writeFileSystem.Directory.GetDirectories(directory))
179-
{
180-
CleanupEmptyDirectories(subDir);
166+
var folder = _writeFileSystem.DirectoryInfo.New(directory);
167+
if (!folder.IsSubPathOf(context.DocumentationOutputDirectory))
168+
return;
181169

182-
if (!_writeFileSystem.Directory.EnumerateFileSystemEntries(subDir).Any())
183-
{
184-
try
185-
{
186-
_writeFileSystem.Directory.Delete(subDir);
187-
}
188-
catch
189-
{
190-
// Silent failure - cleanup is opportunistic
191-
}
192-
}
193-
}
170+
if (folder.Name != "generated-graphs")
171+
return;
172+
173+
if (_writeFileSystem.Directory.EnumerateFileSystemEntries(folder.FullName).Any())
174+
return;
175+
176+
_writeFileSystem.Directory.Delete(folder.FullName);
177+
178+
var parentFolder = folder.Parent;
179+
if (parentFolder is null || parentFolder.Name != "images")
180+
return;
181+
182+
if (_writeFileSystem.Directory.EnumerateFileSystemEntries(parentFolder.FullName).Any())
183+
return;
184+
185+
_writeFileSystem.Directory.Delete(folder.FullName);
194186
}
195187
catch
196188
{

src/Elastic.Documentation/Extensions/IFileInfoExtensions.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,29 @@ public static string ReadToEnd(this IFileInfo fileInfo)
1818
using var reader = new StreamReader(stream);
1919
return reader.ReadToEnd();
2020
}
21+
22+
/// Validates <paramref name="file"/> is in a subdirectory of <paramref name="parentDirectory"/>
23+
public static bool IsSubPathOf(this IFileInfo file, IDirectoryInfo parentDirectory)
24+
{
25+
var parent = file.Directory;
26+
return parent is not null && parent.IsSubPathOf(parentDirectory);
27+
}
28+
}
29+
30+
public static class IDirectoryInfoExtensions
31+
{
32+
/// Validates <paramref name="directory"/> is subdirectory of <paramref name="parentDirectory"/>
33+
public static bool IsSubPathOf(this IDirectoryInfo directory, IDirectoryInfo parentDirectory)
34+
{
35+
var parent = directory;
36+
do
37+
{
38+
if (parent.FullName == parentDirectory.FullName)
39+
return true;
40+
parent = parent.Parent;
41+
}
42+
while (parent != null);
43+
44+
return false;
45+
}
2146
}

src/Elastic.Markdown/DocumentationGenerator.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,6 @@ public async Task ResolveDirectoryTree(Cancel ctx)
108108

109109
public async Task<GenerationResult> GenerateAll(Cancel ctx)
110110
{
111-
// Clear diagram registry for fresh tracking
112-
DocumentationSet.Context.DiagramRegistry.Clear();
113-
114111
var result = new GenerationResult();
115112

116113
var generationState = Context.SkipDocumentationState ? null : GetPreviousGenerationState();
@@ -147,7 +144,7 @@ public async Task<GenerationResult> GenerateAll(Cancel ctx)
147144
_logger.LogInformation($"Generating links.json");
148145
var linkReference = await GenerateLinkReference(ctx);
149146

150-
await CreateDiagramCachedFiles();
147+
await CreateDiagramCachedFiles(ctx);
151148
CleanupUnusedDiagrams();
152149

153150
// ReSharper disable once WithExpressionModifiesAllMembers
@@ -160,9 +157,9 @@ public async Task<GenerationResult> GenerateAll(Cancel ctx)
160157
/// <summary>
161158
/// Downloads diagram files in parallel from Kroki
162159
/// </summary>
163-
public async Task CreateDiagramCachedFiles()
160+
public async Task CreateDiagramCachedFiles(Cancel ctx)
164161
{
165-
var downloadedCount = await DocumentationSet.Context.DiagramRegistry.CreateDiagramCachedFiles(_logger, DocumentationSet.Context.ReadFileSystem);
162+
var downloadedCount = await DocumentationSet.DiagramRegistry.CreateDiagramCachedFiles(ctx);
166163
if (downloadedCount > 0)
167164
{
168165
_logger.LogInformation("Downloaded {DownloadedCount} diagram files from Kroki", downloadedCount);
@@ -174,11 +171,9 @@ public async Task CreateDiagramCachedFiles()
174171
/// </summary>
175172
public void CleanupUnusedDiagrams()
176173
{
177-
var cleanedCount = DocumentationSet.Context.DiagramRegistry.CleanupUnusedDiagrams(DocumentationSet.OutputDirectory);
174+
var cleanedCount = DocumentationSet.DiagramRegistry.CleanupUnusedDiagrams();
178175
if (cleanedCount > 0)
179-
{
180176
_logger.LogInformation("Cleaned up {CleanedCount} unused diagram files", cleanedCount);
181-
}
182177
}
183178

184179
private async Task ProcessDocumentationFiles(HashSet<string> offendingFiles, DateTimeOffset outputSeenChanges, Cancel ctx)

src/Elastic.Markdown/Extensions/DetectionRules/DetectionRuleFile.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ protected override Task<MarkdownDocument> GetMinimalParseDocumentAsync(Cancel ct
2929
{
3030
Title = "Prebuilt detection rules reference";
3131
var markdown = GetMarkdown();
32-
var document = MarkdownParser.MinimalParseStringAsync(markdown, SourceFile, null);
32+
var document = MarkdownParser.MinimalParseString(markdown, SourceFile, null);
3333
return Task.FromResult(document);
3434
}
3535

3636
protected override Task<MarkdownDocument> GetParseDocumentAsync(Cancel ctx)
3737
{
3838
var markdown = GetMarkdown();
39-
var document = MarkdownParser.ParseStringAsync(markdown, SourceFile, null);
39+
var document = MarkdownParser.ParseString(markdown, SourceFile, null);
4040
return Task.FromResult(document);
4141
}
4242

@@ -127,14 +127,14 @@ protected override Task<MarkdownDocument> GetMinimalParseDocumentAsync(Cancel ct
127127
{
128128
Title = Rule?.Name;
129129
var markdown = GetMarkdown();
130-
var document = MarkdownParser.MinimalParseStringAsync(markdown, RuleSourceMarkdownPath, null);
130+
var document = MarkdownParser.MinimalParseString(markdown, RuleSourceMarkdownPath, null);
131131
return Task.FromResult(document);
132132
}
133133

134134
protected override Task<MarkdownDocument> GetParseDocumentAsync(Cancel ctx)
135135
{
136136
var markdown = GetMarkdown();
137-
var document = MarkdownParser.ParseStringAsync(markdown, RuleSourceMarkdownPath, null);
137+
var document = MarkdownParser.ParseString(markdown, RuleSourceMarkdownPath, null);
138138
return Task.FromResult(document);
139139
}
140140

src/Elastic.Markdown/HtmlWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class HtmlWriter(
4343
public string Render(string markdown, IFileInfo? source)
4444
{
4545
source ??= DocumentationSet.Context.ConfigurationPath;
46-
var parsed = DocumentationSet.MarkdownParser.ParseStringAsync(markdown, source, null);
46+
var parsed = DocumentationSet.MarkdownParser.ParseString(markdown, source, null);
4747
return MarkdownFile.CreateHtml(parsed);
4848
}
4949

0 commit comments

Comments
 (0)