Skip to content

Commit

Permalink
Add validation to image/figure directives (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mpdreamz authored Nov 14, 2024
1 parent f5fc018 commit a13287d
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 36 deletions.
3 changes: 3 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,8 @@ protected bool PropBool(params string[] keys)
protected void EmitError(ParserContext context, string message) =>
context.EmitError(Line + 1, 1, Directive.Length + 4 , message);

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


}
12 changes: 6 additions & 6 deletions src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ protected override void Write(HtmlRenderer renderer, DirectiveBlock directiveBlo

private void WriteImage(HtmlRenderer renderer, ImageBlock block)
{
var imageUrl = block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static")
var imageUrl =
block.ImageUrl != null &&
(block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static"))
? $"{block.Build.UrlPathPrefix}/{block.ImageUrl.TrimStart('/')}"
: block.ImageUrl;
var slice = Image.Create(new ImageViewModel
{
Classes = block.Classes,
CrossReferenceName = block.CrossReferenceName,
Label = block.Label,
Align = block.Align,
Alt = block.Alt,
Height = block.Height,
Expand All @@ -99,13 +100,12 @@ private void WriteImage(HtmlRenderer renderer, ImageBlock block)

private void WriteFigure(HtmlRenderer renderer, ImageBlock block)
{
var imageUrl = block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static")
var imageUrl = block.ImageUrl != null && (block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static"))
? $"{block.Build.UrlPathPrefix}/{block.ImageUrl.TrimStart('/')}"
: block.ImageUrl;
var slice = Slices.Directives.Figure.Create(new ImageViewModel
{
Classes = block.Classes,
CrossReferenceName = block.CrossReferenceName,
Label = block.Label,
Align = block.Align,
Alt = block.Alt,
Height = block.Height,
Expand Down
62 changes: 46 additions & 16 deletions src/Elastic.Markdown/Myst/Directives/ImageBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,59 @@ public class ImageBlock(DirectiveBlockParser parser, Dictionary<string, string>
/// </summary>
public string? Target { get; set; }

/// <summary>
/// A space-separated list of CSS classes to add to the image.
/// </summary>
public string? Classes { get; protected set; }

/// <summary>
/// A reference target for the admonition (see cross-referencing).
/// </summary>
public string? CrossReferenceName { get; private set; }
public string? Label { get; private set; }

public string? ImageUrl { get; private set; }

public bool Found { get; private set; }

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

public override void FinalizeAndValidate(ParserContext context)
{
ImageUrl = Arguments ?? string.Empty; //todo validate
Classes = Properties.GetValueOrDefault("class");
CrossReferenceName = Properties.GetValueOrDefault("name");
Alt = Properties.GetValueOrDefault("alt");
Height = Properties.GetValueOrDefault("height");
Width = Properties.GetValueOrDefault("width");
Scale = Properties.GetValueOrDefault("scale");
Align = Properties.GetValueOrDefault("align");
Target = Properties.GetValueOrDefault("target");
Label = Prop("label", "name");
Alt = Prop("alt");
Align = Prop("align");

Height = Prop("height", "h");
Width = Prop("width", "w");

Scale = Prop("scale");
Target = Prop("target");

ExtractImageUrl(context);

}

private void ExtractImageUrl(ParserContext context)
{
var imageUrl = Arguments;
if (string.IsNullOrWhiteSpace(imageUrl))
{
EmitError(context , $"{Directive} requires an argument.");
return;
}

if (Uri.TryCreate(imageUrl, UriKind.Absolute, out var uri) && uri.Scheme.StartsWith("http"))
{
EmitWarning(context, $"{Directive} is using an external URI: {uri} ");
Found = true;
ImageUrl = imageUrl;
return;
}

var includeFrom = context.Path.Directory!.FullName;
if (imageUrl.StartsWith('/'))
includeFrom = context.Parser.SourcePath.FullName;

ImageUrl = imageUrl;
var imagePath = Path.Combine(includeFrom, imageUrl.TrimStart('/'));
if (context.Build.ReadFileSystem.File.Exists(imageUrl))
Found = true;
else
EmitError(context, $"`{imageUrl}` does not exist. resolved to `{imagePath}");
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ public class IncludeBlock(DirectiveBlockParser parser, Dictionary<string, string
//https://mystmd.org/guide/directives#directive-include
public override void FinalizeAndValidate(ParserContext context)
{
var includePath = Arguments; //todo validate

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

ExtractInclusionPath(context);
}

private void ExtractInclusionPath(ParserContext context)
{
var includePath = Arguments;
if (string.IsNullOrWhiteSpace(includePath))
{
context.EmitError(Line, Column, $"```{{{Directive}}}".Length , "include requires an argument.");
Expand All @@ -55,8 +59,6 @@ public override void FinalizeAndValidate(ParserContext context)
Found = true;
else
EmitError(context, $"`{IncludePath}` does not exist.");


}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Elastic.Markdown/Slices/Directives/Figure.cshtml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@inherits RazorSlice<ImageViewModel>
<figure class="align-default" id="@Model.CrossReferenceName">
<figure class="align-default" id="@Model.Label">
<a class="reference internal image-reference" href="@Model.ImageUrl"><img alt="@Model.Alt" class="align-center" src="@Model.ImageUrl" style="@Model.Style">
</a>
<figcaption>
<p>[CONTENT]<a class="headerlink" href="@Model.CrossReferenceName" title="Link to this image">¶</a></p>
<p>[CONTENT]<a class="headerlink" href="@Model.Label" title="Link to this image">¶</a></p>
</figcaption>
</figure>
4 changes: 1 addition & 3 deletions src/Elastic.Markdown/Slices/Directives/_ViewModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// 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.Text;
using Elastic.Markdown.Myst.Directives;

namespace Elastic.Markdown.Slices.Directives;

Expand Down Expand Up @@ -45,8 +44,7 @@ public class IncludeViewModel

public class ImageViewModel
{
public required string? CrossReferenceName { get; init; }
public required string? Classes { get; init; }
public required string? Label { get; init; }
public required string? Align { get; init; }
public required string? Alt { get; init; }
public required string? Height { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown")
});

var file = FileSystem.FileInfo.New("docs/source/index.md");
var root = FileSystem.DirectoryInfo.New(Paths.Root.FullName);
var root = file.Directory!;
Collector = new TestDiagnosticsCollector(logger);
var context = new BuildContext
{
Expand Down
26 changes: 22 additions & 4 deletions tests/Elastic.Markdown.Tests/Directives/ImageTests.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
// 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 FluentAssertions;
using Xunit;
using Xunit.Abstractions;

namespace Elastic.Markdown.Tests.Directives;

public class ImageBlockTests(ITestOutputHelper output) : DirectiveTest<ImageBlock>(output,
"""
```{image} /_static/img/observability.png
```{image} img/observability.png
:alt: Elasticsearch
:width: 250px
```
"""
)
{
public override Task InitializeAsync()
{
FileSystem.AddFile(@"img/observability.png", "");
return base.InitializeAsync();
}

[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();

Expand All @@ -25,7 +32,14 @@ public void ParsesBreakPoint()
{
Block!.Alt.Should().Be("Elasticsearch");
Block!.Width.Should().Be("250px");
Block!.ImageUrl.Should().Be("/_static/img/observability.png");
Block!.ImageUrl.Should().Be("img/observability.png");
}

[Fact]
public void ImageIsFoundSoNoErrorIsEmitted()
{
Block!.Found.Should().BeTrue();
Collector.Diagnostics.Count.Should().Be(0);
}
}

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

[Fact]
public void ParsesBreakPoint()
public void WarnsOnExternalUri()
{
Block!.Found.Should().BeTrue();

Collector.Diagnostics.Should().HaveCount(1)
.And.OnlyContain(d => d.Severity == Severity.Warning);
}
}

0 comments on commit a13287d

Please sign in to comment.