Skip to content
Merged
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
40 changes: 35 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,38 @@ The animal, Chewy, is not a dog.
*/
```

### Registering Decorators

```c#
[Fact]
public void BasicDecorator(IHandlebars handlebars)
{
string source = "{{#block @value-from-decorator}}{{*decorator 42}}{{@value}}{{/block}}";

var handlebars = Handlebars.Create();
handlebars.RegisterHelper("block", (output, options, context, arguments) =>
{
options.Data.CreateProperty("value", arguments[0], out _);
options.Template(output, context);
});

handlebars.RegisterDecorator("decorator",
(TemplateDelegate function, in DecoratorOptions options, in Context context, in Arguments arguments) =>
{
options.Data.CreateProperty("value-from-decorator", arguments[0], out _);
});

var template = handlebars.Compile(source);

var result = template(null);
Assert.Equal("42", result);
}
```
For more examples see [DecoratorTests.cs](https://github.com/Handlebars-Net/Handlebars.Net/tree/master/source/Handlebars.Test/DecoratorTests.cs)

#### Known limitations:
- helpers registered inside of a decorator will not override existing registrations

### Register custom value formatter

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

Will encode non-ascii characters `�`, `�`, ...\
Will encode non-ascii characters `�`, `�`, ...\
Into HTML entities (`<`, `â`, `ß`, ...).

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

var source = "{{Text}}";
var value = new { Text = "< �" };
var value = new { Text = "< �" };

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

Assert.Equal("&lt; �", actual);
Assert.Equal("&lt; �", actual);
}
```

Expand All @@ -301,8 +333,6 @@ Nearly all time spent in rendering is in the routine that resolves values agains
- Rendering starts to get slower (into the tens of milliseconds or more) on dynamic objects.
- 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.

~~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.~~

## Future roadmap

TBD
Expand Down
2 changes: 1 addition & 1 deletion source/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<EmbedUntrackedSources>false</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<LangVersion>8</LangVersion>
<LangVersion>9</LangVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
48 changes: 42 additions & 6 deletions source/Handlebars.Benchmark/EndToEnd.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using HandlebarsDotNet;
using HandlebarsDotNet.Helpers;
using HandlebarsDotNet.PathStructure;

