Skip to content

Commit b338d3e

Browse files
committed
Add tests and fix subs
1 parent 447e529 commit b338d3e

File tree

3 files changed

+226
-5
lines changed

3 files changed

+226
-5
lines changed

src/Elastic.Markdown/Helpers/Interpolation.cs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.Diagnostics.CodeAnalysis;
6+
using System.Globalization;
67
using System.Text.RegularExpressions;
78
using Elastic.Documentation.Diagnostics;
89
using Elastic.Markdown.Myst;
@@ -67,14 +68,24 @@ private static bool ReplaceSubstitutions(
6768
continue;
6869

6970
var spanMatch = span.Slice(match.Index, match.Length);
70-
var key = spanMatch.Trim(['{', '}']);
71+
var fullKey = spanMatch.Trim(['{', '}']);
72+
73+
// Handle mutation operators (same logic as SubstitutionParser)
74+
var components = fullKey.ToString().Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
75+
var key = components.Length > 1 ? components[0].Trim() : fullKey.ToString();
7176
foreach (var lookup in lookups)
7277
{
7378
if (!lookup.TryGetValue(key, out var value))
7479
continue;
7580

7681
collector?.CollectUsedSubstitutionKey(key);
7782

83+
// Apply mutations if present
84+
if (components.Length > 1)
85+
{
86+
value = ApplyMutations(value, components[1..]);
87+
}
88+
7889
replacement ??= span.ToString();
7990
replacement = replacement.Replace(spanMatch.ToString(), value);
8091
replaced = true;
@@ -83,4 +94,63 @@ private static bool ReplaceSubstitutions(
8394

8495
return replaced;
8596
}
97+
98+
private static string ApplyMutations(string value, string[] mutations)
99+
{
100+
var result = value;
101+
foreach (var mutation in mutations)
102+
{
103+
var mutationStr = mutation.Trim();
104+
result = mutationStr switch
105+
{
106+
"M" => TryGetVersionMajor(result),
107+
"M.M" => TryGetVersionMajorMinor(result),
108+
"M.x" => TryGetVersionMajorX(result),
109+
"M+1" => TryGetVersionIncreaseMajor(result),
110+
"M.M+1" => TryGetVersionIncreaseMinor(result),
111+
"lc" => result.ToLowerInvariant(),
112+
"uc" => result.ToUpperInvariant(),
113+
"tc" => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(result.ToLowerInvariant()),
114+
"c" => char.ToUpperInvariant(result[0]) + result[1..].ToLowerInvariant(),
115+
"trim" => result.Trim(),
116+
_ => result // Unknown mutation, return unchanged
117+
};
118+
}
119+
return result;
120+
}
121+
122+
private static string TryGetVersionMajor(string version)
123+
{
124+
if (Version.TryParse(version, out var v))
125+
return v.Major.ToString();
126+
return version;
127+
}
128+
129+
private static string TryGetVersionMajorMinor(string version)
130+
{
131+
if (Version.TryParse(version, out var v))
132+
return $"{v.Major}.{v.Minor}";
133+
return version;
134+
}
135+
136+
private static string TryGetVersionMajorX(string version)
137+
{
138+
if (Version.TryParse(version, out var v))
139+
return $"{v.Major}.x";
140+
return version;
141+
}
142+
143+
private static string TryGetVersionIncreaseMajor(string version)
144+
{
145+
if (Version.TryParse(version, out var v))
146+
return $"{v.Major + 1}.0.0";
147+
return version;
148+
}
149+
150+
private static string TryGetVersionIncreaseMinor(string version)
151+
{
152+
if (Version.TryParse(version, out var v))
153+
return $"{v.Major}.{v.Minor + 1}.0";
154+
return version;
155+
}
86156
}

src/Elastic.Markdown/Myst/InlineParsers/Substitution/SubstitutionParser.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,11 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
180180
var key = content.ToString().Trim(['{', '}']).Trim().ToLowerInvariant();
181181
var found = false;
182182
var replacement = string.Empty;
183-
var components = key.Split('|');
183+
184+
// Improved handling of pipe-separated components with better whitespace handling
185+
var components = key.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
184186
if (components.Length > 1)
185-
key = components[0].Trim(['{', '}']).Trim().ToLowerInvariant();
187+
key = components[0].Trim();
186188

187189
if (context.Substitutions.TryGetValue(key, out var value))
188190
{
@@ -221,13 +223,15 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
221223
{
222224
foreach (var c in components[1..])
223225
{
224-
if (SubstitutionMutationExtensions.TryParse(c.Trim(), out var mutation, true, true))
226+
// Ensure mutation string is properly trimmed and normalized
227+
var mutationStr = c.Trim();
228+
if (SubstitutionMutationExtensions.TryParse(mutationStr, out var mutation, true, true))
225229
{
226230
mutations ??= [];
227231
mutations.Add(mutation);
228232
}
229233
else
230-
processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Mutation '{c}' on {{{key}}} is undefined");
234+
processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Mutation '{mutationStr}' on {{{key}}} is undefined");
231235
}
232236
}
233237

tests/Elastic.Markdown.Tests/Inline/SubstitutionTest.cs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,150 @@ public void OnlySeesGlobalVariable() =>
205205
Html.Should().NotContain("title=\"{{hello-world}}\"")
206206
.And.Contain("title=\"Hello World\"");
207207
}
208+
209+
public class MutationOperatorTest(ITestOutputHelper output) : InlineTest(output,
210+
"""
211+
---
212+
sub:
213+
version: "9.0.4"
214+
---
215+
216+
# Testing Mutation Operators
217+
218+
Version: {{version|M.M}}
219+
Version with space: {{version | M.M}}
220+
Major only: {{version|M}}
221+
Major only with space: {{version | M}}
222+
Major.x: {{version|M.x}}
223+
Major.x with space: {{version | M.x}}
224+
Increase major: {{version|M+1}}
225+
Increase major with space: {{version | M+1}}
226+
Increase minor: {{version|M.M+1}}
227+
Increase minor with space: {{version | M.M+1}}
228+
"""
229+
)
230+
{
231+
[Fact]
232+
public void MutationOperatorsWorkWithAndWithoutSpaces()
233+
{
234+
// Both versions with and without spaces should render the same way
235+
Html.Should().Contain("Version: 9.0")
236+
.And.Contain("Version with space: 9.0")
237+
.And.Contain("Major only: 9")
238+
.And.Contain("Major only with space: 9")
239+
.And.Contain("Major.x: 9.x")
240+
.And.Contain("Major.x with space: 9.x")
241+
.And.Contain("Increase major: 10.0.0")
242+
.And.Contain("Increase major with space: 10.0.0")
243+
.And.Contain("Increase minor: 9.1.0")
244+
.And.Contain("Increase minor with space: 9.1.0");
245+
}
246+
247+
[Fact]
248+
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
249+
}
250+
251+
public class MultipleMutationOperatorsTest(ITestOutputHelper output) : InlineTest(output,
252+
"""
253+
---
254+
sub:
255+
version: "9.0.4"
256+
product: "Elasticsearch"
257+
---
258+
259+
# Testing Multiple Mutation Operators
260+
261+
Version: {{version|M.M|lc}}
262+
Version with spaces: {{version | M.M | lc}}
263+
Product: {{product|uc}}
264+
Product with spaces: {{product | uc}}
265+
"""
266+
)
267+
{
268+
[Fact]
269+
public void MultipleMutationOperatorsWorkWithAndWithoutSpaces()
270+
{
271+
// Both versions with and without spaces should render the same way
272+
Html.Should().Contain("Version: 9.0")
273+
.And.Contain("Version with spaces: 9.0")
274+
.And.Contain("Product: ELASTICSEARCH")
275+
.And.Contain("Product with spaces: ELASTICSEARCH");
276+
}
277+
278+
[Fact]
279+
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
280+
}
281+
282+
public class MutationOperatorsInLinksTest(ITestOutputHelper output) : InlineTest(output,
283+
"""
284+
---
285+
sub:
286+
version: "9.0.4"
287+
product: "Elasticsearch"
288+
---
289+
290+
# Testing Mutation Operators in Links
291+
292+
[Link with mutation operator](https://www.elastic.co/guide/en/elasticsearch/reference/{{version|M.M}}/index.html)
293+
[Link with mutation operator and space](https://www.elastic.co/guide/en/elasticsearch/reference/{{version | M.M}}/index.html)
294+
[Link text with mutation]({{product|uc}} {{version|M.M}})
295+
[Link text with mutation and space]({{product | uc}} {{version | M.M}})
296+
297+
"""
298+
)
299+
{
300+
[Fact]
301+
public void MutationOperatorsWorkInLinks()
302+
{
303+
// Check URL mutations
304+
Html.Should().Contain("href=\"https://www.elastic.co/guide/en/elasticsearch/reference/9.0/index.html\"")
305+
.And.NotContain("{{version|M.M}}")
306+
.And.NotContain("{{version | M.M}}");
307+
308+
// Check link text mutations
309+
Html.Should().Contain("ELASTICSEARCH 9.0")
310+
.And.NotContain("{{product|uc}}")
311+
.And.NotContain("{{version|M.M}}");
312+
313+
// Check link text mutations with spaces
314+
Html.Should().Contain("ELASTICSEARCH 9.0")
315+
.And.NotContain("{{product | uc}}")
316+
.And.NotContain("{{version | M.M}}");
317+
}
318+
319+
[Fact]
320+
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
321+
}
322+
323+
public class MutationOperatorsInCodeBlocksTest(ITestOutputHelper output) : BlockTest<EnhancedCodeBlock>(output,
324+
"""
325+
---
326+
sub:
327+
version: "9.0.4"
328+
product: "Elasticsearch"
329+
---
330+
331+
# Testing Mutation Operators in Code Blocks
332+
333+
```{code} sh subs=true
334+
# Install Elasticsearch {{version|M.M}}
335+
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{{version|M.M}}-linux-x86_64.tar.gz
336+
337+
# With space in mutation
338+
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-{{version | M.M}}-linux-x86_64.tar.gz
339+
```
340+
"""
341+
)
342+
{
343+
[Fact]
344+
public void MutationOperatorsWorkInCodeBlocks()
345+
{
346+
Html.Should().Contain("# Install Elasticsearch 9.0")
347+
.And.Contain("elasticsearch-9.0-linux-x86_64.tar.gz")
348+
.And.NotContain("{{version|M.M}}")
349+
.And.NotContain("{{version | M.M}}");
350+
}
351+
352+
[Fact]
353+
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
354+
}

0 commit comments

Comments
 (0)