Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static void EmitError(this InlineProcessor processor, int line, int colum
}


public static void EmitWarning(this BlockProcessor processor, int line, int column, int length, string message)
public static void EmitWarning(this InlineProcessor processor, int line, int column, int length, string message)
{
var context = processor.GetContext();
if (context.SkipValidation) return;
Expand Down
4 changes: 0 additions & 4 deletions src/Elastic.Markdown/Elastic.Markdown.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,4 @@
<PackageReference Include="System.IO.Abstractions" Version="21.0.29" />
</ItemGroup>

<ItemGroup>
<Folder Include="Myst\Inline\" />
</ItemGroup>

</Project>
29 changes: 26 additions & 3 deletions src/Elastic.Markdown/IO/DocumentationSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,30 @@ public class DocumentationSet

public ConfigurationFile Configuration { get; }

private MarkdownParser MarkdownParser { get; }
public MarkdownParser MarkdownParser { get; }

public DocumentationSet(BuildContext context) : this(null, null, context) { }

public DocumentationSet(IDirectoryInfo? sourcePath, IDirectoryInfo? outputPath, BuildContext context)
{
SourcePath = sourcePath ?? context.ReadFileSystem.DirectoryInfo.New(Path.Combine(Paths.Root.FullName, "docs/source"));
OutputPath = outputPath ?? context.WriteFileSystem.DirectoryInfo.New(Path.Combine(Paths.Root.FullName, ".artifacts/docs/html"));

var configurationFile = SourcePath.EnumerateFiles("docset.yml", SearchOption.AllDirectories).FirstOrDefault();
if (configurationFile is null)
{
configurationFile = context.ReadFileSystem.FileInfo.New(Path.Combine(SourcePath.FullName, "docset.yml"));
context.EmitWarning(configurationFile, "No configuration file found");
}

if (configurationFile.Directory!.FullName != SourcePath.FullName)
SourcePath = configurationFile.Directory;

MarkdownParser = new MarkdownParser(SourcePath, context, GetTitle);

Name = SourcePath.FullName;
MarkdownParser = new MarkdownParser(SourcePath, context);
OutputStateFile = OutputPath.FileSystem.FileInfo.New(Path.Combine(OutputPath.FullName, ".doc.state"));

var configurationFile = context.ReadFileSystem.FileInfo.New(Path.Combine(SourcePath.FullName, "docset.yml"));
Configuration = new ConfigurationFile(configurationFile, SourcePath, context);

Files = context.ReadFileSystem.Directory
Expand All @@ -57,6 +68,18 @@ public DocumentationSet(IDirectoryInfo? sourcePath, IDirectoryInfo? outputPath,
Tree = new DocumentationFolder(Configuration.TableOfContents, FlatMappedFiles, folderFiles);
}

private string? GetTitle(string relativePath) => GetMarkdownFile(relativePath)?.YamlFrontMatter?.Title;

public MarkdownFile? GetMarkdownFile(string relativePath)
{
if (FlatMappedFiles.TryGetValue(relativePath, out var file) && file is MarkdownFile markdownFile)
return markdownFile;
return null;
}

public async Task ResolveDirectoryTree(Cancel ctx) =>
await Tree.Resolve(ctx);

private DocumentationFile CreateMarkDownFile(IFileInfo file, BuildContext context)
{
if (Configuration.Exclude.Any(g => g.IsMatch(file.Name)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ private void WriteIncludeBlock(HtmlRenderer renderer, IncludeBlock block)
if (!block.Found || block.IncludePath is null)
return;

var parser = new MarkdownParser(block.DocumentationSourcePath, block.Build);
var parser = new MarkdownParser(block.DocumentationSourcePath, block.Build, block.GetTitle);
var file = block.FileSystem.FileInfo.New(block.IncludePath);
var document = parser.ParseAsync(file, block.FrontMatter, default).GetAwaiter().GetResult();
var html = document.ToHtml(parser.Pipeline);
Expand Down
2 changes: 2 additions & 0 deletions src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class IncludeBlock(DirectiveBlockParser parser, Dictionary<string, string

public BuildContext Build { get; } = context.Build;

public Func<string, string?>? GetTitle { get; } = context.GetTitle;

public IFileSystem FileSystem { get; } = context.Build.ReadFileSystem;

public IDirectoryInfo DocumentationSourcePath { get; } = context.Parser.SourcePath;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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 Markdig;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Syntax.Inlines;

namespace Elastic.Markdown.Myst.InlineParsers;

public static class DirectiveMarkdownBuilderExtensions
{
public static MarkdownPipelineBuilder UseDiagnosticLinks(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<DiagnosticLinkInlineExtensions>();
return pipeline;
}
}

public class DiagnosticLinkInlineExtensions : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline) =>
pipeline.InlineParsers.Replace<LinkInlineParser>(new DiagnosticLinkInlineParser());

public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { }
}

public class DiagnosticLinkInlineParser : LinkInlineParser
{
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
var match = base.Match(processor, ref slice);
if (!match) return false;

if (processor.Inline is not LinkInline link)
return match;

var url = link.Url;
var line = link.Line + 1;
var column = link.Column;
var length = url?.Length ?? 1;

var context = processor.GetContext();
if (processor.GetContext().SkipValidation)
return match;

if (string.IsNullOrEmpty(url))
{
processor.EmitWarning(line, column, length, $"Found empty url");
return match;
}

if (Uri.TryCreate(url, UriKind.Absolute, out var uri) && uri.Scheme.StartsWith("http"))
{
processor.EmitWarning(line, column, length, $"external URI: {uri} ");
return match;
}

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

var pathOnDisk = Path.Combine(includeFrom, url.TrimStart('/'));
if (!context.Build.ReadFileSystem.File.Exists(pathOnDisk))
processor.EmitError(line, column, length, $"`{url}` does not exist. resolved to `{pathOnDisk}");

if (link.FirstChild == null)
{
var title = context.GetTitle?.Invoke(url);
if (!string.IsNullOrEmpty(title))
link.AppendChild(new LiteralInline(title));
}

if (url.EndsWith(".md"))
link.Url = Path.ChangeExtension(url, ".html");

return match;



}
}
18 changes: 14 additions & 4 deletions src/Elastic.Markdown/Myst/MarkdownParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,31 @@
using Cysharp.IO;
using Elastic.Markdown.Myst.Comments;
using Elastic.Markdown.Myst.Directives;
using Elastic.Markdown.Myst.InlineParsers;
using Elastic.Markdown.Myst.Substitution;
using Markdig;
using Markdig.Extensions.EmphasisExtras;
using Markdig.Syntax;

namespace Elastic.Markdown.Myst;

public class MarkdownParser(IDirectoryInfo sourcePath, BuildContext context)
public class MarkdownParser(IDirectoryInfo sourcePath, BuildContext context, Func<string, string?>? getTitle)
{
public IDirectoryInfo SourcePath { get; } = sourcePath;
public BuildContext Context { get; } = context;

public MarkdownPipeline MinimalPipeline { get; } =
new MarkdownPipelineBuilder()
.UseDiagnosticLinks()
.UseSubstitution()
.UseYamlFrontMatter()
.Build();

public MarkdownPipeline Pipeline { get; } =
public MarkdownPipeline Pipeline =>
new MarkdownPipelineBuilder()
.EnableTrackTrivia()
.UsePreciseSourceLocation()
.UseDiagnosticLinks()
.UseGenericAttributes()
.UseEmphasisExtras(EmphasisExtraOptions.Default)
.UseSoftlineBreakAsHardlineBreak()
Expand All @@ -43,13 +46,20 @@ public class MarkdownParser(IDirectoryInfo sourcePath, BuildContext context)

public Task<MarkdownDocument> MinimalParseAsync(IFileInfo path, Cancel ctx)
{
var context = new ParserContext(this, path, null, Context) { SkipValidation = true };
var context = new ParserContext(this, path, null, Context)
{
SkipValidation = true,
GetTitle = getTitle
};
return ParseAsync(path, context, MinimalPipeline, ctx);
}

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

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 @@ -52,4 +52,5 @@ public ParserContext(MarkdownParser markdownParser,
public YamlFrontMatter? FrontMatter { get; }
public BuildContext Build { get; }
public bool SkipValidation { get; init; }
public Func<string, string?>? GetTitle { get; init; }
}
17 changes: 12 additions & 5 deletions tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public abstract class DirectiveTest : IAsyncLifetime
protected MarkdownDocument Document { get; private set; }
protected MockFileSystem FileSystem { get; }
protected TestDiagnosticsCollector Collector { get; }
protected DocumentationSet Set { get; set; }


protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown")]string content)
{
Expand All @@ -62,23 +64,28 @@ protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown")
{
CurrentDirectory = Paths.Root.FullName
});
// ReSharper disable once VirtualMemberCallInConstructor
// nasty but sub implementations won't use class state.
AddToFileSystem(FileSystem);

var root = FileSystem.DirectoryInfo.New(Path.Combine(Paths.Root.FullName, "docs/source"));
FileSystem.GenerateDocSetYaml(root);

var file = FileSystem.FileInfo.New("docs/source/index.md");
var root = file.Directory!;
Collector = new TestDiagnosticsCollector(logger);
var context = new BuildContext
{
ReadFileSystem = FileSystem,
WriteFileSystem = FileSystem,
Collector = Collector
};
var parser = new MarkdownParser(root, context);

File = new MarkdownFile(file, root, parser, context);
Set = new DocumentationSet(null, null, context);
File = Set.GetMarkdownFile("index.md") ?? throw new NullReferenceException();
Html = default!; //assigned later
Document = default!;
}

protected virtual void AddToFileSystem(MockFileSystem fileSystem) { }

public virtual async Task InitializeAsync()
{
var collectTask = Task.Run(async () => await Collector.StartAsync(default), default);
Expand Down
8 changes: 3 additions & 5 deletions tests/Elastic.Markdown.Tests/Directives/ImageTests.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.TestingHelpers;
using Elastic.Markdown.Diagnostics;
using Elastic.Markdown.Myst.Directives;
using FluentAssertions;
Expand All @@ -18,11 +19,8 @@ public class ImageBlockTests(ITestOutputHelper output) : DirectiveTest<ImageBloc
"""
)
{
public override Task InitializeAsync()
{
FileSystem.AddFile(@"docs/source/img/observability.png", "");
return base.InitializeAsync();
}
protected override void AddToFileSystem(MockFileSystem fileSystem) =>
fileSystem.AddFile(@"docs/source/img/observability.png", "");

[Fact]
public void ParsesBlock() => Block.Should().NotBeNull();
Expand Down
11 changes: 5 additions & 6 deletions tests/Elastic.Markdown.Tests/FileInclusion/IncludeTests.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.TestingHelpers;
using Elastic.Markdown.Diagnostics;
using Elastic.Markdown.Myst.Directives;
using Elastic.Markdown.Tests.Directives;
Expand All @@ -18,12 +19,11 @@ public class IncludeTests(ITestOutputHelper output) : DirectiveTest<IncludeBlock
"""
)
{
public override Task InitializeAsync()
protected override void AddToFileSystem(MockFileSystem fileSystem)
{
// language=markdown
var inclusion = "*Hello world*";
FileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
return base.InitializeAsync();
fileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
}

[Fact]
Expand All @@ -50,12 +50,11 @@ public class IncludeSubstitutionTests(ITestOutputHelper output) : DirectiveTest<
"""
)
{
public override Task InitializeAsync()
protected override void AddToFileSystem(MockFileSystem fileSystem)
{
// language=markdown
var inclusion = "*Hello {{foo}}*";
FileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
return base.InitializeAsync();
fileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
}

[Fact]
Expand Down
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 System.IO.Abstractions.TestingHelpers;
using Elastic.Markdown.Myst.Directives;
using Elastic.Markdown.Tests.Directives;
using FluentAssertions;
Expand All @@ -17,12 +19,11 @@ public class LiteralIncludeUsingPropertyTests(ITestOutputHelper output) : Direct
"""
)
{
public override Task InitializeAsync()
protected override void AddToFileSystem(MockFileSystem fileSystem)
{
// language=markdown
var inclusion = "*Hello world*";
FileSystem.AddFile(@"docs/source/_snippets/test.txt", inclusion);
return base.InitializeAsync();
fileSystem.AddFile(@"docs/source/_snippets/test.txt", inclusion);
}

[Fact]
Expand All @@ -43,12 +44,11 @@ public class LiteralIncludeTests(ITestOutputHelper output) : DirectiveTest<Inclu
"""
)
{
public override Task InitializeAsync()
protected override void AddToFileSystem(MockFileSystem fileSystem)
{
// language=markdown
var inclusion = "*Hello world*";
FileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
return base.InitializeAsync();
fileSystem.AddFile(@"docs/source/_snippets/test.md", inclusion);
}

[Fact]
Expand Down
Loading
Loading