Skip to content

Commit ee27f2a

Browse files
committed
Refactor
1 parent cce710c commit ee27f2a

File tree

3 files changed

+124
-90
lines changed

3 files changed

+124
-90
lines changed

src/Elastic.Markdown/Helpers/Interpolation.cs

Lines changed: 14 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -72,93 +72,28 @@ private static bool ReplaceSubstitutions(
7272
var spanMatch = span.Slice(match.Index, match.Length);
7373
var fullKey = spanMatch.Trim(['{', '}']);
7474

75-
// Handle mutation operators (same logic as SubstitutionParser)
76-
var components = fullKey.ToString().Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
77-
var key = components.Length > 1 ? components[0].Trim() : fullKey.ToString();
75+
// Enhanced mutation support: parse key and mutations using shared utility
76+
var (cleanKey, mutations) = SubstitutionMutationHelper.ParseKeyWithMutations(fullKey.ToString());
77+
7878
foreach (var lookup in lookups)
7979
{
80-
if (!lookup.TryGetValue(key, out var value))
81-
continue;
82-
83-
collector?.CollectUsedSubstitutionKey(key);
84-
85-
// Apply mutations if present
86-
if (components.Length > 1)
80+
if (lookup.TryGetValue(cleanKey, out var value))
8781
{
88-
value = ApplyMutationsUsingExistingSystem(value, components[1..]);
89-
}
82+
// Apply mutations if present using shared utility
83+
if (mutations.Length > 0)
84+
{
85+
value = SubstitutionMutationHelper.ApplyMutations(value, mutations);
86+
}
9087

91-
replacement ??= span.ToString();
92-
replacement = replacement.Replace(spanMatch.ToString(), value);
93-
replaced = true;
94-
}
95-
}
88+
collector?.CollectUsedSubstitutionKey(cleanKey);
9689

97-
return replaced;
98-
}
99-
100-
private static string ApplyMutationsUsingExistingSystem(string value, string[] mutations)
101-
{
102-
var result = value;
103-
foreach (var mutationStr in mutations)
104-
{
105-
var trimmedMutation = mutationStr.Trim();
106-
if (SubstitutionMutationExtensions.TryParse(trimmedMutation, out var mutation, true, true))
107-
{
108-
// Use the same logic as SubstitutionRenderer.Write
109-
var (success, update) = mutation switch
110-
{
111-
SubstitutionMutation.MajorComponent => TryGetVersion(result, v => $"{v.Major}"),
112-
SubstitutionMutation.MajorX => TryGetVersion(result, v => $"{v.Major}.x"),
113-
SubstitutionMutation.MajorMinor => TryGetVersion(result, v => $"{v.Major}.{v.Minor}"),
114-
SubstitutionMutation.IncreaseMajor => TryGetVersion(result, v => $"{v.Major + 1}.0.0"),
115-
SubstitutionMutation.IncreaseMinor => TryGetVersion(result, v => $"{v.Major}.{v.Minor + 1}.0"),
116-
SubstitutionMutation.LowerCase => (true, result.ToLowerInvariant()),
117-
SubstitutionMutation.UpperCase => (true, result.ToUpperInvariant()),
118-
SubstitutionMutation.Capitalize => (true, Capitalize(result)),
119-
SubstitutionMutation.KebabCase => (true, ToKebabCase(result)),
120-
SubstitutionMutation.CamelCase => (true, ToCamelCase(result)),
121-
SubstitutionMutation.PascalCase => (true, ToPascalCase(result)),
122-
SubstitutionMutation.SnakeCase => (true, ToSnakeCase(result)),
123-
SubstitutionMutation.TitleCase => (true, TitleCase(result)),
124-
SubstitutionMutation.Trim => (true, Trim(result)),
125-
_ => (false, result)
126-
};
127-
if (success)
128-
{
129-
result = update;
90+
replacement ??= span.ToString();
91+
replacement = replacement.Replace(spanMatch.ToString(), value);
92+
replaced = true;
13093
}
13194
}
13295
}
133-
return result;
134-
}
13596

