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
2 changes: 1 addition & 1 deletion docs/syntax/_snippets/reusable-snippet.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This is a snippet included on "{{page_title}}".
This is a snippet included on "{{context.page_title}}".

:::{tip}
This is a snippet
Expand Down
3 changes: 2 additions & 1 deletion docs/testing/nested/index.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
sub:
x: "Variable"
navigation_title: "Testing nesting and {{x}}"
---
# Testing Nesting
# Testing Nesting and {{x}}

The files in this directory are used for testing purposes. Do not edit these files unless you are working on tests.

Expand Down
67 changes: 54 additions & 13 deletions src/Elastic.Markdown/Helpers/Interpolation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// 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.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using Elastic.Markdown.Myst;

namespace Elastic.Markdown.Helpers;

Expand All @@ -14,22 +16,60 @@ internal static partial class InterpolationRegex

public static class Interpolation
{
public static bool ReplaceSubstitutions(this ReadOnlySpan<char> span, IReadOnlyDictionary<string, string>? properties, out string? replacement)
public static string ReplaceSubstitutions(
this string input,
ParserContext context
)
{
var span = input.AsSpan();
if (span.ReplaceSubstitutions([context.Substitutions, context.ContextSubstitutions], out var replacement))
return replacement;
return input;
}


public static bool ReplaceSubstitutions(
this ReadOnlySpan<char> span,
ParserContext context,
[NotNullWhen(true)] out string? replacement
) =>
span.ReplaceSubstitutions([context.Substitutions, context.ContextSubstitutions], out replacement);

public static bool ReplaceSubstitutions(
this ReadOnlySpan<char> span,
IReadOnlyDictionary<string, string>? properties,
[NotNullWhen(true)] out string? replacement
)
{
replacement = null;
if (properties is null || properties.Count == 0)
return false;

if (span.IndexOf("}}") < 0)
return false;

if (properties is null || properties.Count == 0)
return span.ReplaceSubstitutions([properties], out replacement);
}

public static bool ReplaceSubstitutions(
this ReadOnlySpan<char> span,
IReadOnlyDictionary<string, string>[] properties,
[NotNullWhen(true)] out string? replacement
)
{
replacement = null;
if (span.IndexOf("}}") < 0)
return false;

var substitutions = properties as Dictionary<string, string>
?? new Dictionary<string, string>(properties, StringComparer.OrdinalIgnoreCase);
if (substitutions.Count == 0)
if (properties.Length == 0 || properties.Sum(p => p.Count) == 0)
return false;

var lookups = properties
.Select(p => p as Dictionary<string, string> ?? new Dictionary<string, string>(p, StringComparer.OrdinalIgnoreCase))
.Select(d => d.GetAlternateLookup<ReadOnlySpan<char>>())
.ToArray();

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

var replaced = false;
foreach (var match in matchSubs)
Expand All @@ -39,14 +79,15 @@ public static bool ReplaceSubstitutions(this ReadOnlySpan<char> span, IReadOnlyD

var spanMatch = span.Slice(match.Index, match.Length);
var key = spanMatch.Trim(['{', '}']);
foreach (var lookup in lookups)
{
if (!lookup.TryGetValue(key, out var value))
continue;

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

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

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

return replaced;
Expand Down
28 changes: 15 additions & 13 deletions src/Elastic.Markdown/Myst/Directives/AdmonitionBlock.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// 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;

namespace Elastic.Markdown.Myst.Directives;

public class DropdownBlock(DirectiveBlockParser parser, ParserContext context) : AdmonitionBlock(parser, "dropdown", context);
Expand All @@ -14,6 +17,11 @@ public AdmonitionBlock(DirectiveBlockParser parser, string admonition, ParserCon
_admonition = admonition;
if (_admonition is "admonition")
Classes = "plain";

var t = Admonition;
var title = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(t);
Title = title;

}

public string Admonition => _admonition;
Expand All @@ -23,26 +31,20 @@ public AdmonitionBlock(DirectiveBlockParser parser, string admonition, ParserCon
public string? Classes { get; protected set; }
public bool? DropdownOpen { get; private set; }

public string Title
{
get
{
var t = Admonition;
var title = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(t);
if (_admonition is "admonition" or "dropdown" && !string.IsNullOrEmpty(Arguments))
title = Arguments;
else if (!string.IsNullOrEmpty(Arguments))
title += $" {Arguments}";
return title;
}
}
public string Title { get; private set; }

public override void FinalizeAndValidate(ParserContext context)
{
CrossReferenceName = Prop("name");
DropdownOpen = TryPropBool("open");
if (DropdownOpen.HasValue)
Classes = "dropdown";

if (_admonition is "admonition" or "dropdown" && !string.IsNullOrEmpty(Arguments))
Title = Arguments;
else if (!string.IsNullOrEmpty(Arguments))
Title += $" {Arguments}";
Title = Title.ReplaceSubstitutions(context);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Elastic.Markdown/Myst/Directives/TabSetBlock.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 Elastic.Markdown.Diagnostics;
using Elastic.Markdown.Helpers;
using Elastic.Markdown.Slices.Directives;

namespace Elastic.Markdown.Myst.Directives;
Expand Down Expand Up @@ -44,7 +45,7 @@ public override void FinalizeAndValidate(ParserContext context)
if (string.IsNullOrWhiteSpace(Arguments))
this.EmitError("{tab-item} requires an argument to name the tab.");

Title = Arguments ?? "{undefined}";
Title = (Arguments ?? "{undefined}").ReplaceSubstitutions(context);
Index = Parent!.IndexOf(this);

var tabSet = Parent as TabSetBlock;
Expand Down
23 changes: 16 additions & 7 deletions src/Elastic.Markdown/Myst/ParserContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,29 @@ public ParserContext(
Build = context;
Configuration = configuration;

foreach (var (key, value) in configuration.Substitutions)
Properties[key.ToLowerInvariant()] = value;

if (frontMatter?.Properties is { } props)
if (frontMatter?.Properties is not { Count: > 0 })
Substitutions = configuration.Substitutions;
else
{
foreach (var (k, value) in props)
var subs = new Dictionary<string, string>(configuration.Substitutions);
foreach (var (k, value) in frontMatter.Properties)
{
var key = k.ToLowerInvariant();
if (configuration.Substitutions.TryGetValue(key, out _))
this.EmitError($"{{{key}}} can not be redeclared in front matter as its a global substitution");
else
Properties[key] = value;
subs[key] = value;
}

Substitutions = subs;
}

var contextSubs = new Dictionary<string, string>();

if (frontMatter?.Title is { } title)
Properties["page_title"] = title;
contextSubs["context.page_title"] = title;

ContextSubstitutions = contextSubs;
}

public ConfigurationFile Configuration { get; }
Expand All @@ -70,4 +76,7 @@ public ParserContext(
public BuildContext Build { get; }
public bool SkipValidation { get; init; }
public Func<IFileInfo, DocumentationFile?>? GetDocumentationFile { get; init; }
public IReadOnlyDictionary<string, string> Substitutions { get; }
public IReadOnlyDictionary<string, string> ContextSubstitutions { get; }

}
13 changes: 11 additions & 2 deletions src/Elastic.Markdown/Myst/Substitution/SubstitutionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Net.Mime;
using System.Runtime.CompilerServices;
using Elastic.Markdown.Diagnostics;
using Markdig;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers;
Expand Down Expand Up @@ -98,6 +99,9 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
if (slice.PeekCharExtra(1) != match)
return false;

if (processor.Context is not ParserContext context)
return false;

Debug.Assert(match is not ('\r' or '\n'));

// Match the opened sticks
Expand Down Expand Up @@ -140,10 +144,15 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
var key = content.ToString().Trim(['{', '}']).ToLowerInvariant();
var found = false;
var replacement = string.Empty;
if (processor.Context?.Properties.TryGetValue(key, out var value) ?? false)
if (context.Substitutions.TryGetValue(key, out var value))
{
found = true;
replacement = value;
}
else if (context.ContextSubstitutions.TryGetValue(key, out value))
{
found = true;
replacement = value.ToString() ?? string.Empty;
replacement = value;
}

var start = processor.GetSourcePosition(startPosition, out var line, out var column);
Expand Down