Skip to content

Commit a13287d

Browse files
authored
Add validation to image/figure directives (#68)
1 parent f5fc018 commit a13287d

File tree

8 files changed

+87
-36
lines changed

8 files changed

+87
-36
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,8 @@ protected bool PropBool(params string[] keys)
9494
protected void EmitError(ParserContext context, string message) =>
9595
context.EmitError(Line + 1, 1, Directive.Length + 4 , message);
9696

97+
protected void EmitWarning(ParserContext context, string message) =>
98+
context.EmitWarning(Line + 1, 1, Directive.Length + 4 , message);
99+
97100

98101
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,14 @@ protected override void Write(HtmlRenderer renderer, DirectiveBlock directiveBlo
7979

8080
private void WriteImage(HtmlRenderer renderer, ImageBlock block)
8181
{
82-
var imageUrl = block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static")
82+
var imageUrl =
83+
block.ImageUrl != null &&
84+
(block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static"))
8385
? $"{block.Build.UrlPathPrefix}/{block.ImageUrl.TrimStart('/')}"
8486
: block.ImageUrl;
8587
var slice = Image.Create(new ImageViewModel
8688
{
87-
Classes = block.Classes,
88-
CrossReferenceName = block.CrossReferenceName,
89+
Label = block.Label,
8990
Align = block.Align,
9091
Alt = block.Alt,
9192
Height = block.Height,
@@ -99,13 +100,12 @@ private void WriteImage(HtmlRenderer renderer, ImageBlock block)
99100

100101
private void WriteFigure(HtmlRenderer renderer, ImageBlock block)
101102
{
102-
var imageUrl = block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static")
103+
var imageUrl = block.ImageUrl != null && (block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static"))
103104
? $"{block.Build.UrlPathPrefix}/{block.ImageUrl.TrimStart('/')}"
104105
: block.ImageUrl;
105106
var slice = Slices.Directives.Figure.Create(new ImageViewModel
106107
{
107-
Classes = block.Classes,
108-
CrossReferenceName = block.CrossReferenceName,
108+
Label = block.Label,
109109
Align = block.Align,
110110
Alt = block.Alt,
111111
Height = block.Height,

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

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,59 @@ public class ImageBlock(DirectiveBlockParser parser, Dictionary<string, string>
4646
/// </summary>
4747
public string? Target { get; set; }
4848

49-
/// <summary>
50-
/// A space-separated list of CSS classes to add to the image.
51-
/// </summary>
52-
public string? Classes { get; protected set; }
53-
5449
/// <summary>
5550
/// A reference target for the admonition (see cross-referencing).
5651
/// </summary>
57-
public string? CrossReferenceName { get; private set; }
52+
public string? Label { get; private set; }
53+
54+
public string? ImageUrl { get; private set; }
55+
56+
public bool Found { get; private set; }
5857

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

6159
public override void FinalizeAndValidate(ParserContext context)
6260
{
63-
ImageUrl = Arguments ?? string.Empty; //todo validate
64-
Classes = Properties.GetValueOrDefault("class");
65-
CrossReferenceName = Properties.GetValueOrDefault("name");
66-
Alt = Properties.GetValueOrDefault("alt");
67-
Height = Properties.GetValueOrDefault("height");
68-
Width = Properties.GetValueOrDefault("width");
69-
Scale = Properties.GetValueOrDefault("scale");
70-
Align = Properties.GetValueOrDefault("align");
71-
Target = Properties.GetValueOrDefault("target");
61+
Label = Prop("label", "name");
62+
Alt = Prop("alt");
63+
Align = Prop("align");
64+
65+
Height = Prop("height", "h");
66+
Width = Prop("width", "w");
67+
68+
Scale = Prop("scale");
69+
Target = Prop("target");
70+
71+
ExtractImageUrl(context);
72+
73+
}
74+
75+
private void ExtractImageUrl(ParserContext context)
76+
{
77+
var imageUrl = Arguments;
78+
if (string.IsNullOrWhiteSpace(imageUrl))
79+
{
80+
EmitError(context , $"{Directive} requires an argument.");
81+
return;
82+
}
83+
84+
if (Uri.TryCreate(imageUrl, UriKind.Absolute, out var uri) && uri.Scheme.StartsWith("http"))
85+
{
86+
EmitWarning(context, $"{Directive} is using an external URI: {uri} ");
87+
Found = true;
88+
ImageUrl = imageUrl;
89+
return;
90+
}
91+
92+
var includeFrom = context.Path.Directory!.FullName;
93+
if (imageUrl.StartsWith('/'))
94+
includeFrom = context.Parser.SourcePath.FullName;
95+
96+
ImageUrl = imageUrl;
97+
var imagePath = Path.Combine(includeFrom, imageUrl.TrimStart('/'));
98+
if (context.Build.ReadFileSystem.File.Exists(imageUrl))
99+
Found = true;
100+
else
101+
EmitError(context, $"`{imageUrl}` does not exist. resolved to `{imagePath}");
72102
}
73103
}
74104

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,17 @@ public class IncludeBlock(DirectiveBlockParser parser, Dictionary<string, string
3333
//https://mystmd.org/guide/directives#directive-include
3434
public override void FinalizeAndValidate(ParserContext context)
3535
{
36-
var includePath = Arguments; //todo validate
37-
3836
Literal |= PropBool("literal");
3937
Language = Prop("lang", "language", "code");
4038
Caption = Prop("caption");
4139
Label = Prop("label");
4240

41+
ExtractInclusionPath(context);
42+
}
43+
44+
private void ExtractInclusionPath(ParserContext context)
45+
{
46+
var includePath = Arguments;
4347
if (string.IsNullOrWhiteSpace(includePath))
4448
{
4549
context.EmitError(Line, Column, $"```{{{Directive}}}".Length , "include requires an argument.");
@@ -55,8 +59,6 @@ public override void FinalizeAndValidate(ParserContext context)
5559
Found = true;
5660
else
5761
EmitError(context, $"`{IncludePath}` does not exist.");
58-
59-
6062
}
6163
}
6264

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
@inherits RazorSlice<ImageViewModel>
2-
<figure class="align-default" id="@Model.CrossReferenceName">
2+
<figure class="align-default" id="@Model.Label">
33
<a class="reference internal image-reference" href="@Model.ImageUrl"><img alt="@Model.Alt" class="align-center" src="@Model.ImageUrl" style="@Model.Style">
44
</a>
55
<figcaption>
6-
<p>[CONTENT]<a class="headerlink" href="@Model.CrossReferenceName" title="Link to this image">¶</a></p>
6+
<p>[CONTENT]<a class="headerlink" href="@Model.Label" title="Link to this image">¶</a></p>
77
</figcaption>
88
</figure>

src/Elastic.Markdown/Slices/Directives/_ViewModels.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
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.Text;
5-
using Elastic.Markdown.Myst.Directives;
65

76
namespace Elastic.Markdown.Slices.Directives;
87

@@ -45,8 +44,7 @@ public class IncludeViewModel
4544

4645
public class ImageViewModel
4746
{
48-
public required string? CrossReferenceName { get; init; }
49-
public required string? Classes { get; init; }
47+
public required string? Label { get; init; }
5048
public required string? Align { get; init; }
5149
public required string? Alt { get; init; }
5250
public required string? Height { get; init; }

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown")
6464
});
6565

6666
var file = FileSystem.FileInfo.New("docs/source/index.md");
67-
var root = FileSystem.DirectoryInfo.New(Paths.Root.FullName);
67+
var root = file.Directory!;
6868
Collector = new TestDiagnosticsCollector(logger);
6969
var context = new BuildContext
7070
{

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
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 FluentAssertions;
6-
using Xunit;
78
using Xunit.Abstractions;
89

910
namespace Elastic.Markdown.Tests.Directives;
1011

1112
public class ImageBlockTests(ITestOutputHelper output) : DirectiveTest<ImageBlock>(output,
1213
"""
13-
```{image} /_static/img/observability.png
14+
```{image} img/observability.png
1415
:alt: Elasticsearch
1516
:width: 250px
1617
```
1718
"""
1819
)
1920
{
21+
public override Task InitializeAsync()
22+
{
23+
FileSystem.AddFile(@"img/observability.png", "");
24+
return base.InitializeAsync();
25+
}
26+
2027
[Fact]
2128
public void ParsesBlock() => Block.Should().NotBeNull();
2229

@@ -25,7 +32,14 @@ public void ParsesBreakPoint()
2532
{
2633
Block!.Alt.Should().Be("Elasticsearch");
2734
Block!.Width.Should().Be("250px");
28-
Block!.ImageUrl.Should().Be("/_static/img/observability.png");
35+
Block!.ImageUrl.Should().Be("img/observability.png");
36+
}
37+
38+
[Fact]
39+
public void ImageIsFoundSoNoErrorIsEmitted()
40+
{
41+
Block!.Found.Should().BeTrue();
42+
Collector.Diagnostics.Count.Should().Be(0);
2943
}
3044
}
3145

@@ -45,7 +59,11 @@ Relaxing at the beach 🏝 🌊 😎
4559
public void ParsesBlock() => Block.Should().NotBeNull();
4660

4761
[Fact]
48-
public void ParsesBreakPoint()
62+
public void WarnsOnExternalUri()
4963
{
64+
Block!.Found.Should().BeTrue();
65+
66+
Collector.Diagnostics.Should().HaveCount(1)
67+
.And.OnlyContain(d => d.Severity == Severity.Warning);
5068
}
5169
}

0 commit comments

Comments
 (0)