136-
private static (bool Success, string Result) TryGetVersion(string version, Func<SemVersion, string> transform)
137-
{
138-
if (!SemVersion.TryParse(version, out var v) && !SemVersion.TryParse(version + ".0", out v))
139-
return (false, version);
140-
return (true, transform(v));
97+
return replaced;
14198
}
142-
143-
// These methods match the exact implementation in SubstitutionRenderer
144-
private static string Capitalize(string input) =>
145-
input switch
146-
{
147-
null => string.Empty,
148-
"" => string.Empty,
149-
_ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1))
150-
};
151-
152-
private static string ToKebabCase(string str) => JsonNamingPolicy.KebabCaseLower.ConvertName(str).Replace(" ", string.Empty);
153-
154-
private static string ToCamelCase(string str) => JsonNamingPolicy.CamelCase.ConvertName(str).Replace(" ", string.Empty);
155-
156-
private static string ToPascalCase(string str) => TitleCase(str).Replace(" ", string.Empty);
157-
158-
private static string ToSnakeCase(string str) => JsonNamingPolicy.SnakeCaseLower.ConvertName(str).Replace(" ", string.Empty);
159-
160-
private static string TitleCase(string str) => System.Globalization.CultureInfo.InvariantCulture.TextInfo.ToTitleCase(str);
161-
162-
private static string Trim(string str) =>
163-
str.AsSpan().Trim(['!', ' ', '\t', '\r', '\n', '.', ',', ')', '(', ':', ';', '<', '>', '[', ']']).ToString();
16499
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
using System.Linq;
7+
using System.Text.Json;
8+
using Elastic.Documentation;
9+
10+
namespace Elastic.Markdown.Myst.InlineParsers.Substitution;
11+
12+
/// <summary>
13+
/// Shared utility for parsing and applying substitution mutations
14+
/// </summary>
15+
public static class SubstitutionMutationHelper
16+
{
17+
/// <summary>
18+
/// Parses a substitution key with mutations and returns the key and mutation components
19+
/// </summary>
20+
/// <param name="rawKey">The raw substitution key (e.g., "version.stack | M.M")</param>
21+
/// <returns>A tuple containing the cleaned key and array of mutation strings</returns>
22+
public static (string Key, string[] Mutations) ParseKeyWithMutations(string rawKey)
23+
{
24+
// Improved handling of pipe-separated components with better whitespace handling
25+
var components = rawKey.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
26+
var key = components[0].Trim();
27+
var mutations = components.Length > 1 ? components[1..] : [];
28+
29+
return (key, mutations);
30+
}
31+
32+
/// <summary>
33+
/// Applies mutations to a value using the existing SubstitutionMutation system
34+
/// </summary>
35+
/// <param name="value">The original value to transform</param>
36+
/// <param name="mutations">Array of mutation strings to apply</param>
37+
/// <returns>The transformed value</returns>
38+
public static string ApplyMutations(string value, string[] mutations)
39+
{
40+
var result = value;
41+
foreach (var mutationStr in mutations)
42+
{
43+
var trimmedMutation = mutationStr.Trim();
44+
if (SubstitutionMutationExtensions.TryParse(trimmedMutation, out var mutation, true, true))
45+
{
46+
// Use the same logic as SubstitutionRenderer.Write
47+
var (success, update) = mutation switch
48+
{
49+
SubstitutionMutation.MajorComponent => TryGetVersion(result, v => $"{v.Major}"),
50+
SubstitutionMutation.MajorX => TryGetVersion(result, v => $"{v.Major}.x"),
51+
SubstitutionMutation.MajorMinor => TryGetVersion(result, v => $"{v.Major}.{v.Minor}"),
52+
SubstitutionMutation.IncreaseMajor => TryGetVersion(result, v => $"{v.Major + 1}.0.0"),
53+
SubstitutionMutation.IncreaseMinor => TryGetVersion(result, v => $"{v.Major}.{v.Minor + 1}.0"),
54+
SubstitutionMutation.LowerCase => (true, result.ToLowerInvariant()),
55+
SubstitutionMutation.UpperCase => (true, result.ToUpperInvariant()),
56+
SubstitutionMutation.Capitalize => (true, Capitalize(result)),
57+
SubstitutionMutation.KebabCase => (true, ToKebabCase(result)),
58+
SubstitutionMutation.CamelCase => (true, ToCamelCase(result)),
59+
SubstitutionMutation.PascalCase => (true, ToPascalCase(result)),
60+
SubstitutionMutation.SnakeCase => (true, ToSnakeCase(result)),
61+
SubstitutionMutation.TitleCase => (true, TitleCase(result)),
62+
SubstitutionMutation.Trim => (true, Trim(result)),
63+
_ => (false, result)
64+
};
65+
if (success)
66+
{
67+
result = update;
68+
}
69+
}
70+
}
71+
return result;
72+
}
73+
74+
private static (bool Success, string Result) TryGetVersion(string version, Func<SemVersion, string> transform)
75+
{
76+
if (!SemVersion.TryParse(version, out var v) && !SemVersion.TryParse(version + ".0", out v))
77+
return (false, version);
78+
return (true, transform(v));
79+
}
80+
81+
// These methods match the exact implementation in SubstitutionRenderer
82+
private static string Capitalize(string input) =>
83+
input switch
84+
{
85+
null => string.Empty,
86+
"" => string.Empty,
87+
_ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1))
88+
};
89+
90+
private static string ToKebabCase(string str) => JsonNamingPolicy.KebabCaseLower.ConvertName(str).Replace(" ", string.Empty);
91+
92+
private static string ToCamelCase(string str) => JsonNamingPolicy.CamelCase.ConvertName(str).Replace(" ", string.Empty);
93+
94+
private static string ToPascalCase(string str) => JsonNamingPolicy.CamelCase.ConvertName(str).Replace(" ", string.Empty);
95+
96+
private static string ToSnakeCase(string str) => JsonNamingPolicy.SnakeCaseLower.ConvertName(str).Replace(" ", string.Empty);
97+
98+
private static string TitleCase(string str) => str.Split(' ').Select(word => Capitalize(word)).Aggregate((a, b) => $"{a} {b}");
99+
100+
private static string Trim(string str) => str.Trim();
101+
}

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -177,14 +177,12 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
177177
startPosition -= openSticks;
178178
startPosition = Math.Max(startPosition, 0);
179179

