Skip to content

Commit f5be0d5

Browse files
authored
Ensure include/literalinclude emits validation (#63)
1 parent a488085 commit f5be0d5

File tree

6 files changed

+93
-32
lines changed

6 files changed

+93
-32
lines changed

src/Elastic.Markdown/Myst/Directives/AdmonitionBlock.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public override void FinalizeAndValidate(ParserContext context)
2929
{
3030
Classes = Properties.GetValueOrDefault("class");
3131
CrossReferenceName = Properties.GetValueOrDefault("name");
32-
ParseBool("open", b => DropdownOpen = b);
32+
DropdownOpen = PropBool("open");
3333
}
3434
}
3535

src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,25 @@ public abstract class DirectiveBlock(DirectiveBlockParser parser, Dictionary<str
6969
/// <param name="context"></param>
7070
public abstract void FinalizeAndValidate(ParserContext context);
7171

72-
protected void ParseBool(string key, Action<bool> setter)
72+
protected bool PropBool(params string[] keys)
7373
{
74-
var value = Properties.GetValueOrDefault(key);
74+
var value = Prop(keys);
7575
if (string.IsNullOrEmpty(value))
76+
return keys.Any(k => Properties.ContainsKey(k));
77+
78+
return bool.TryParse(value, out var result) && result;
79+
}
80+
81+
protected string? Prop(params string[] keys)
82+
{
83+
foreach (var key in keys)
7684
{
77-
setter(Properties.ContainsKey(key));
78-
return;
85+
if (Properties.TryGetValue(key, out var value))
86+
return value;
7987
}
8088

81-
if (bool.TryParse(value, out var result))
82-
setter(result);
83-
//todo invalidate
89+
return default;
8490
}
8591

92+
8693
}

src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44
using System.IO.Abstractions;
5+
using Elastic.Markdown.Diagnostics;
56

67
namespace Elastic.Markdown.Myst.Directives;
78

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

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

17-
public IFileInfo IncludeFromPath { get; } = context.Path;
18-
1918
public YamlFrontMatter? FrontMatter { get; } = context.FrontMatter;
2019

2120
public string? IncludePath { get; private set; }
2221

23-
public bool Literal { get; protected set; }
22+
public bool Found { get; private set; }
23+
24+
protected virtual string Directive { get; } = "include";
2425

26+
public bool Literal { get; protected set; }
2527
public string? Language { get; private set; }
28+
public string? Caption { get; private set; }
29+
public string? Label { get; private set; }
2630

27-
public bool Found { get; private set; }
2831

2932
//TODO add all options from
3033
//https://mystmd.org/guide/directives#directive-include
3134
public override void FinalizeAndValidate(ParserContext context)
3235
{
3336
var includePath = Arguments; //todo validate
34-
Literal |= bool.TryParse(Properties.GetValueOrDefault("literal"), out var b) && b;
35-
Language = Properties.GetValueOrDefault("language");
36-
if (includePath is null)
37+
38+
Literal |= PropBool("literal");
39+
Language = Prop("lang", "language", "code");
40+
Caption = Prop("caption");
41+
Label = Prop("label");
42+
43+
if (string.IsNullOrWhiteSpace(includePath))
3744
{
38-
//TODO emit empty error
45+
context.EmitError(Line, Column, $"```{{{Directive}}}".Length , "include requires an argument.");
46+
return;
3947
}
48+
49+
var includeFrom = context.Path.Directory!.FullName;
50+
if (includePath.StartsWith('/'))
51+
includeFrom = DocumentationSourcePath.FullName;
52+
53+
IncludePath = Path.Combine(includeFrom, includePath.TrimStart('/'));
54+
if (FileSystem.File.Exists(IncludePath))
55+
Found = true;
4056
else
41-
{
42-
var includeFrom = IncludeFromPath.Directory!.FullName;
43-
if (includePath.StartsWith('/'))
44-
includeFrom = DocumentationSourcePath.FullName;
45-
46-
IncludePath = Path.Combine(includeFrom, includePath.TrimStart('/'));
47-
if (FileSystem.File.Exists(IncludePath))
48-
Found = true;
49-
else
50-
{
51-
//TODO emit error
52-
}
53-
}
57+
context.EmitError(Line, Column, "```{include}".Length , $"`{IncludePath}` does not exist.");
5458

5559

5660
}
@@ -61,4 +65,6 @@ public class LiteralIncludeBlock : IncludeBlock
6165
{
6266
public LiteralIncludeBlock(DirectiveBlockParser parser, Dictionary<string, string> properties, ParserContext context)
6367
: base(parser, properties, context) => Literal = true;
68+
69+
protected override string Directive { get; } = "literalinclude";
6470
}

src/Elastic.Markdown/Myst/Directives/UnsupportedDirectiveBlock.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ public class UnsupportedDirectiveBlock(DirectiveBlockParser parser, string direc
1414
public string IssueUrl => $"https://github.com/elastic/docs-builder/issues/{issueId}";
1515

1616
public override void FinalizeAndValidate(ParserContext context) =>
17-
context.EmitWarning(line:1, column:1, length:2, message: $"Directive block '{directive}' is unsupported. See {IssueUrl} for more information.");
17+
context.EmitWarning(line:1, column:1, length:directive.Length, message: $"Directive block '{directive}' is unsupported. See {IssueUrl} for more information.");
1818
}

tests/Elastic.Markdown.Tests/Directives/UnsupportedTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ A regular paragraph.
3333
[Fact]
3434
public void EmitsUnsupportedWarnings()
3535
{
36-
Collector.Diagnostics.Should().NotBeNullOrEmpty()
37-
.And.HaveCount(1);
36+
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
3837
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Warning);
3938
Collector.Diagnostics.Should()
4039
.OnlyContain(d => d.Message.StartsWith($"Directive block '{directive}' is unsupported."));

tests/Elastic.Markdown.Tests/FileInclusion/IncludeTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to Elasticsearch B.V under one or more agreements.
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
4+
5+
using Elastic.Markdown.Diagnostics;
46
using Elastic.Markdown.Myst.Directives;
57
using Elastic.Markdown.Tests.Directives;
68
using FluentAssertions;
@@ -66,3 +68,50 @@ public void InclusionInheritsYamlContext() =>
6668
.And.Be("<p><em>Hello bar</em></p>\n")
6769
;
6870
}
71+
72+
73+
public class IncludeNotFoundTests(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
74+
"""
75+
```{include} _snippets/notfound.md
76+
```
77+
"""
78+
)
79+
{
80+
[Fact]
81+
public void ParsesBlock() => Block.Should().NotBeNull();
82+
83+
[Fact]
84+
public void IncludesNothing() => Html.Should().Be("");
85+
86+
[Fact]
87+
public void EmitsError()
88+
{
89+
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
90+
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
91+
Collector.Diagnostics.Should()
92+
.OnlyContain(d => d.Message.Contains("notfound.md` does not exist"));
93+
}
94+
}
95+
96+
public class IncludeRequiresArgument(ITestOutputHelper output) : DirectiveTest<IncludeBlock>(output,
97+
"""
98+
```{include}
99+
```
100+
"""
101+
)
102+
{
103+
[Fact]
104+
public void ParsesBlock() => Block.Should().NotBeNull();
105+
106+
[Fact]
107+
public void IncludesNothing() => Html.Should().Be("");
108+
109+
[Fact]
110+
public void EmitsError()
111+
{
112+
Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1);
113+
Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error);
114+
Collector.Diagnostics.Should()
115+
.OnlyContain(d => d.Message.Contains("include requires an argument."));
116+
}
117+
}

0 commit comments

Comments
 (0)