Skip to content

Commit

Permalink
Add infrastructure and diagnostics for unsupported directives
Browse files Browse the repository at this point in the history
This PR also ensures tests log output correctly.
  • Loading branch information
Mpdreamz committed Nov 12, 2024
1 parent f880e6d commit 03dc186
Show file tree
Hide file tree
Showing 39 changed files with 338 additions and 92 deletions.
14 changes: 12 additions & 2 deletions src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public void TryComplete(Exception? exception = null)
_ctxSource.Cancel();
}

public ValueTask<bool> WaitToWrite() => _channel.Writer.WaitToWriteAsync();

public void Write(Diagnostic diagnostic)
{
var written = _channel.Writer.TryWrite(diagnostic);
Expand Down Expand Up @@ -84,10 +86,18 @@ public class DiagnosticsCollector(ILoggerFactory loggerFactory, IReadOnlyCollect

public async Task StartAsync(Cancel ctx)
{
await Channel.WaitToWrite();
while (!Channel.CancellationToken.IsCancellationRequested)
{
while (await Channel.Reader.WaitToReadAsync(Channel.CancellationToken))
Drain();
try
{
while (await Channel.Reader.WaitToReadAsync(Channel.CancellationToken))
Drain();
}
catch
{
//ignore
}
}
Drain();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public static class ProcessorDiagnosticExtensions
{
public static void EmitError(this InlineProcessor processor, int line, int column, int length, string message)
{
var context = processor.GetContext();
if (context.SkipValidation) return;
var d = new Diagnostic
{
Severity = Severity.Error,
Expand All @@ -21,6 +23,53 @@ public static void EmitError(this InlineProcessor processor, int line, int colum
Message = message,
Length = length
};
processor.GetBuildContext().Collector.Channel.Write(d);
context.Build.Collector.Channel.Write(d);
}


public static void EmitWarning(this BlockProcessor processor, int line, int column, int length, string message)
{
var context = processor.GetContext();
if (context.SkipValidation) return;
var d = new Diagnostic
{
Severity = Severity.Warning,
File = processor.GetContext().Path.FullName,
Column = column,
Line = line,
Message = message,
Length = length
};
context.Build.Collector.Channel.Write(d);
}

public static void EmitError(this ParserContext context, int line, int column, int length, string message)
{
if (context.SkipValidation) return;
var d = new Diagnostic
{
Severity = Severity.Error,
File = context.Path.FullName,
Column = column,
Line = line,
Message = message,
Length = length
};
context.Build.Collector.Channel.Write(d);
}

public static void EmitWarning(this ParserContext context, int line, int column, int length, string message)
{
if (context.SkipValidation) return;
var d = new Diagnostic
{
Severity = Severity.Warning,
File = context.Path.FullName,
Column = column,
Line = line,
Message = message,
Length = length
};
context.Build.Collector.Channel.Write(d);
}
}
6 changes: 3 additions & 3 deletions src/Elastic.Markdown/Myst/Directives/AdmonitionBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public string Title
}
}

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
Classes = Properties.GetValueOrDefault("class");
CrossReferenceName = Properties.GetValueOrDefault("name");
Expand All @@ -38,9 +38,9 @@ public class DropdownBlock(DirectiveBlockParser parser, Dictionary<string, strin
: AdmonitionBlock(parser, "admonition", properties)
{
// ReSharper disable once RedundantOverriddenMember
public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
base.FinalizeAndValidate();
base.FinalizeAndValidate(context);
Classes = $"dropdown {Classes}";
}
}
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/CardBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class CardBlock(DirectiveBlockParser parser, Dictionary<string, string> p

public string? Footer { get; set; }

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
Title = Arguments;
Link = Properties.GetValueOrDefault("link");
Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/CodeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public string Language
}
}

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
Caption = Properties.GetValueOrDefault("caption");
CrossReferenceName = Properties.GetValueOrDefault("name");
Expand Down
3 changes: 2 additions & 1 deletion src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public abstract class DirectiveBlock(DirectiveBlockParser parser, Dictionary<str
/// <summary>
/// Allows blocks to finalize setting properties once fully parsed
/// </summary>
public abstract void FinalizeAndValidate();
/// <param name="context"></param>
public abstract void FinalizeAndValidate(ParserContext context);

protected void ParseBool(string key, Action<bool> setter)
{
Expand Down
21 changes: 20 additions & 1 deletion src/Elastic.Markdown/Myst/Directives/DirectiveBlockParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.

using System.Collections.Frozen;
using Markdig.Parsers;
using Markdig.Syntax;
using static System.StringSplitOptions;
Expand Down Expand Up @@ -37,6 +38,20 @@ public DirectiveBlockParser()

private readonly string[] _codeBlocks = [ "code", "code-block", "sourcecode"];

private readonly FrozenDictionary<string, int> _unsupportedBlocks = new Dictionary<string, int>
{
{ "bibliography", 5 },
{ "blockquote", 6 },
{ "csv-table", 9 },
{ "iframe", 14 },
{ "list-table", 17 },
{ "myst", 22 },
{ "topic", 24 },
{ "exercise", 30 },
{ "solution", 31 },
{ "toctree", 32 },
}.ToFrozenDictionary();

protected override DirectiveBlock CreateFencedBlock(BlockProcessor processor)
{
_admonitionData = new Dictionary<string, string>();
Expand Down Expand Up @@ -108,14 +123,18 @@ protected override DirectiveBlock CreateFencedBlock(BlockProcessor processor)
if (info.IndexOf($"{{{code}}}") > 0)
return new CodeBlock(this, code, _admonitionData);
}
// TODO alternate lookup .NET 9
var directive = info.ToString().Trim(['{', '}', '`']);
if (_unsupportedBlocks.TryGetValue(directive, out var issueId))
return new UnsupportedDirectiveBlock(this, directive, _admonitionData, issueId);

return new UnknownDirectiveBlock(this, info.ToString(), _admonitionData);
}

public override bool Close(BlockProcessor processor, Block block)
{
if (block is DirectiveBlock directiveBlock)
directiveBlock.FinalizeAndValidate();
directiveBlock.FinalizeAndValidate(processor.GetContext());


if (block is not TocTreeBlock toc)
Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/ImageBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class ImageBlock(DirectiveBlockParser parser, Dictionary<string, string>

public string ImageUrl { get; private set; } = default!;

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
ImageUrl = Arguments ?? string.Empty; //todo validate
Classes = Properties.GetValueOrDefault("class");
Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class IncludeBlock(DirectiveBlockParser parser, Dictionary<string, string

//TODO add all options from
//https://mystmd.org/guide/directives#directive-include
public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
var includePath = Arguments; //todo validate
Literal |= bool.TryParse(Properties.GetValueOrDefault("literal"), out var b) && b;
Expand Down
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/MermaidBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Elastic.Markdown.Myst.Directives;
public class MermaidBlock(DirectiveBlockParser parser, Dictionary<string, string> properties)
: DirectiveBlock(parser, properties)
{
public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
}
}
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/SideBarBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Elastic.Markdown.Myst.Directives;
public class SideBarBlock(DirectiveBlockParser parser, Dictionary<string, string> properties)
: DirectiveBlock(parser, properties)
{
public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
}
}
18 changes: 4 additions & 14 deletions src/Elastic.Markdown/Myst/Directives/TabSetBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class TabSetBlock(DirectiveBlockParser parser, Dictionary<string, string>
: DirectiveBlock(parser, properties)
{
public int Index { get; set; }
public override void FinalizeAndValidate() => Index = FindIndex();
public override void FinalizeAndValidate(ParserContext context) => Index = FindIndex();

private int _index = -1;
public int FindIndex()
Expand All @@ -28,7 +28,7 @@ public class TabItemBlock(DirectiveBlockParser parser, Dictionary<string, string
public int Index { get; set; }
public int TabSetIndex { get; set; }

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
Title = Arguments ?? "Unnamed Tab";
Index = Parent!.IndexOf(this);
Expand Down Expand Up @@ -80,7 +80,7 @@ public class GridBlock(DirectiveBlockParser parser, Dictionary<string, string> p
public string? ClassRow { get; set; }


public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
//todo we always assume 4 integers
if (!string.IsNullOrEmpty(Arguments))
Expand Down Expand Up @@ -124,17 +124,7 @@ private void ParseData(string data, Action<int, int, int, int> setter, bool allo
public class GridItemCardBlock(DirectiveBlockParser parser, Dictionary<string, string> properties)
: DirectiveBlock(parser, properties)
{
public override void FinalizeAndValidate()
{
}
}

public class UnknownDirectiveBlock(DirectiveBlockParser parser, string directive, Dictionary<string, string> properties)
: DirectiveBlock(parser, properties)
{
public string Directive => directive;

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
}
}
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/TocTreeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class TocTreeBlock(DirectiveBlockParser parser, Dictionary<string, string
{
public OrderedList<TocTreeLink> Links { get; } = new();

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
}
}
15 changes: 15 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/UnknownDirectiveBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

namespace Elastic.Markdown.Myst.Directives;

public class UnknownDirectiveBlock(DirectiveBlockParser parser, string directive, Dictionary<string, string> properties)
: DirectiveBlock(parser, properties)
{
public string Directive => directive;

public override void FinalizeAndValidate(ParserContext context)
{
}
}
18 changes: 18 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/UnsupportedDirectiveBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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.Markdown.Diagnostics;

namespace Elastic.Markdown.Myst.Directives;

public class UnsupportedDirectiveBlock(DirectiveBlockParser parser, string directive, Dictionary<string, string> properties, int issueId)
: DirectiveBlock(parser, properties)
{
public string Directive => directive;

public string IssueUrl => $"https://github.com/elastic/docs-builder/issues/{issueId}";

public override void FinalizeAndValidate(ParserContext context) =>
context.EmitWarning(line:1, column:1, length:2, message: $"Directive block '{directive}' is unsupported. See {IssueUrl} for more information.");
}
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/VersionBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public string Title
}
}

public override void FinalizeAndValidate()
public override void FinalizeAndValidate(ParserContext context)
{
}
}
19 changes: 13 additions & 6 deletions src/Elastic.Markdown/Myst/MarkdownParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,37 @@ public class MarkdownParser(IDirectoryInfo sourcePath, BuildContext context)
// TODO only scan for yaml front matter and toc information
public Task<MarkdownDocument> QuickParseAsync(IFileInfo path, Cancel ctx)
{
var context = new ParserContext(this, path, null, Context);
return ParseAsync(path, context, ctx);
var context = new ParserContext(this, path, null, Context)
{
SkipValidation = true
};
return ParseAsync(path, context, Pipeline, ctx);
}

public Task<MarkdownDocument> ParseAsync(IFileInfo path, YamlFrontMatter? matter, Cancel ctx)
{
var context = new ParserContext(this, path, matter, Context);
return ParseAsync(path, context, ctx);
return ParseAsync(path, context, Pipeline, ctx);
}

private async Task<MarkdownDocument> ParseAsync(IFileInfo path, MarkdownParserContext context, Cancel ctx)
private async Task<MarkdownDocument> ParseAsync(
IFileInfo path,
MarkdownParserContext context,
MarkdownPipeline pipeline,
Cancel ctx)
{
if (path.FileSystem is FileSystem)
{
//real IO optimize through UTF8 stream reader.
await using var streamReader = new Utf8StreamReader(path.FullName, fileOpenMode: FileOpenMode.Throughput);
var inputMarkdown = await streamReader.AsTextReader().ReadToEndAsync(ctx);
var markdownDocument = Markdig.Markdown.Parse(inputMarkdown, Pipeline, context);
var markdownDocument = Markdig.Markdown.Parse(inputMarkdown, pipeline, context);
return markdownDocument;
}
else
{
var inputMarkdown = await path.FileSystem.File.ReadAllTextAsync(path.FullName, ctx);
var markdownDocument = Markdig.Markdown.Parse(inputMarkdown, Pipeline, context);
var markdownDocument = Markdig.Markdown.Parse(inputMarkdown, pipeline, context);
return markdownDocument;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Elastic.Markdown/Myst/ParserContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ public ParserContext(MarkdownParser markdownParser,
public IFileInfo Path { get; }
public YamlFrontMatter? FrontMatter { get; }
public BuildContext Build { get; }
public bool SkipValidation { get; init; }
}
Loading

0 comments on commit 03dc186

Please sign in to comment.