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
47 changes: 47 additions & 0 deletions src/Elastic.Markdown/Helpers/Interpolation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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.Text.RegularExpressions;

namespace Elastic.Markdown.Helpers;

internal static partial class InterpolationRegex
{
[GeneratedRegex(@"\{\{[^\r\n}]+?\}\}", RegexOptions.IgnoreCase, "en-US")]
public static partial Regex MatchSubstitutions();
}

public static class Interpolation
{
public static bool ReplaceSubstitutions(this ReadOnlySpan<char> span, Dictionary<string, string>? properties, out string? replacement)
{
replacement = null;
var substitutions = properties ?? new();
if (substitutions.Count == 0)
return false;

var matchSubs = InterpolationRegex.MatchSubstitutions().EnumerateMatches(span);
var lookup = substitutions.GetAlternateLookup<ReadOnlySpan<char>>();

var replaced = false;
foreach (var match in matchSubs)
{
if (match.Length == 0)
continue;

var spanMatch = span.Slice(match.Index, match.Length);
var key = spanMatch.Trim(['{', '}']);

if (!lookup.TryGetValue(key, out var value))
continue;

replacement ??= span.ToString();
replacement = replacement.Replace(spanMatch.ToString(), value);
replaced = true;

}

return replaced;
}
}
19 changes: 19 additions & 0 deletions src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information
using System.IO.Abstractions;
using Elastic.Markdown.Diagnostics;
using Elastic.Markdown.Helpers;
using Elastic.Markdown.IO.Navigation;
using Elastic.Markdown.Myst;
using Elastic.Markdown.Myst.Directives;
Expand Down Expand Up @@ -102,6 +103,24 @@ private void ReadDocumentInstructions(MarkdownDocument document)
YamlFrontMatter = ReadYamlFrontMatter(document, raw);
Title = YamlFrontMatter.Title;
NavigationTitle = YamlFrontMatter.NavigationTitle;
if (!string.IsNullOrEmpty(NavigationTitle))
{
var props = MarkdownParser.Configuration.Substitutions;
var properties = YamlFrontMatter.Properties;
if (properties is { Count: >= 0 } local)
{
var allProperties = new Dictionary<string, string>(local);
foreach (var (key, value) in props)
allProperties[key] = value;
if (NavigationTitle.AsSpan().ReplaceSubstitutions(allProperties, out var replacement))
NavigationTitle = replacement;
}
else
{
if (NavigationTitle.AsSpan().ReplaceSubstitutions(properties, out var replacement))
NavigationTitle = replacement;
}
}
}
else
{
Expand Down
3 changes: 0 additions & 3 deletions src/Elastic.Markdown/Myst/CodeBlocks/CallOutParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,4 @@ public static partial class CallOutParser

[GeneratedRegex(@"^.+\S+.*?\s(?:\/\/|#)\s[^""]+$", RegexOptions.IgnoreCase, "en-US")]
public static partial Regex MathInlineAnnotation();

[GeneratedRegex(@"\{\{[^\r\n}]+?\}\}", RegexOptions.IgnoreCase, "en-US")]
public static partial Regex MatchSubstitutions();
}
34 changes: 2 additions & 32 deletions src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Text.RegularExpressions;
using Elastic.Markdown.Diagnostics;
using Elastic.Markdown.Helpers;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
Expand Down Expand Up @@ -87,7 +88,7 @@ public override bool Close(BlockProcessor processor, Block block)
var line = lines.Lines[index];
var span = line.Slice.AsSpan();

if (ReplaceSubstitutions(context, span, out var replacement))
if (span.ReplaceSubstitutions(context.FrontMatter?.Properties, out var replacement))
{
var s = new StringSlice(replacement);
lines.Lines[index] = new StringLine(ref s);
Expand Down Expand Up @@ -139,37 +140,6 @@ public override bool Close(BlockProcessor processor, Block block)
return base.Close(processor, block);
}

