Skip to content

Commit

Permalink
Add interpolation support and unify substitution logic (#201)
Browse files Browse the repository at this point in the history
Introduced interpolation functionality for handling string replacements
in Markdown. Refactored and centralized substitution logic into a
reusable helper, replacing redundant implementations. Updated navigation
title and code block parsing to leverage the new interpolation system,
ensuring consistency and cleaner code.
  • Loading branch information
Mpdreamz authored Jan 14, 2025
1 parent ddd64a5 commit 2254d10
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 39 deletions.
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
1 change: 0 additions & 1 deletion src/Elastic.Markdown/Myst/FrontMatter/FrontMatterParser.cs
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
15 changes: 15 additions & 0 deletions tests/Elastic.Markdown.Tests/FrontMatter/YamlFrontMatterTests.cs
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");
}
34 changes: 34 additions & 0 deletions tests/Elastic.Markdown.Tests/Interpolation/InterpolationTests.cs
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();
}
}

0 comments on commit 2254d10

Please sign in to comment.