From b725538d648f4247e1ead3ca7ffd74063c090f9c Mon Sep 17 00:00:00 2001
From: Martijn Laarman
Date: Tue, 15 Jul 2025 12:03:44 +0200
Subject: [PATCH 1/6] Add support for variable operator syntax
---
docs/syntax/substitutions.md | 94 ++++++++++++-
docs/syntax/version-variables.md | 21 ++-
.../Builder/ConfigurationFile.cs | 16 ---
.../Substitution/SubstitutionParser.cs | 131 +++++++++++++++++-
.../authoring/Inline/SubstitutionMutations.fs | 52 +++++++
tests/authoring/authoring.fsproj | 1 +
6 files changed, 286 insertions(+), 29 deletions(-)
create mode 100644 tests/authoring/Inline/SubstitutionMutations.fs
diff --git a/docs/syntax/substitutions.md b/docs/syntax/substitutions.md
index d7bd928d1..45adc889f 100644
--- a/docs/syntax/substitutions.md
+++ b/docs/syntax/substitutions.md
@@ -3,6 +3,7 @@ sub:
frontmatter_key: "Front Matter Value"
a-key-with-dashes: "A key with dashes"
version: 7.17.0
+ hello-world: "Hello world!"
---
# Substitutions
@@ -26,7 +27,7 @@ Doing so will result in a build error.
To use the variables in your files, surround them in curly brackets (`{{variable}}`).
-## Example
+### Example
Here are some variable substitutions:
@@ -36,6 +37,97 @@ Here are some variable substitutions:
| {{a-key-with-dashes}} | Front Matter |
| {{a-global-variable}} | `docset.yml` |
+## Mutations
+
+Substitutions can be mutated using a chain of operators seperated by a pipe (`|`).
+
+````markdown
+`{{hello-world | trim | lc | tc}}`
+````
+
+Will trim, lowercase and finally titlecase the contents of the 'hello-world' variable.
+
+### Operators
+
+
+| Operator | Purpose |
+|----------|----------------------------------------------------|
+| `lc` | LowerCase, |
+| `uc` | UpperCase, |
+| `tc` | TitleCase, capitalizes all words, |
+| `c` | Capitalize the first letter, |
+| `kc` | Convert to KebabCase, |
+| `sc` | Convert to SnakeCase, |
+| `cc` | Convert to CamelCase, |
+| `pc` | Convert to PascalCase, |
+| `trim` | Trim common non word characters from start and end |
+
+For variables declaring a semantic version or `Major.Minor` the following operations are also exposed
+
+| Operator | Purpose |
+|----------|------------------------------------------|
+| `M` | Display only the major component |
+| `M.x` | Display major component followed by '.x' |
+| `M.M` | Display only the major and the minor |
+| `M+1` | The next major version |
+| `M.M+1` | The next minor version |
+
+### Example
+
+Given the following frontmatter:
+
+```yaml
+---
+sub:
+ hello-world: "Hello world!"
+---
+```
+
+::::{tab-set}
+
+:::{tab-item} Output
+
+* Lowercase: {{hello-world | lc}}
+* Uppercase: {{hello-world | uc}}
+* TitleCase: {{hello-world | tc}}
+* kebab-case: {{hello-world | kc}}
+* camelCase: {{hello-world | tc | cc}}
+* PascalCase: {{hello-world | pc}}
+* SnakeCase: {{hello-world | sc}}
+* CapitalCase (chained): {{hello-world | lc | c}}
+* Trim: {{hello-world | trim}}
+* M.x: {{version.stack | M.x }}
+* M.M: {{version.stack | M.M }}
+* M: {{version.stack | M }}
+* M+1: {{version.stack | M+1 }}
+* M+1 | M.M: {{version.stack | M+1 | M.M }}
+* M.M+1: {{version.stack | M.M+1 }}
+
+:::
+
+:::{tab-item} Markdown
+
+````markdown
+* Lowercase: {{hello-world | lc}}
+* Uppercase: {{hello-world | uc}}
+* TitleCase: {{hello-world | tc}}
+* kebab-case: {{hello-world | kc}}
+* camelCase: {{hello-world | tc | cc}}
+* PascalCase: {{hello-world | pc}}
+* SnakeCase: {{hello-world | sc}}
+* CapitalCase (chained): {{hello-world | lc | c}}
+* Trim: {{hello-world | trim}}
+* M.x: {{version.stack | M.x }}
+* M.M: {{version.stack | M.M }}
+* M: {{version.stack | M }}
+* M+1: {{version.stack | M+1 }}
+* M+1 | M.M: {{version.stack | M+1 | M.M }}
+* M.M+1: {{version.stack | M.M+1 }}
+````
+:::
+
+::::
+
## Code blocks
Substitutions are supported in code blocks but are disabled by default. Enable substitutions by adding `subs=true` to the code block.
diff --git a/docs/syntax/version-variables.md b/docs/syntax/version-variables.md
index 87cc0cf07..da9a63401 100644
--- a/docs/syntax/version-variables.md
+++ b/docs/syntax/version-variables.md
@@ -10,14 +10,21 @@ Besides the current version, the following suffixes are available:
| Version substitution | result | purpose |
|--------------------------------------|-----------------------------------|-----------------------------------------|
-| `{{versions.stack}}` | {{version.stack}} | Current version |
-| `{{versions.stack.major_minor}}` | {{version.stack.major_minor}} | Current `MAJOR.MINOR` |
-| `{{versions.stack.major_x}}` | {{version.stack.major_x}} | Current `MAJOR.X` |
-| `{{versions.stack.major_component}}` | {{version.stack.major_component}} | Current major component |
-| `{{versions.stack.next_major}}` | {{version.stack.next_major}} | The next major version |
-| `{{versions.stack.next_minor}}` | {{version.stack.next_minor}} | The next minor version |
-| `{{versions.stack.base}}` | {{version.stack.base}} | The first version on the new doc system |
+| `{{version.stack}}` | {{version.stack}} | Current version |
+| `{{version.stack.base}}` | {{version.stack.base}} | The first version on the new doc system |
+## Formatting
+
+Using specialized [mutation operators](substitutions.md#mutations) versions
+can be printed in any kind of ways.
+
+
+| Version substitution | result |
+|------------------------|-----------|
+| `{{version.stack| M.M}}` | {{version.stack|M.M}} |
+| `{{version.stack.base | M }}` | {{version.stack.base | M }} |
+| `{{version.stack | M+1 | M }}` | {{version.stack | M+1 | M }} |
+| `{{version.stack.base | M.M+1 }}` | {{version.stack.base | M.M+1 }} |
## Available versioning schemes.
diff --git a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
index 24cbaa5b8..da4cb39de 100644
--- a/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
+++ b/src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
@@ -163,27 +163,11 @@ public ConfigurationFile(IDocumentationContext context, VersionsConfiguration ve
foreach (var (id, system) in versionsConfig.VersioningSystems)
{
var name = id.ToStringFast(true);
- var current = system.Current;
var key = $"version.{name}";
_substitutions[key] = system.Current;
key = $"version.{name}.base";
_substitutions[key] = system.Base;
-
- key = $"version.{name}.major_minor";
- _substitutions[key] = $"{current.Major:N0}.{current.Minor:N0}";
-
- key = $"version.{name}.major_x";
- _substitutions[key] = $"{current.Major:N0}.x";
-
- key = $"version.{name}.major_component";
- _substitutions[key] = $"{current.Major:N0}";
-
- key = $"version.{name}.next_minor";
- _substitutions[key] = new SemVersion(current.Major, current.Minor + 1, current.Patch, current.Prerelease, current.Metadata);
-
- key = $"version.{name}.next_major";
- _substitutions[key] = new SemVersion(current.Major + 1, current.Minor, current.Patch, current.Prerelease, current.Metadata);
}
var toc = new TableOfContentsConfiguration(this, sourceFile, ScopeDirectory, _context, 0, "");
diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
index 34c20295a..0b7e91deb 100644
--- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
+++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
@@ -3,7 +3,12 @@
// See the LICENSE file in the project root for more information
using System.Buffers;
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using System.Text.Json;
+using Elastic.Documentation;
using Elastic.Markdown.Diagnostics;
using Markdig.Helpers;
using Markdig.Parsers;
@@ -11,27 +16,117 @@
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
+using NetEscapades.EnumGenerators;
namespace Elastic.Markdown.Myst.InlineParsers.Substitution;
[DebuggerDisplay("{GetType().Name} Line: {Line}, Found: {Found}, Replacement: {Replacement}")]
-public class SubstitutionLeaf(string content, bool found, string replacement) : CodeInline(content)
+public class SubstitutionLeaf(string content, bool found, string replacement)
+ : CodeInline(content)
{
public bool Found { get; } = found;
public string Replacement { get; } = replacement;
+ public IReadOnlyCollection? Mutations { get; set; }
+}
+
+[EnumExtensions]
+public enum SubstitutionMutation
+{
+ [Display(Name = "M")] MajorComponent,
+ [Display(Name = "M.x")] MajorX,
+ [Display(Name = "M.M")] MajorMinor,
+ [Display(Name = "M+1")] IncreaseMajor,
+ [Display(Name = "M.M+1")] IncreaseMinor,
+ [Display(Name = "lc")] LowerCase,
+ [Display(Name = "uc")] UpperCase,
+ [Display(Name = "tc")] TitleCase,
+ [Display(Name = "c")] Capitalize,
+ [Display(Name = "kc")] KebabCase,
+ [Display(Name = "sc")] SnakeCase,
+ [Display(Name = "cc")] CamelCase,
+ [Display(Name = "pc")] PascalCase,
+ [Display(Name = "trim")] Trim
}
public class SubstitutionRenderer : HtmlObjectRenderer
{
- protected override void Write(HtmlRenderer renderer, SubstitutionLeaf obj) =>
- renderer.Write(obj.Found ? obj.Replacement : obj.Content);
+ protected override void Write(HtmlRenderer renderer, SubstitutionLeaf leaf)
+ {
+ if (!leaf.Found)
+ {
+ _ = renderer.Write(leaf.Content);
+ return;
+ }
+
+ var replacement = leaf.Replacement;
+ if (leaf.Mutations is null or { Count: 0 })
+ {
+ _ = renderer.Write(replacement);
+ return;
+ }
+
+ foreach (var mutation in leaf.Mutations)
+ {
+ var (success, update) = mutation switch
+ {
+ SubstitutionMutation.MajorComponent => TryGetVersion(replacement, v => $"{v.Major}"),
+ SubstitutionMutation.MajorX => TryGetVersion(replacement, v => $"{v.Major}.x"),
+ SubstitutionMutation.MajorMinor => TryGetVersion(replacement, v => $"{v.Major}.{v.Minor}"),
+ SubstitutionMutation.IncreaseMajor => TryGetVersion(replacement, v => $"{v.Major + 1}.0.0"),
+ SubstitutionMutation.IncreaseMinor => TryGetVersion(replacement, v => $"{v.Major}.{v.Minor + 1}.0"),
+ SubstitutionMutation.LowerCase => (true, replacement.ToLowerInvariant()),
+ SubstitutionMutation.UpperCase => (true, replacement.ToUpperInvariant()),
+ SubstitutionMutation.Capitalize => (true, Capitalize(replacement)),
+ SubstitutionMutation.KebabCase => (true, ToKebabCase(replacement)),
+ SubstitutionMutation.CamelCase => (true, ToCamelCase(replacement)),
+ SubstitutionMutation.PascalCase => (true, ToPascalCase(replacement)),
+ SubstitutionMutation.SnakeCase => (true, ToSnakeCase(replacement)),
+ SubstitutionMutation.TitleCase => (true, TitleCase(replacement)),
+ SubstitutionMutation.Trim => (true, Trim(replacement)),
+ _ => throw new Exception($"encountered an unknown mutation '{mutation.ToStringFast(true)}'")
+ };
+ if (!success)
+ {
+ _ = renderer.Write(leaf.Content);
+ return;
+ }
+ replacement = update;
+ }
+ _ = renderer.Write(replacement);
+ }
+
+ private static string ToCamelCase(string str) => JsonNamingPolicy.CamelCase.ConvertName(str.Replace(" ", string.Empty));
+ private static string ToSnakeCase(string str) => JsonNamingPolicy.SnakeCaseLower.ConvertName(str).Replace(" ", string.Empty);
+ private static string ToKebabCase(string str) => JsonNamingPolicy.KebabCaseLower.ConvertName(str).Replace(" ", string.Empty);
+ private static string ToPascalCase(string str) => TitleCase(str).Replace(" ", string.Empty);
+
+ private static string TitleCase(string str) => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(str);
+
+ private static string Trim(string str) =>
+ str.AsSpan().Trim(['!', ' ', '\t', '\r', '\n', '.', ',', ')', '(', ':', ';', '<', '>', '[', ']']).ToString();
+
+ private static string Capitalize(string input) =>
+ input switch
+ {
+ null => string.Empty,
+ "" => string.Empty,
+ _ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1))
+ };
+
+ private (bool, string) TryGetVersion(string version, Func mutate)
+ {
+ if (!SemVersion.TryParse(version, out var v) && !SemVersion.TryParse(version + ".0", out v))
+ return (false, string.Empty);
+
+ return (true, mutate(v));
+ }
}
public class SubstitutionParser : InlineParser
{
public SubstitutionParser() => OpeningCharacters = ['{'];
- private readonly SearchValues _values = SearchValues.Create(['\r', '\n', ' ', '\t', '}']);
+ private readonly SearchValues _values = SearchValues.Create(['\r', '\n', '\t', '}']);
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
@@ -84,6 +179,10 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
var key = content.ToString().Trim(['{', '}']).ToLowerInvariant();
var found = false;
var replacement = string.Empty;
+ var components = key.Split('|');
+ if (components.Length > 1)
+ key = components[0].Trim();
+
if (context.Substitutions.TryGetValue(key, out var value))
{
found = true;
@@ -100,7 +199,6 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
var start = processor.GetSourcePosition(startPosition, out var line, out var column);
var end = processor.GetSourcePosition(slice.Start);
var sourceSpan = new SourceSpan(start, end);
-
var substitutionLeaf = new SubstitutionLeaf(content.ToString(), found, replacement)
{
Delimiter = '{',
@@ -109,8 +207,31 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
Column = column,
DelimiterCount = openSticks
};
+
if (!found)
processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined");
+ else
+ {
+ List? mutations = null;
+ if (components.Length >= 10)
+ processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} defines too many mutations, none will be applied");
+ else if (components.Length > 1)
+ {
+ foreach (var c in components[1..])
+ {
+ if (SubstitutionMutationExtensions.TryParse(c.Trim(), out var mutation, true, true))
+ {
+ mutations ??= [];
+ mutations.Add(mutation);
+ }
+ else
+ processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Mutation '{c}' on {{{key}}} is undefined");
+ }
+ }
+
+ substitutionLeaf.Mutations = mutations;
+ }
+
if (processor.TrackTrivia)
{
diff --git a/tests/authoring/Inline/SubstitutionMutations.fs b/tests/authoring/Inline/SubstitutionMutations.fs
new file mode 100644
index 000000000..6929efeff
--- /dev/null
+++ b/tests/authoring/Inline/SubstitutionMutations.fs
@@ -0,0 +1,52 @@
+// 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
+
+module ``inline elements``.``substitution mutations``
+
+open Xunit
+open authoring
+
+type ``read sub from yaml frontmatter`` () =
+ let markdown = Setup.Document """---
+sub:
+ hello-world: "Hello world!"
+ versions.stack: 9.1.0
+---
+* Lowercase: {{hello-world | lc}}
+* Uppercase: {{hello-world | uc}}
+* TitleCase: {{hello-world | tc}}
+* kebab-case: {{hello-world | kc}}
+* camelCase: {{hello-world | tc | cc}}
+* PascalCase: {{hello-world | pc}}
+* SnakeCase: {{hello-world | sc}}
+* CapitalCase (chained): {{hello-world | lc | c}}
+* Trim: {{hello-world | trim}}
+* M.x: {{versions.stack | M.x }}
+* M.M: {{versions.stack | M.M }}
+* M: {{versions.stack | M }}
+* M+1: {{versions.stack | M+1 }}
+* M+1 | M.M: {{versions.stack | M+1 | M.M }}
+* M.M+1: {{versions.stack | M.M+1 }}
+"""
+
+ []
+ let ``validate HTML: replace substitution`` () =
+ markdown |> convertsToHtml """
+ - Lowercase: hello world!
+ - Uppercase: HELLO WORLD!
+ - TitleCase: Hello World!
+ - kebab-case: hello-world!
+ - camelCase: helloWorld!
+ - PascalCase: HelloWorld!
+ - SnakeCase: hello_world!
+ - CapitalCase (chained): Hello world!
+ - Trim: Hello world
+ - M.x: 9.x
+ - M.M: 9.1
+ - M: 9
+ - M+1: 10.0.0
+ - M+1 | M.M: 10.0
+ - M.M+1: 9.2.0
+
+ """
diff --git a/tests/authoring/authoring.fsproj b/tests/authoring/authoring.fsproj
index a58fafc22..a0b659cb4 100644
--- a/tests/authoring/authoring.fsproj
+++ b/tests/authoring/authoring.fsproj
@@ -40,6 +40,7 @@
+
From bf4cb8ccdae4a993a8c750f59d0ee98a6020c237 Mon Sep 17 00:00:00 2001
From: Martijn Laarman
Date: Tue, 15 Jul 2025 12:09:10 +0200
Subject: [PATCH 2/6] do not report version variables as unused
---
src/Elastic.Markdown/DocumentationGenerator.cs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/Elastic.Markdown/DocumentationGenerator.cs b/src/Elastic.Markdown/DocumentationGenerator.cs
index 3a9bd9eed..b956771ea 100644
--- a/src/Elastic.Markdown/DocumentationGenerator.cs
+++ b/src/Elastic.Markdown/DocumentationGenerator.cs
@@ -180,7 +180,13 @@ private void HintUnusedSubstitutionKeys()
{
var definedKeys = new HashSet(Context.Configuration.Substitutions.Keys.ToArray());
var inUse = new HashSet(Context.Collector.InUseSubstitutionKeys.Keys);
- var keysNotInUse = definedKeys.Except(inUse).ToArray();
+ var keysNotInUse = definedKeys.Except(inUse)
+ // versions keys are injected
+ .Where(key => !key.StartsWith("version."))
+ // reserving context namespace
+ .Where(key => !key.StartsWith("context."))
+ .ToArray();
+
// If we have less than 20 unused keys, emit them separately,
// Otherwise emit one hint with all of them for brevity
if (keysNotInUse.Length >= 20)
From 0b2ebeb421b69d833405747f69d803b1e96806ad Mon Sep 17 00:00:00 2001
From: Martijn Laarman
Date: Tue, 15 Jul 2025 12:15:58 +0200
Subject: [PATCH 3/6] Allow a space after {{
---
.../Myst/InlineParsers/Substitution/SubstitutionParser.cs | 2 +-
tests/authoring/Inline/Substitutions.fs | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
index 0b7e91deb..ad2623f41 100644
--- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
+++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
@@ -176,7 +176,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
startPosition -= openSticks;
startPosition = Math.Max(startPosition, 0);
- var key = content.ToString().Trim(['{', '}']).ToLowerInvariant();
+ var key = content.ToString().Trim(['{', '}']).Trim().ToLowerInvariant();
var found = false;
var replacement = string.Empty;
var components = key.Split('|');
diff --git a/tests/authoring/Inline/Substitutions.fs b/tests/authoring/Inline/Substitutions.fs
index ed75ba22b..1a7679952 100644
--- a/tests/authoring/Inline/Substitutions.fs
+++ b/tests/authoring/Inline/Substitutions.fs
@@ -46,6 +46,7 @@ The following should be subbed: {{hello-world}}
not a comment
not a {{valid-key}}
not a {substitution}
+The following should be subbed too: {{ hello-world }}
"""
[]
@@ -58,5 +59,7 @@ not a {substitution}
The following should be subbed: Hello World!
not a comment
not a {{valid-key}}
- not a {substitution}
+ not a {substitution}
+ The following should be subbed too: Hello World!
+
"""
From ad4d342ae34eabefe0e651e185d6faffdfb13a81 Mon Sep 17 00:00:00 2001
From: Martijn Laarman
Date: Tue, 15 Jul 2025 12:21:17 +0200
Subject: [PATCH 4/6] carry trimming over to split
---
.../Myst/InlineParsers/Substitution/SubstitutionParser.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
index ad2623f41..ae0f956e6 100644
--- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
+++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
@@ -181,7 +181,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
var replacement = string.Empty;
var components = key.Split('|');
if (components.Length > 1)
- key = components[0].Trim();
+ key = components[0].Trim(['{', '}']).Trim().ToLowerInvariant();
if (context.Substitutions.TryGetValue(key, out var value))
{
From 6e1bee4f22f097de903afcae5f6d7d4cbe4b9283 Mon Sep 17 00:00:00 2001
From: Martijn Laarman
Date: Tue, 15 Jul 2025 12:41:51 +0200
Subject: [PATCH 5/6] Temporarily make variables with spaces that are not found
hints, we used to not do anything here
---
.../ProcessorDiagnosticExtensions.cs | 28 +++++++------------
.../Substitution/SubstitutionParser.cs | 4 ++-
2 files changed, 13 insertions(+), 19 deletions(-)
diff --git a/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs b/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs
index 24e43c434..abcefd00d 100644
--- a/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs
+++ b/src/Elastic.Markdown/Diagnostics/ProcessorDiagnosticExtensions.cs
@@ -14,32 +14,24 @@ public static class ProcessorDiagnosticExtensions
{
private static string CreateExceptionMessage(string message, Exception? e) => message + (e != null ? Environment.NewLine + e : string.Empty);
- public static void EmitError(this InlineProcessor processor, int line, int column, int length, string message)
- {
- var context = processor.GetContext();
- if (context.SkipValidation)
- return;
- var d = new Diagnostic
- {
- Severity = Severity.Error,
- File = processor.GetContext().MarkdownSourcePath.FullName,
- Column = column,
- Line = line,
- Message = message,
- Length = length
- };
- context.Build.Collector.Write(d);
- }
+ public static void EmitError(this InlineProcessor processor, int line, int column, int length, string message) =>
+ processor.Emit(Severity.Error, line, column, length, message);
- public static void EmitWarning(this InlineProcessor processor, int line, int column, int length, string message)
+ public static void EmitWarning(this InlineProcessor processor, int line, int column, int length, string message) =>
+ processor.Emit(Severity.Warning, line, column, length, message);
+
+ public static void EmitHint(this InlineProcessor processor, int line, int column, int length, string message) =>
+ processor.Emit(Severity.Hint, line, column, length, message);
+
+ public static void Emit(this InlineProcessor processor, Severity severity, int line, int column, int length, string message)
{
var context = processor.GetContext();
if (context.SkipValidation)
return;
var d = new Diagnostic
{
- Severity = Severity.Warning,
+ Severity = severity,
File = processor.GetContext().MarkdownSourcePath.FullName,
Column = column,
Line = line,
diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
index ae0f956e6..c7b9804d4 100644
--- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
+++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
@@ -9,6 +9,7 @@
using System.Text;
using System.Text.Json;
using Elastic.Documentation;
+using Elastic.Documentation.Diagnostics;
using Elastic.Markdown.Diagnostics;
using Markdig.Helpers;
using Markdig.Parsers;
@@ -209,7 +210,8 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
};
if (!found)
- processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined");
+ // We temporarily diagnose variable spaces as hints. We used to not read this at all.
+ processor.Emit(key.Contains(' ') ? Severity.Error : Severity.Hint, line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined");
else
{
List? mutations = null;
From c7ae71a4b451ff34cd13251a1499492172b4e8f7 Mon Sep 17 00:00:00 2001
From: Martijn Laarman
Date: Tue, 15 Jul 2025 12:45:20 +0200
Subject: [PATCH 6/6] flip ternary
---
.../Myst/InlineParsers/Substitution/SubstitutionParser.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
index c7b9804d4..7b0f33e67 100644
--- a/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
+++ b/src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs
@@ -211,7 +211,7 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
if (!found)
// We temporarily diagnose variable spaces as hints. We used to not read this at all.
- processor.Emit(key.Contains(' ') ? Severity.Error : Severity.Hint, line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined");
+ processor.Emit(key.Contains(' ') ? Severity.Hint : Severity.Error, line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} is undefined");
else
{
List? mutations = null;