Skip to content

Commit 558562b

Browse files
authored
Merge pull request #489 from Handlebars-Net/feature/decorators
Decorators implementation
2 parents bf2925d + c127e41 commit 558562b

File tree

62 files changed

+1786
-300
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1786
-300
lines changed

README.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,38 @@ The animal, Chewy, is not a dog.
160160
*/
161161
```
162162

163+
### Registering Decorators
164+
165+
```c#
166+
[Fact]
167+
public void BasicDecorator(IHandlebars handlebars)
168+
{
169+
string source = "{{#block @value-from-decorator}}{{*decorator 42}}{{@value}}{{/block}}";
170+
171+
var handlebars = Handlebars.Create();
172+
handlebars.RegisterHelper("block", (output, options, context, arguments) =>
173+
{
174+
options.Data.CreateProperty("value", arguments[0], out _);
175+
options.Template(output, context);
176+
});
177+
178+
handlebars.RegisterDecorator("decorator",
179+
(TemplateDelegate function, in DecoratorOptions options, in Context context, in Arguments arguments) =>
180+
{
181+
options.Data.CreateProperty("value-from-decorator", arguments[0], out _);
182+
});
183+
184+
var template = handlebars.Compile(source);
185+
186+
var result = template(null);
187+
Assert.Equal("42", result);
188+
}
189+
```
190+
For more examples see [DecoratorTests.cs](https://github.com/Handlebars-Net/Handlebars.Net/tree/master/source/Handlebars.Test/DecoratorTests.cs)
191+
192+
#### Known limitations:
193+
- helpers registered inside of a decorator will not override existing registrations
194+
163195
### Register custom value formatter
164196

165197
In case you need to apply custom value formatting (e.g. `DateTime`) you can use `IFormatter` and `IFormatterProvider` interfaces:
@@ -262,7 +294,7 @@ Will not encode:\
262294
` (backtick)\
263295
' (single quote)
264296

265-
Will encode non-ascii characters `â`, `ß`, ...\
297+
Will encode non-ascii characters ``, ``, ...\
266298
Into HTML entities (`<`, `â`, `ß`, ...).
267299

268300
##### Areas
@@ -277,12 +309,12 @@ public void UseCanonicalHtmlEncodingRules()
277309
handlebars.Configuration.TextEncoder = new HtmlEncoder();
278310

279311
var source = "{{Text}}";
280-
var value = new { Text = "< â" };
312+
var value = new { Text = "< " };
281313

282314
var template = handlebars.Compile(source);
283315
var actual = template(value);
284316

