Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure include/literalinclude emits validation #63

Merged
merged 3 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion src/Elastic.Markdown/Myst/Directives/AdmonitionBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public override void FinalizeAndValidate(ParserContext context)
{
Classes = Properties.GetValueOrDefault("class");
CrossReferenceName = Properties.GetValueOrDefault("name");
ParseBool("open", b => DropdownOpen = b);
DropdownOpen = PropBool("open");
}
}

Expand Down
21 changes: 14 additions & 7 deletions src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,25 @@ public abstract class DirectiveBlock(DirectiveBlockParser parser, Dictionary<str
/// <param name="context"></param>
public abstract void FinalizeAndValidate(ParserContext context);

protected void ParseBool(string key, Action<bool> setter)
protected bool PropBool(params string[] keys)
{
var value = Properties.GetValueOrDefault(key);
var value = Prop(keys);
if (string.IsNullOrEmpty(value))
return keys.Any(k => Properties.ContainsKey(k));

return bool.TryParse(value, out var result) && result;
}

protected string? Prop(params string[] keys)
{
foreach (var key in keys)
{
setter(Properties.ContainsKey(key));
return;
if (Properties.TryGetValue(key, out var value))
return value;
}

if (bool.TryParse(value, out var result))
setter(result);
//todo invalidate
return default;
}


}
48 changes: 27 additions & 21 deletions src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +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.IO.Abstractions;
using Elastic.Markdown.Diagnostics;

namespace Elastic.Markdown.Myst.Directives;

Expand All @@ -14,43 +15,46 @@ public class IncludeBlock(DirectiveBlockParser parser, Dictionary<string, string

public IDirectoryInfo DocumentationSourcePath { get; } = context.Parser.SourcePath;

public IFileInfo IncludeFromPath { get; } = context.Path;

public YamlFrontMatter? FrontMatter { get; } = context.FrontMatter;

public string? IncludePath { get; private set; }

public bool Literal { get; protected set; }
public bool Found { get; private set; }

protected virtual string Directive { get; } = "include";

public bool Literal { get; protected set; }
public string? Language { get; private set; }
public string? Caption { get; private set; }
public string? Label { get; private set; }

public bool Found { get; private set; }

//TODO add all options from
//https://mystmd.org/guide/directives#directive-include
public override void FinalizeAndValidate(ParserContext context)
{
var includePath = Arguments; //todo validate
Literal |= bool.TryParse(Properties.GetValueOrDefault("literal"), out var b) && b;
Language = Properties.GetValueOrDefault("language");
if (includePath is null)

Literal |= PropBool("literal");
Language = Prop("lang", "language", "code");
Caption = Prop("caption");
Label = Prop("label");

if (string.IsNullOrWhiteSpace(includePath))
{
//TODO emit empty error
context.EmitError(Line, Column, $"```{{{Directive}}}".Length , "include requires an argument.");
return;
}

var includeFrom = context.Path.Directory!.FullName;
if (includePath.StartsWith('/'))
includeFrom = DocumentationSourcePath.FullName;

IncludePath = Path.Combine(includeFrom, includePath.TrimStart('/'));
if (FileSystem.File.Exists(IncludePath))
Found = true;
else
{
var includeFrom = IncludeFromPath.Directory!.FullName;
if (includePath.StartsWith('/'))
includeFrom = DocumentationSourcePath.FullName;

IncludePath = Path.Combine(includeFrom, includePath.TrimStart('/'));
if (FileSystem.File.Exists(IncludePath))
Found = true;
else
{
//TODO emit error
}
}
context.EmitError(Line, Column, "```{include}".Length , $"`{IncludePath}` does not exist.");


}
Expand All @@ -61,4 +65,6 @@ public class LiteralIncludeBlock : IncludeBlock
{
public LiteralIncludeBlock(DirectiveBlockParser parser, Dictionary<string, string> properties, ParserContext context)
: base(parser, properties, context) => Literal = true;

protected override string Directive { get; } = "literalinclude";
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ public class UnsupportedDirectiveBlock(DirectiveBlockParser parser, string direc
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.");
context.EmitWarning(line:1, column:1, length:directive.Length, message: $"Directive block '{directive}' is unsupported. See {IssueUrl} for more information.");
}
3 changes: 1 addition & 2 deletions tests/Elastic.Markdown.Tests/Directives/UnsupportedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ A regular paragraph.
[Fact]
public void EmitsUnsupportedWarnings()
{
Collector.Diagnostics.Should().NotBeNullOrEmpty()
.And.HaveCount(1);
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Warning);
Collector.Diagnostics.Should()
.OnlyContain(d => d.Message.StartsWith($"Directive block '{directive}' is unsupported."));
Expand Down
49 changes: 49 additions & 0 deletions tests/Elastic.Markdown.Tests/FileInclusion/IncludeTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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;
using Elastic.Markdown.Myst.Directives;
using Elastic.Markdown.Tests.Directives;
using FluentAssertions;
Expand Down Expand Up @@ -66,3 +68,50 @@ public void InclusionInheritsYamlContext() =>
.And.Be("<p><em>Hello bar</em></p>\n")
;
}


public class IncludeNotFoundTests(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
"""
```{include} _snippets/notfound.md
```
"""
)
{
[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();

[Fact]
public void IncludesNothing() => Html.Should().Be("");

[Fact]
public void EmitsError()
{
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
Collector.Diagnostics.Should()
.OnlyContain(d => d.Message.Contains("notfound.md` does not exist"));
}
}

public class IncludeRequiresArgument(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
"""
```{include}
```
"""
)
{
[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();

[Fact]
public void IncludesNothing() => Html.Should().Be("");

[Fact]
public void EmitsError()
{
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
Collector.Diagnostics.Should()
.OnlyContain(d => d.Message.Contains("include requires an argument."));
}
}