Skip to content
Closed
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
57 changes: 47 additions & 10 deletions src/TextTemplate/TemplateEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
/// </summary>
public static string Process(string templateString, IDictionary<string, object> data)
{
templateString = PreprocessAssignments(templateString);
templateString = PreprocessWhitespace(templateString);
templateString = PreprocessComments(templateString);
AntlrInputStream inputStream = new(templateString);
Expand Down Expand Up @@ -80,6 +81,14 @@
return Regex.Replace(template, "\\{\\{-?\\s*/\\*.*?\\*/\\s*-?\\}\\}", string.Empty, RegexOptions.Singleline);
}

private static string PreprocessAssignments(string template)
{
return Regex.Replace(template,
"\\{\\{\\s*\\$([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?::=|=)\\s*(.*?)\\s*\\}\\}",
m => $"{{{{ assign \"{m.Groups[1].Value}\" {m.Groups[2].Value} }}}}",
RegexOptions.Singleline);
}

private static IDictionary<string, object> ToDictionary(object model)
{
var dict = new Dictionary<string, object>();
Expand Down Expand Up @@ -141,7 +150,7 @@
if (args.Length == 0) return string.Empty;
string fmt = args[0]?.ToString() ?? string.Empty;
var rest = args.Skip(1).ToArray();
return SprintfFormatter.Format(fmt, rest);

Check warning on line 153 in src/TextTemplate/TemplateEngine.cs

View workflow job for this annotation

GitHub Actions / build

Argument of type 'object?[]' cannot be used for parameter 'args' of type 'object[]' in 'string SprintfFormatter.Format(string format, params object[] args)' due to differences in the nullability of reference types.
},
["html"] = args => System.Net.WebUtility.HtmlEncode(string.Concat(args.Select(a => a?.ToString()))),
["js"] = args => System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(string.Concat(args.Select(a => a?.ToString()))),
Expand Down Expand Up @@ -173,7 +182,7 @@
}
if (current is IDictionary dict)
{
current = dict[key];

Check warning on line 185 in src/TextTemplate/TemplateEngine.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'key' in 'object? IDictionary.this[object key]'.
continue;
}
current = null;
Expand Down Expand Up @@ -522,18 +531,35 @@
{
if (text == "$" || text == "$.")
return _rootCurrent;
text = text.StartsWith("$.") ? text.Substring(2) : text.Substring(1);
if (string.IsNullOrEmpty(text))
return _rootCurrent;
var segmentsRoot = ParseSegments(text);
object? currentRoot = _rootData;
foreach (var seg in segmentsRoot)

if (text.StartsWith("$."))
{
if (currentRoot == null)
return null;
currentRoot = ResolveSegment(currentRoot, seg);
text = text.Substring(2);
if (string.IsNullOrEmpty(text))
return _rootCurrent;
var segs = ParseSegments(text);
object? cur = _rootData;
foreach (var seg in segs)
{
if (cur == null)
return null;
cur = ResolveSegment(cur, seg);
}
return cur;
}
else
{
text = text.Substring(1);
var segs = ParseSegments(text);
object? cur = _data;
foreach (var seg in segs)
{
if (cur == null)
return null;
cur = ResolveSegment(cur, seg);
}
return cur;
}
return currentRoot;
}

var segments = ParseSegments(text);
Expand Down Expand Up @@ -653,6 +679,17 @@

private object? ApplyPipelineFunction(string name, params object?[] args)
{
if (name == "assign")
{
if (args.Length >= 2)
{
string varName = args[0]?.ToString() ?? string.Empty;
_data[varName] = args[1]!;
return string.Empty;
}
return string.Empty;
}

if (PipelineFuncs.TryGetValue(name, out var fn))
return fn(args);
return args.Length > 0 ? args[0] : null;
Expand Down
27 changes: 27 additions & 0 deletions tests/TextTemplate.Tests/TemplateEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -670,4 +670,31 @@ public void Range_DotAndRootAccess()
});
result.ShouldBe("- a root;- b root;");
}

[Fact]
public void VariableDeclaration()
{
const string tmpl = "{{ $g := \"Hi\" }}{{ $g }}";
var result = TemplateEngine.Process(tmpl, new Dictionary<string, object>());
result.ShouldBe("Hi");
}

[Fact]
public void VariableReassignment()
{
const string tmpl = "{{ $g := \"Hi\" }}{{ $g = \"Bye\" }}{{ $g }}";
var result = TemplateEngine.Process(tmpl, new Dictionary<string, object>());
result.ShouldBe("Bye");
}

[Fact]
public void RangeLoopWithIndexAndValue()
{
const string tmpl = "{{ range $i, $v := .Items }}{{ $i }}: {{ $v }};{{ end }}";
var result = TemplateEngine.Process(tmpl, new Dictionary<string, object>
{
["Items"] = new[] { "a", "b" }
});
result.ShouldBe("0: a;1: b;");
}
}
Loading