180-
var key = content.ToString().Trim(['{', '}']).Trim().ToLowerInvariant();
180+
var rawKey = content.ToString().Trim(['{', '}']).Trim().ToLowerInvariant();
181181
var found = false;
182182
var replacement = string.Empty;
183183

184-
// Improved handling of pipe-separated components with better whitespace handling
185-
var components = key.Split('|', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
186-
if (components.Length > 1)
187-
key = components[0].Trim();
184+
// Use shared mutation parsing logic
185+
var (key, mutationStrings) = SubstitutionMutationHelper.ParseKeyWithMutations(rawKey);
188186

189187
if (context.Substitutions.TryGetValue(key, out var value))
190188
{
@@ -217,21 +215,21 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
217215
else
218216
{
219217
List<SubstitutionMutation>? mutations = null;
220-
if (components.Length >= 10)
218+
if (mutationStrings.Length >= 10)
221219
processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Substitution key {{{key}}} defines too many mutations, none will be applied");
222-
else if (components.Length > 1)
220+
else if (mutationStrings.Length > 0)
223221
{
224-
foreach (var c in components[1..])
222+
foreach (var mutationStr in mutationStrings)
225223
{
226224
// Ensure mutation string is properly trimmed and normalized
227-
var mutationStr = c.Trim();
228-
if (SubstitutionMutationExtensions.TryParse(mutationStr, out var mutation, true, true))
225+
var trimmedMutation = mutationStr.Trim();
226+
if (SubstitutionMutationExtensions.TryParse(trimmedMutation, out var mutation, true, true))
229227
{
230228
mutations ??= [];
231229
mutations.Add(mutation);
232230
}
233231
else
234-
processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Mutation '{mutationStr}' on {{{key}}} is undefined");
232+
processor.EmitError(line + 1, column + 3, substitutionLeaf.Span.Length - 3, $"Mutation '{trimmedMutation}' on {{{key}}} is undefined");
235233
}
236234
}
237235

0 commit comments

Comments
 (0)