Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 0 additions & 114 deletions Elastic.Documentation/Diagnostics/DiagnosticsChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -51,115 +49,3 @@ public interface IDiagnosticsOutput
{
void Write(Diagnostic diagnostic);
}

public class DiagnosticsCollector(IReadOnlyCollection<IDiagnosticsOutput> 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<string> OffendingFiles { get; } = [];

public ConcurrentDictionary<string, bool> InUseSubstitutionKeys { get; } = [];

public ConcurrentBag<string> 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<char> key) =>
_ = InUseSubstitutionKeys.TryAdd(key.ToString(), true);
}
127 changes: 127 additions & 0 deletions Elastic.Documentation/Diagnostics/DiagnosticsCollector.cs
Original file line number Diff line number Diff line change
@@ -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<IDiagnosticsOutput> 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<string> OffendingFiles { get; } = [];

public ConcurrentDictionary<string, bool> InUseSubstitutionKeys { get; } = [];

public ConcurrentBag<string> 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<char> key) =>
_ = InUseSubstitutionKeys.TryAdd(key.ToString(), true);
}
4 changes: 2 additions & 2 deletions Elastic.Documentation/Elastic.Documentation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNet.Glob" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging"/>
<PackageReference Include="NetEscapades.EnumGenerators"/>
<PackageReference Include="Vecc.YamlDotNet.Analyzers.StaticGenerator"/>
<PackageReference Include="YamlDotNet"/>
<PackageReference Include="SoftCircuits.IniFileParser" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions Elastic.Documentation/IDocumentationContext.cs
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>? mappedPages);
}

public record BypassHistoryMapper : IHistoryMapper
public record NoopLegacyUrlMapper : ILegacyUrlMapper
{
public LegacyPageMapping? MapLegacyUrl(IReadOnlyCollection<string>? mappedPages) => null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Text.Json.Serialization;
using Elastic.Documentation.Serialization;

namespace Elastic.Documentation;
namespace Elastic.Documentation.Links;

public record LinkMetadata
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@

namespace Elastic.Documentation.Links;

public record LinkIndex
public record LinkReferenceRegistry
{
[JsonPropertyName("repositories")] public required Dictionary<string, Dictionary<string, LinkIndexEntry>> Repositories { get; init; }
/// Map of branch to <see cref="LinkRegistryEntry"/>
[JsonPropertyName("repositories")]
public required Dictionary<string, Dictionary<string, LinkRegistryEntry>> 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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
7 changes: 7 additions & 0 deletions docs-builder.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand All @@ -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
Loading
Loading