285-
Assert.Equal("&lt; â", actual);
317+
Assert.Equal("&lt; ", actual);
286318
}
287319
```
288320

@@ -301,8 +333,6 @@ Nearly all time spent in rendering is in the routine that resolves values agains
301333
- Rendering starts to get slower (into the tens of milliseconds or more) on dynamic objects.
302334
- The slowest (up to hundreds of milliseconds or worse) tend to be objects with custom type implementations (such as `ICustomTypeDescriptor`) that are not optimized for heavy reflection.
303335

304-
~~A frequent performance issue that comes up is JSON.NET's `JObject`, which for reasons we haven't fully researched, has very slow reflection characteristics when used as a model in Handlebars.Net. A simple fix is to just use JSON.NET's built-in ability to deserialize a JSON string to an `ExpandoObject` instead of a `JObject`. This will yield nearly an order of magnitude improvement in render times on average.~~
305-
306336
## Future roadmap
307337

308338
TBD

source/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<EmbedUntrackedSources>false</EmbedUntrackedSources>
1313
<IncludeSymbols>true</IncludeSymbols>
1414
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
15-
<LangVersion>8</LangVersion>
15+
<LangVersion>9</LangVersion>
1616
</PropertyGroup>
1717

1818
<PropertyGroup>

source/Handlebars.Benchmark/EndToEnd.cs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.IO;
44
using BenchmarkDotNet.Attributes;
55
using HandlebarsDotNet;
6+
using HandlebarsDotNet.Helpers;
7+
using HandlebarsDotNet.PathStructure;
68

79
namespace HandlebarsNet.Benchmark
810
{
@@ -66,9 +68,9 @@ public void Setup()
6668
var handlebars = Handlebars.Create();
6769
using(handlebars.Configure())
6870
{
69-
handlebars.RegisterHelper("pow1", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
70-
handlebars.RegisterHelper("pow2", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
71-
handlebars.RegisterHelper("pow5", (output, options, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
71+
handlebars.RegisterHelper(new PowHelper("pow1"));
72+
handlebars.RegisterHelper(new PowHelper("pow2"));
73+
handlebars.RegisterHelper(new BlockPowHelper("pow5"));
7274
}
7375

7476
using (var reader = new StringReader(template))
@@ -78,10 +80,10 @@ public void Setup()
7880

7981
using(handlebars.Configure())
8082
{
81-
handlebars.RegisterHelper("pow3", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
82-
handlebars.RegisterHelper("pow4", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
83+
handlebars.RegisterHelper(new PowHelper("pow3"));
84+
handlebars.RegisterHelper(new PowHelper("pow4"));
8385
}
84-
86+
8587
List<object> ObjectLevel1Generator()
8688
{
8789
var level = new List<object>();
@@ -171,6 +173,40 @@ List<Dictionary<string, object>> DictionaryLevel3Generator(int id1, int id2)
171173
}
172174
}
173175

176+
private class PowHelper : IHelperDescriptor<HelperOptions>
177+
{
178+
public PowHelper(PathInfo name) => Name = name;
179+
180+
public PathInfo Name { get; }
181+
182+
public object Invoke(in HelperOptions options, in Context context, in Arguments arguments)
183+
{
184+
return ((int)arguments[0] * (int)arguments[0]).ToString();
185+
}
186+
187+
public void Invoke(in EncodedTextWriter output, in HelperOptions options, in Context context, in Arguments arguments)
188+
{
189+
output.WriteSafeString(((int)arguments[0] * (int)arguments[0]).ToString());
190+
}
191+
}
192+
193+
private class BlockPowHelper : IHelperDescriptor<BlockHelperOptions>
194+
{
195+
public BlockPowHelper(PathInfo name) => Name = name;
196+
197+
public PathInfo Name { get; }
198+
199+
public object Invoke(in BlockHelperOptions options, in Context context, in Arguments arguments)
200+
{
201+
return ((int)arguments[0] * (int)arguments[0]).ToString();
202+
}
203+
204+
public void Invoke(in EncodedTextWriter output, in BlockHelperOptions options, in Context context, in Arguments arguments)
205+
{
206+
output.WriteSafeString(((int)arguments[0] * (int)arguments[0]).ToString());
207+
}
208+
}
209+
174210
[Benchmark]
175211
public void Default() => _default(TextWriter.Null, _data);
176212
}

source/Handlebars.Test/BasicIntegrationTests.cs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,10 @@
1212
using HandlebarsDotNet.Features;
1313
using HandlebarsDotNet.IO;
1414
using HandlebarsDotNet.PathStructure;
15+
using HandlebarsDotNet.ValueProviders;
1516

1617
namespace HandlebarsDotNet.Test
1718
{
18-
public class HandlebarsEnvGenerator : IEnumerable<object[]>
19-
{
20-
private readonly List<IHandlebars> _data = new List<IHandlebars>
21-
{
22-
Handlebars.Create(),
23-
Handlebars.Create(new HandlebarsConfiguration().Configure(o => o.Compatibility.RelaxedHelperNaming = true)),
24-
Handlebars.Create(new HandlebarsConfiguration().UseWarmUp(types =>
25-
{
26-
types.Add(typeof(Dictionary<string, object>));
27-
types.Add(typeof(Dictionary<int, object>));
28-
types.Add(typeof(Dictionary<long, object>));
29-
types.Add(typeof(Dictionary<string, string>));
30-
})),
31-
Handlebars.Create(new HandlebarsConfiguration().Configure(o => o.TextEncoder = new HtmlEncoder())),
32-
};
33-
34-
public IEnumerator<object[]> GetEnumerator() => _data.Select(o => new object[] { o }).GetEnumerator();
35-
36-
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
37-
}
38-
3919
public class BasicIntegrationTests
4020
{
4121
private static string HtmlEncodeStringHelper(IHandlebars handlebars, string inputString)

source/Handlebars.Test/ClosureBuilderTests.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ public class ClosureBuilderTests
1313
[Fact]
1414
public void GeneratesClosureWithOverflow()
1515
{
16-
var builder = new ClosureBuilder();
16+
using var builder = ClosureBuilder.Create();
1717

1818
var paths = GeneratePaths(builder, 6);
1919
var helpers = GenerateHelpers(builder, 6);
2020
var blockHelpers = GenerateBlockHelpers(builder, 6);
21+
var decoratorDelegates = GenerateDecoratorDelegates(builder, 6);
2122
var others = GenerateOther(builder, 6);
2223

2324
_ = builder.Build(out var closure);
@@ -34,6 +35,10 @@ public void GeneratesClosureWithOverflow()
3435
Assert.Equal(blockHelpers[3], closure.BHD3);
3536
Assert.Equal(blockHelpers[5], closure.BHDA[1]);
3637

38+
Assert.Equal(decoratorDelegates[0], closure.DDD0);
39+
Assert.Equal(decoratorDelegates[3], closure.DDD3);
40+
Assert.Equal(decoratorDelegates[5], closure.DDDA[1]);
41+
3742
Assert.Equal(others[0], closure.A[0]);
3843
Assert.Equal(others[3], closure.A[3]);
3944
Assert.Equal(others[5], closure.A[5]);
@@ -42,11 +47,12 @@ public void GeneratesClosureWithOverflow()
4247
[Fact]
4348
public void GeneratesClosureWithoutOverflow()
4449
{
45-
var builder = new ClosureBuilder();
50+
using var builder = ClosureBuilder.Create();
4651

4752
var paths = GeneratePaths(builder, 2);
4853
var helpers = GenerateHelpers(builder, 2);
4954
var blockHelpers = GenerateBlockHelpers(builder, 2);
55+
var decorators = GenerateDecoratorDelegates(builder, 2);
5056
var others = GenerateOther(builder, 2);
5157

5258
_ = builder.Build(out var closure);
@@ -63,6 +69,11 @@ public void GeneratesClosureWithoutOverflow()
6369
Assert.Equal(blockHelpers[1], closure.BHD1);
6470
Assert.Null(closure.BHDA);
6571

72+
Assert.Equal(decorators[0], closure.DDD0);
73+
Assert.Equal(decorators[1], closure.DDD1);
74+
Assert.Null(closure.DDD2);
75+
Assert.Null(closure.BHDA);
76+
6677
Assert.Equal(others[0], closure.A[0]);
6778
Assert.Equal(others[1], closure.A[1]);
6879
Assert.Equal(2, closure.A.Length);
@@ -106,6 +117,19 @@ private static List<Ref<IHelperDescriptor<HelperOptions>>> GenerateHelpers(Closu
106117

107118
return helpers;
108119
}
120+
121+
private static List<DecoratorDelegate> GenerateDecoratorDelegates(ClosureBuilder builder, int count)
122+
{
123+
var helpers = new List<DecoratorDelegate>();
124+
for (int i = 0; i < count; i++)
125+
{
126+
DecoratorDelegate helper = (in EncodedTextWriter writer, BindingContext context, TemplateDelegate function) => function;
127+
builder.Add(Const(helper));
128+
helpers.Add(helper);
129+
}
130+
131+
return helpers;
132+
}
109133

110134
private static List<PathInfo> GeneratePaths(ClosureBuilder builder, int count)
111135
{

0 commit comments

Comments
 (0)