up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
using StellaOps.Notify.Models;
|
||||
|
||||
namespace StellaOps.Notifier.Worker.Processing;
|
||||
|
||||
/// <summary>
|
||||
/// Simple Handlebars-like template renderer supporting {{property}} and {{#each}} blocks.
|
||||
/// </summary>
|
||||
public sealed partial class SimpleTemplateRenderer : INotifyTemplateRenderer
|
||||
{
|
||||
private static readonly Regex PlaceholderPattern = PlaceholderRegex();
|
||||
private static readonly Regex EachBlockPattern = EachBlockRegex();
|
||||
|
||||
public string Render(NotifyTemplate template, JsonNode? payload)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(template);
|
||||
|
||||
var body = template.Body;
|
||||
if (string.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Process {{#each}} blocks first
|
||||
body = ProcessEachBlocks(body, payload);
|
||||
|
||||
// Then substitute simple placeholders
|
||||
body = SubstitutePlaceholders(body, payload);
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
private static string ProcessEachBlocks(string body, JsonNode? payload)
|
||||
{
|
||||
return EachBlockPattern.Replace(body, match =>
|
||||
{
|
||||
var collectionPath = match.Groups[1].Value.Trim();
|
||||
var innerTemplate = match.Groups[2].Value;
|
||||
|
||||
var collection = ResolvePath(payload, collectionPath);
|
||||
if (collection is not JsonObject obj)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var results = new List<string>();
|
||||
foreach (var (key, value) in obj)
|
||||
{
|
||||
var itemResult = innerTemplate
|
||||
.Replace("{{@key}}", key)
|
||||
.Replace("{{this}}", value?.ToString() ?? string.Empty);
|
||||
results.Add(itemResult);
|
||||
}
|
||||
|
||||
return string.Join(string.Empty, results);
|
||||
});
|
||||
}
|
||||
|
||||
private static string SubstitutePlaceholders(string body, JsonNode? payload)
|
||||
{
|
||||
return PlaceholderPattern.Replace(body, match =>
|
||||
{
|
||||
var path = match.Groups[1].Value.Trim();
|
||||
var resolved = ResolvePath(payload, path);
|
||||
return resolved?.ToString() ?? string.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
private static JsonNode? ResolvePath(JsonNode? root, string path)
|
||||
{
|
||||
if (root is null || string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var segments = path.Split('.');
|
||||
var current = root;
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (current is JsonObject obj && obj.TryGetPropertyValue(segment, out var next))
|
||||
{
|
||||
current = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\{\{([^#/}]+)\}\}", RegexOptions.Compiled)]
|
||||
private static partial Regex PlaceholderRegex();
|
||||
|
||||
[GeneratedRegex(@"\{\{#each\s+([^}]+)\}\}(.*?)\{\{/each\}\}", RegexOptions.Compiled | RegexOptions.Singleline)]
|
||||
private static partial Regex EachBlockRegex();
|
||||
}
|
||||
Reference in New Issue
Block a user