Skip to content

Commit 412f799

Browse files
committed
Allow customizing FluidParser construction
1 parent f68e664 commit 412f799

4 files changed

Lines changed: 126 additions & 66 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Reflection;
2+
using Fluid;
3+
using NJsonSchema.CodeGeneration.CSharp;
4+
5+
namespace NJsonSchema.CodeGeneration.Tests;
6+
7+
public class TemplateFactoryTests
8+
{
9+
[Fact]
10+
public void Can_create_custom_factory_with_custom_liquid_parser()
11+
{
12+
var generatorSettings = new CSharpGeneratorSettings
13+
{
14+
TemplateDirectory = "Templates",
15+
};
16+
17+
var factory = new CustomTemplateFactory(generatorSettings, [typeof(CSharpGeneratorSettings).Assembly]);
18+
generatorSettings.TemplateFactory = factory;
19+
20+
var template = factory.CreateTemplate("csharp", "inline-liquid", new { });
21+
22+
var templateResult = template.Render();
23+
24+
Assert.Equal("WELCOME TO THE LIQUID TAG", templateResult);
25+
}
26+
}
27+
28+
public sealed class CustomTemplateFactory : DefaultTemplateFactory
29+
{
30+
private readonly LiquidParser _parser;
31+
32+
public CustomTemplateFactory(CodeGeneratorSettingsBase settings, Assembly[] assemblies) : base(settings, assemblies)
33+
{
34+
_parser = new LiquidParser(new FluidParserOptions { AllowLiquidTag = true });
35+
}
36+
37+
protected override FluidParser CreateParser() => _parser;
38+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{%
2+
liquid
3+
echo
4+
'welcome ' | upcase
5+
echo 'to the liquid tag'
6+
| upcase
7+
%}

src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs

Lines changed: 16 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@
1010
using System.Globalization;
1111
using System.Linq;
1212
using System.Reflection;
13-
using System.Text.Encodings.Web;
1413
using System.Text.RegularExpressions;
1514
using Fluid;
16-
using Fluid.Ast;
1715
using Fluid.Values;
18-
using Parlot.Fluent;
1916

2017
namespace NJsonSchema.CodeGeneration
2118
{
2219
/// <summary>The default template factory which loads templates from embedded resources.</summary>
2320
public partial class DefaultTemplateFactory : ITemplateFactory
2421
{
22+
internal const string TemplateTagName = "template";
23+
24+
private static readonly LiquidParser LiquidParser = new(new FluidParserOptions());
25+
2526
private readonly CodeGeneratorSettingsBase _settings;
2627
private readonly Assembly[] _assemblies;
2728
private readonly Func<string, string, string, string> _templateContentLoader;
@@ -44,13 +45,15 @@ public DefaultTemplateFactory(CodeGeneratorSettingsBase settings, Assembly[] ass
4445
/// <exception cref="InvalidOperationException">Could not load template.</exception>
4546
public ITemplate CreateTemplate(string language, string template, object model)
4647
{
47-
return new LiquidTemplate(
48-
language,
49-
template,
50-
_templateContentLoader,
51-
model,
52-
GetToolchainVersion(),
53-
_settings);
48+
return new LiquidTemplate(CreateParser(), language, template, _templateContentLoader, model, GetToolchainVersion(), _settings);
49+
}
50+
51+
/// <summary>
52+
/// Returns an instance of <see cref="FluidParser"/> that will be used when parsing template content.
53+
/// </summary>
54+
protected virtual FluidParser CreateParser()
55+
{
56+
return LiquidParser;
5457
}
5558

5659
/// <summary>Gets the current toolchain version.</summary>
@@ -115,13 +118,10 @@ private sealed partial class LiquidTemplate : ITemplate
115118
{
116119
private readonly record struct TemplateKey(string Language, string Template, string TemplateDirectory);
117120

118-
internal const string TemplateTagName = "template";
119121
private static readonly ConcurrentDictionary<TemplateKey, IFluidTemplate> Templates = new();
120122

121123
static LiquidTemplate()
122124
{
123-
// thread-safe
124-
_parser = new LiquidParser();
125125
_templateOptions = new TemplateOptions
126126
{
127127
MemberAccessStrategy = new UnsafeMemberAccessStrategy(),
@@ -135,14 +135,14 @@ static LiquidTemplate()
135135
_templateOptions.Filters.AddFilter("literal", LiquidFilters.Literal);
136136
}
137137

138+
private readonly FluidParser _parser;
138139
private readonly string _language;
139140
private readonly string _template;
140141
private readonly Func<string, string, string, string> _templateContentLoader;
141142
private readonly object _model;
142143
private readonly string _toolchainVersion;
143144
private readonly CodeGeneratorSettingsBase _settings;
144145

145-
private static readonly LiquidParser _parser;
146146
private static readonly TemplateOptions _templateOptions;
147147

148148
private const string TabCountRegexString = @"(\s*)?\{%(-)?\s+template\s+([a-zA-Z0-9_.]+)(\s*?.*?)\s(-)?%}";
@@ -163,13 +163,15 @@ static LiquidTemplate()
163163
#endif
164164

165165
public LiquidTemplate(
166+
FluidParser parser,
166167
string language,
167168
string template,
168169
Func<string, string, string, string> templateContentLoader,
169170
object model,
170171
string toolchainVersion,
171172
CodeGeneratorSettingsBase settings)
172173
{
174+
_parser = parser;
173175
_language = language;
174176
_template = template;
175177
_templateContentLoader = templateContentLoader;
@@ -323,58 +325,6 @@ public static ValueTask<FluidValue> Literal(FluidValue input, FilterArguments ar
323325
}
324326
}
325327

326-
private sealed class LiquidParser : FluidParser
327-
{
328-
internal const string LanguageKey = "__language";
329-
internal const string TemplateKey = "__template";
330-
internal const string SettingsKey = "__settings";
331-
332-
public LiquidParser()
333-
{
334-
RegisterParserTag(LiquidTemplate.TemplateTagName, Parsers.OneOrMany(Primary), RenderTemplate);
335-
}
336-
337-
private static ValueTask<Completion> RenderTemplate(
338-
IReadOnlyList<Expression> arguments,
339-
TextWriter writer,
340-
TextEncoder encoder,
341-
TemplateContext context)
342-
{
343-
var templateName = ((LiteralExpression) arguments[0]).Value.ToStringValue();
344-
345-
var tabCount = -1;
346-
if (arguments.Count > 1 && arguments[1] is LiteralExpression literalExpression)
347-
{
348-
tabCount = (int) literalExpression.Value.ToNumberValue();
349-
}
350-
351-
var settings = (CodeGeneratorSettingsBase) context.AmbientValues[SettingsKey];
352-
var language = (string) context.AmbientValues[LanguageKey];
353-
templateName = !string.IsNullOrEmpty(templateName)
354-
? templateName
355-
: (string) context.AmbientValues[TemplateKey] + "!";
356-
357-
var template = settings.TemplateFactory.CreateTemplate(language, templateName, context);
358-
var output = template.Render();
359-
360-
if (string.IsNullOrWhiteSpace(output))
361-
{
362-
// signal cleanup
363-
writer.Write("__EMPTY-TEMPLATE__");
364-
}
365-
else if (tabCount > 0)
366-
{
367-
ConversionUtilities.Tab(output, tabCount, writer);
368-
}
369-
else
370-
{
371-
writer.Write(output);
372-
}
373-
374-
return new ValueTask<Completion>(Completion.Normal);
375-
}
376-
}
377-
378328
/// <summary>
379329
/// Version that allows all access, safe as models are handled by NJsonSchema.
380330
/// </summary>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Text.Encodings.Web;
2+
using Fluid;
3+
using Fluid.Ast;
4+
using Parlot.Fluent;
5+
6+
namespace NJsonSchema.CodeGeneration;
7+
8+
/// <summary>
9+
/// A custom Fluid parser that can handle NJsonSchema Fluid extensions and customizations.
10+
/// </summary>
11+
public sealed class LiquidParser : FluidParser
12+
{
13+
internal const string LanguageKey = "__language";
14+
internal const string TemplateKey = "__template";
15+
internal const string SettingsKey = "__settings";
16+
17+
/// <summary>
18+
/// Creates a new instance of custom Liquid parser that can handle NJsonSchema templates.
19+
/// </summary>
20+
/// <param name="options"></param>
21+
public LiquidParser(FluidParserOptions options) : base(options)
22+
{
23+
RegisterParserTag(DefaultTemplateFactory.TemplateTagName, Parsers.OneOrMany(Primary), RenderTemplate);
24+
}
25+
26+
private static ValueTask<Completion> RenderTemplate(
27+
IReadOnlyList<Expression> arguments,
28+
TextWriter writer,
29+
TextEncoder encoder,
30+
TemplateContext context)
31+
{
32+
var templateName = ((LiteralExpression)arguments[0]).Value.ToStringValue();
33+
34+
var tabCount = -1;
35+
if (arguments.Count > 1 && arguments[1] is LiteralExpression literalExpression)
36+
{
37+
tabCount = (int)literalExpression.Value.ToNumberValue();
38+
}
39+
40+
var settings = (CodeGeneratorSettingsBase)context.AmbientValues[SettingsKey];
41+
var language = (string)context.AmbientValues[LanguageKey];
42+
templateName = !string.IsNullOrEmpty(templateName)
43+
? templateName
44+
: (string)context.AmbientValues[TemplateKey] + "!";
45+
46+
var template = settings.TemplateFactory.CreateTemplate(language, templateName, context);
47+
var output = template.Render();
48+
49+
if (string.IsNullOrWhiteSpace(output))
50+
{
51+
// signal cleanup
52+
writer.Write("__EMPTY-TEMPLATE__");
53+
}
54+
else if (tabCount > 0)
55+
{
56+
ConversionUtilities.Tab(output, tabCount, writer);
57+
}
58+
else
59+
{
60+
writer.Write(output);
61+
}
62+
63+
return new ValueTask<Completion>(Completion.Normal);
64+
}
65+
}

0 commit comments

Comments
 (0)