private static bool ReplaceSubstitutions(ParserContext context, ReadOnlySpan<char> span, out string? replacement)
{
replacement = null;
var substitutions = context.FrontMatter?.Properties ?? new();
if (substitutions.Count == 0)
return false;

var matchSubs = CallOutParser.MatchSubstitutions().EnumerateMatches(span);

var replaced = false;
foreach (var match in matchSubs)
{
if (match.Length == 0)
continue;

var spanMatch = span.Slice(match.Index, match.Length);
var key = spanMatch.Trim(['{', '}']);

// TODO: alternate lookup using span in c# 9
if (substitutions.TryGetValue(key.ToString(), out var value))
{
replacement ??= span.ToString();
replacement = replacement.Replace(spanMatch.ToString(), value);
replaced = true;
}

}

return replaced;
}

private static CallOut? EnumerateAnnotations(Regex.ValueMatchEnumerator matches,
ref ReadOnlySpan<char> span,
ref int callOutIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class YamlFrontMatter
[YamlMember(Alias = "sub")]
public Dictionary<string, string>? Properties { get; set; }


[YamlMember(Alias = "applies")]
public Deployment? AppliesTo { get; set; }
}
8 changes: 5 additions & 3 deletions src/Elastic.Markdown/Myst/MarkdownParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ public class MarkdownParser(
.DisableHtml()
.Build();

public ConfigurationFile Configuration { get; } = configuration;

public Task<MarkdownDocument> MinimalParseAsync(IFileInfo path, Cancel ctx)
{
var context = new ParserContext(this, path, null, Context, configuration)
var context = new ParserContext(this, path, null, Context, Configuration)
{
SkipValidation = true,
GetDocumentationFile = getDocumentationFile
Expand All @@ -65,7 +67,7 @@ public Task<MarkdownDocument> MinimalParseAsync(IFileInfo path, Cancel ctx)

public Task<MarkdownDocument> ParseAsync(IFileInfo path, YamlFrontMatter? matter, Cancel ctx)
{
var context = new ParserContext(this, path, matter, Context, configuration)
var context = new ParserContext(this, path, matter, Context, Configuration)
{
GetDocumentationFile = getDocumentationFile
};
Expand Down Expand Up @@ -96,7 +98,7 @@ private async Task<MarkdownDocument> ParseAsync(

public MarkdownDocument Parse(string yaml, IFileInfo parent, YamlFrontMatter? matter)
{
var context = new ParserContext(this, parent, matter, Context, configuration)
var context = new ParserContext(this, parent, matter, Context, Configuration)
{
GetDocumentationFile = getDocumentationFile
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,18 @@ public void WarnsOfNoTitle() =>
Collector.Diagnostics.Should().NotBeEmpty()
.And.Contain(d => d.Message.Contains("Missing yaml front-matter block defining a title"));
}

public class NavigationTitleSupportReplacements(ITestOutputHelper output) : DirectiveTest(output,
"""
---
title: Elastic Docs v3
navigation_title: "Documentation Guide: {{key}}"
sub:
key: "value"
---
"""
)
{
[Fact]
public void ReadsNavigationTitle() => File.NavigationTitle.Should().Be("Documentation Guide: value");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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.Helpers;
using FluentAssertions;

namespace Elastic.Markdown.Tests.Interpolation;

public class InterpolationTests
{
[Fact]
public void ReplacesVariables()
{
var span = "My text {{with-variables}} {{not-defined}}".AsSpan();
var replacements = new Dictionary<string, string> { { "with-variables", "With Variables" } };
var replaced = span.ReplaceSubstitutions(replacements, out var replacement);

replaced.Should().BeTrue();
replacement.Should().Be("My text With Variables {{not-defined}}");
}

[Fact]
public void OnlyReplacesDefinedVariables()
{
var span = "My text {{not-defined}}".AsSpan();
var replacements = new Dictionary<string, string> { { "with-variables", "With Variables" } };
var replaced = span.ReplaceSubstitutions(replacements, out var replacement);

replaced.Should().BeFalse();
// no need to allocate replacement we can continue with span
replacement.Should().BeNull();
}
}
Loading