namespace HandlebarsNet.Benchmark
{
Expand Down Expand Up @@ -66,9 +68,9 @@ public void Setup()
var handlebars = Handlebars.Create();
using(handlebars.Configure())
{
handlebars.RegisterHelper("pow1", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
handlebars.RegisterHelper("pow2", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
handlebars.RegisterHelper("pow5", (output, options, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
handlebars.RegisterHelper(new PowHelper("pow1"));
handlebars.RegisterHelper(new PowHelper("pow2"));
handlebars.RegisterHelper(new BlockPowHelper("pow5"));
}

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

using(handlebars.Configure())
{
handlebars.RegisterHelper("pow3", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
handlebars.RegisterHelper("pow4", (output, context, arguments) => output.WriteSafeString(((int) arguments[0] * (int) arguments[0]).ToString()));
handlebars.RegisterHelper(new PowHelper("pow3"));
handlebars.RegisterHelper(new PowHelper("pow4"));
}

List<object> ObjectLevel1Generator()
{
var level = new List<object>();
Expand Down Expand Up @@ -171,6 +173,40 @@ List<Dictionary<string, object>> DictionaryLevel3Generator(int id1, int id2)
}
}

private class PowHelper : IHelperDescriptor<HelperOptions>
{
public PowHelper(PathInfo name) => Name = name;

public PathInfo Name { get; }

public object Invoke(in HelperOptions options, in Context context, in Arguments arguments)
{
return ((int)arguments[0] * (int)arguments[0]).ToString();
}

public void Invoke(in EncodedTextWriter output, in HelperOptions options, in Context context, in Arguments arguments)
{
output.WriteSafeString(((int)arguments[0] * (int)arguments[0]).ToString());
}
}

private class BlockPowHelper : IHelperDescriptor<BlockHelperOptions>
{
public BlockPowHelper(PathInfo name) => Name = name;

public PathInfo Name { get; }

public object Invoke(in BlockHelperOptions options, in Context context, in Arguments arguments)
{
return ((int)arguments[0] * (int)arguments[0]).ToString();
}

public void Invoke(in EncodedTextWriter output, in BlockHelperOptions options, in Context context, in Arguments arguments)
{
output.WriteSafeString(((int)arguments[0] * (int)arguments[0]).ToString());
}
}

[Benchmark]
public void Default() => _default(TextWriter.Null, _data);
}
Expand Down
22 changes: 1 addition & 21 deletions source/Handlebars.Test/BasicIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,10 @@
using HandlebarsDotNet.Features;
using HandlebarsDotNet.IO;
using HandlebarsDotNet.PathStructure;
using HandlebarsDotNet.ValueProviders;

namespace HandlebarsDotNet.Test
{
public class HandlebarsEnvGenerator : IEnumerable<object[]>
{
private readonly List<IHandlebars> _data = new List<IHandlebars>
{
Handlebars.Create(),
Handlebars.Create(new HandlebarsConfiguration().Configure(o => o.Compatibility.RelaxedHelperNaming = true)),
Handlebars.Create(new HandlebarsConfiguration().UseWarmUp(types =>
{
types.Add(typeof(Dictionary<string, object>));
types.Add(typeof(Dictionary<int, object>));
types.Add(typeof(Dictionary<long, object>));
types.Add(typeof(Dictionary<string, string>));
})),
Handlebars.Create(new HandlebarsConfiguration().Configure(o => o.TextEncoder = new HtmlEncoder())),
};

public IEnumerator<object[]> GetEnumerator() => _data.Select(o => new object[] { o }).GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class BasicIntegrationTests
{
private static string HtmlEncodeStringHelper(IHandlebars handlebars, string inputString)
Expand Down
28 changes: 26 additions & 2 deletions source/Handlebars.Test/ClosureBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ public class ClosureBuilderTests
[Fact]
public void GeneratesClosureWithOverflow()
{
var builder = new ClosureBuilder();
using var builder = ClosureBuilder.Create();

var paths = GeneratePaths(builder, 6);
var helpers = GenerateHelpers(builder, 6);
var blockHelpers = GenerateBlockHelpers(builder, 6);
var decoratorDelegates = GenerateDecoratorDelegates(builder, 6);
var others = GenerateOther(builder, 6);

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

Assert.Equal(decoratorDelegates[0], closure.DDD0);
Assert.Equal(decoratorDelegates[3], closure.DDD3);
Assert.Equal(decoratorDelegates[5], closure.DDDA[1]);

Assert.Equal(others[0], closure.A[0]);
Assert.Equal(others[3], closure.A[3]);
Assert.Equal(others[5], closure.A[5]);
Expand All @@ -42,11 +47,12 @@ public void GeneratesClosureWithOverflow()
[Fact]
public void GeneratesClosureWithoutOverflow()
{
var builder = new ClosureBuilder();
using var builder = ClosureBuilder.Create();

var paths = GeneratePaths(builder, 2);
var helpers = GenerateHelpers(builder, 2);
var blockHelpers = GenerateBlockHelpers(builder, 2);
var decorators = GenerateDecoratorDelegates(builder, 2);
var others = GenerateOther(builder, 2);

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

Assert.Equal(decorators[0], closure.DDD0);
Assert.Equal(decorators[1], closure.DDD1);
Assert.Null(closure.DDD2);
Assert.Null(closure.BHDA);

Assert.Equal(others[0], closure.A[0]);
Assert.Equal(others[1], closure.A[1]);
Assert.Equal(2, closure.A.Length);
Expand Down Expand Up @@ -106,6 +117,19 @@ private static List<Ref<IHelperDescriptor<HelperOptions>>> GenerateHelpers(Closu

return helpers;
}

private static List<DecoratorDelegate> GenerateDecoratorDelegates(ClosureBuilder builder, int count)
{
var helpers = new List<DecoratorDelegate>();
for (int i = 0; i < count; i++)
{
DecoratorDelegate helper = (in EncodedTextWriter writer, BindingContext context, TemplateDelegate function) => function;
builder.Add(Const(helper));
helpers.Add(helper);
}

return helpers;
}

private static List<PathInfo> GeneratePaths(ClosureBuilder builder, int count)
{
Expand Down
Loading