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
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Expressions.Shortcuts;
using FastExpressionCompiler;
using HandlebarsDotNet.Features;
using static Expressions.Shortcuts.ExpressionShortcuts;

namespace HandlebarsDotNet.Extension.CompileFast
{
Expand Down Expand Up @@ -46,11 +46,10 @@ public T Compile<T>(Expression<T> expression) where T: class
var compiledLambda = method?.Invoke(null, new object[] { lambda }) ?? throw new InvalidOperationException("lambda cannot be compiled");

var outerParameters = expression.Parameters.Select(o => Expression.Parameter(o.Type, o.Name)).ToArray();

var store = (Expression) Expression.Field(Expression.Constant(_templateClosure), nameof(TemplateClosure.Store));
var outerLambda = Expression.Lambda<T>(
Expression.Invoke(Expression.Constant(compiledLambda), new[] {store}.Concat(outerParameters)),
outerParameters);
var store = Arg(_templateClosure).Member(o => o.Store);
var parameterExpressions = new[] { store.Expression }.Concat(outerParameters);
var invocationExpression = Expression.Invoke(Expression.Constant(compiledLambda), parameterExpressions);
var outerLambda = Expression.Lambda<T>(invocationExpression, outerParameters);

return outerLambda.CompileFast();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<Compile Remove="netstandard2.0\**" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net451' or '$(TargetFramework)'=='net452'">
<Reference Include="Microsoft.CSharp" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.2" />

Expand Down
13 changes: 12 additions & 1 deletion source/Handlebars.Extension.Logger/LoggerFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ public LoggerFeature(Log logger)

public void OnCompiling(ICompiledHandlebarsConfiguration configuration)
{
configuration.ReturnHelpers["log"] = LogHelper;
if (configuration.ReturnHelpers.TryGetValue("log", out var logger))
{
configuration.ReturnHelpers["log"] = (context, arguments) =>
{
logger(context, arguments);
return LogHelper(context, arguments);
};
}
else
{
configuration.ReturnHelpers["log"] = LogHelper;
}
}

public void CompilationCompleted()
Expand Down
186 changes: 186 additions & 0 deletions source/Handlebars.Test/HelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HandlebarsDotNet.Features;

namespace HandlebarsDotNet.Test
{
Expand Down Expand Up @@ -29,6 +30,7 @@ public void HelperWithLiteralArguments()
Assert.Equal(expected, output);
}


[Fact]
public void BlockHelperWithBlockParams()
{
Expand Down Expand Up @@ -84,6 +86,44 @@ public void BlockHelperLateBound()
Assert.Equal(expected, output);
}

[Fact]
public void BlockHelperLateBoundConflictsWithValue()
{
var source = "{{#late}}late{{/late}}";

var handlebars = Handlebars.Create();
var template = handlebars.Compile(source);

handlebars.RegisterHelper("late", (writer, options, context, args) =>
{
options.Template(writer, context);
});

var output = template(new { late = "should be ignored" });

var expected = "late";

Assert.Equal(expected, output);
}

[Fact]
public void BlockHelperLateBoundMissingHelperFallbackToDeferredSection()
{
var source = "{{#late}}late{{/late}}";

var handlebars = Handlebars.Create();
handlebars.Configuration.RegisterMissingHelperHook(
(context, arguments) => "Hook"
);
var template = handlebars.Compile(source);

var output = template(new { late = "late" });

var expected = "late";

Assert.Equal(expected, output);
}

[Fact]
public void HelperLateBound()
{
Expand Down Expand Up @@ -144,6 +184,152 @@ public void WrongHelperLiteralLateBound(string source)

Assert.Equal(string.Empty, output);
}

[Theory]
[InlineData("missing")]
[InlineData("[missing]")]
[InlineData("[$missing]")]
[InlineData("[m.i.s.s.i.n.g]")]
public void MissingHelperHook(string helperName)
{
var handlebars = Handlebars.Create();
var format = "Missing helper: {0}";
handlebars.Configuration
.RegisterMissingHelperHook(
(context, arguments) =>
{
var name = arguments.Last().ToString();
return string.Format(format, name.Trim('[', ']'));
});

var source = "{{"+ helperName +"}}";

var template = handlebars.Compile(source);

var output = template(null);

var expected = string.Format(format, helperName.Trim('[', ']'));
Assert.Equal(expected, output);
}

[Fact]
public void MissingHelperHookViaFeatureAndMethod()
{
var expected = "Hook";
var handlebars = Handlebars.Create();
handlebars.Configuration
.RegisterMissingHelperHook(
(context, arguments) => expected
);

handlebars.RegisterHelper("helperMissing",
(context, arguments) => "Should be ignored"
);

var source = "{{missing}}";
var template = handlebars.Compile(source);

var output = template(null);

Assert.Equal(expected, output);
}

[Theory]
[InlineData("missing")]
[InlineData("[missing]")]
[InlineData("[$missing]")]
[InlineData("[m.i.s.s.i.n.g]")]
public void MissingHelperHookViaHelperRegistration(string helperName)
{
var handlebars = Handlebars.Create();
var format = "Missing helper: {0}";
handlebars.RegisterHelper("helperMissing", (context, arguments) =>
{
var name = arguments.Last().ToString();
return string.Format(format, name.Trim('[', ']'));
});

var source = "{{"+ helperName +"}}";

var template = handlebars.Compile(source);

var output = template(null);

var expected = string.Format(format, helperName.Trim('[', ']'));
Assert.Equal(expected, output);
}

[Theory]
[InlineData("missing")]
[InlineData("[missing]")]
[InlineData("[$missing]")]
[InlineData("[m.i.s.s.i.n.g]")]
public void MissingBlockHelperHook(string helperName)
{
var handlebars = Handlebars.Create();
var format = "Missing block helper: {0}";
handlebars.Configuration
.RegisterMissingHelperHook(
blockHelperMissing: (writer, options, context, arguments) =>
{
var name = options.GetValue<string>("name");
writer.WriteSafeString(string.Format(format, name.Trim('[', ']')));
});

var source = "{{#"+ helperName +"}}should not appear{{/" + helperName + "}}";

var template = handlebars.Compile(source);

var output = template(null);

var expected = string.Format(format, helperName.Trim('[', ']'));
Assert.Equal(expected, output);
}

[Theory]
[InlineData("missing")]
[InlineData("[missing]")]
[InlineData("[$missing]")]
[InlineData("[m.i.s.s.i.n.g]")]
public void MissingBlockHelperHookViaHelperRegistration(string helperName)
{
var handlebars = Handlebars.Create();
var format = "Missing block helper: {0}";
handlebars.RegisterHelper("blockHelperMissing", (writer, options, context, arguments) =>
{
var name = options.GetValue<string>("name");
writer.WriteSafeString(string.Format(format, name.Trim('[', ']')));
});

var source = "{{#"+ helperName +"}}should not appear{{/" + helperName + "}}";

var template = handlebars.Compile(source);

var output = template(null);

var expected = string.Format(format, helperName.Trim('[', ']'));
Assert.Equal(expected, output);
}

[Fact]
public void MissingHelperHookWhenVariableExists()
{
var handlebars = Handlebars.Create();
var expected = "Variable";

handlebars.Configuration
.RegisterMissingHelperHook(
(context, arguments) => "Hook"
);

var source = "{{missing}}";

var template = Handlebars.Compile(source);

var output = template(new { missing = "Variable" });

Assert.Equal(expected, output);
}

[Fact]
public void HelperWithLiteralArgumentsWithQuotes()
Expand Down
26 changes: 26 additions & 0 deletions source/Handlebars.Test/IssueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,31 @@ public void LateBoundHelperWithSameNameVariablePath()
actual = template(data);
Assert.Equal(expected, actual);
}

// Issue https://github.com/rexm/Handlebars.Net/issues/354
[Fact]
public void BlockHelperWithInversion()
{
string source = "{{^test input}}empty{{else}}not empty{{/test}}";

var handlebars = Handlebars.Create();
handlebars.RegisterHelper("test", (output, options, context, arguments) =>
{
if (HandlebarsUtils.IsTruthy(arguments[0]))
{
options.Template(output, context);
}
else
{
options.Inverse(output, context);
}
});

var template = handlebars.Compile(source);

Assert.Equal("empty", template(null));
Assert.Equal("empty", template(new { otherInput = 1 }));
Assert.Equal("not empty", template(new { input = 1 }));
}
}
}
26 changes: 26 additions & 0 deletions source/Handlebars/Adapters/HelperToReturnHelperAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace HandlebarsDotNet.Adapters
{
internal class HelperToReturnHelperAdapter
{
private readonly HandlebarsHelper _helper;
private readonly HandlebarsReturnHelper _delegate;

public HelperToReturnHelperAdapter(HandlebarsHelper helper)
{
_helper = helper;
_delegate = (context, arguments) =>
{
using (var writer = new PolledStringWriter())
{
_helper(writer, context, arguments);
return writer.ToString();
}
};
}

public static implicit operator HandlebarsReturnHelper(HelperToReturnHelperAdapter adapter)
{
return adapter._delegate;
}
}
}
24 changes: 24 additions & 0 deletions source/Handlebars/Adapters/LambdaEnricher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.IO;
using HandlebarsDotNet.Compiler;

namespace HandlebarsDotNet.Adapters
{
internal class LambdaEnricher
{
private readonly Action<TextWriter, object> _direct;
private readonly Action<TextWriter, object> _inverse;

public LambdaEnricher(Action<TextWriter, object> direct, Action<TextWriter, object> inverse)
{
_direct = direct;
_inverse = inverse;

Direct = (context, writer, arg) => _direct(writer, arg);
Inverse = (context, writer, arg) => _inverse(writer, arg);
}

public readonly Action<BindingContext, TextWriter, object> Direct;
public readonly Action<BindingContext, TextWriter, object> Inverse;
}
}
26 changes: 26 additions & 0 deletions source/Handlebars/Adapters/LambdaReducer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.IO;
using HandlebarsDotNet.Compiler;

namespace HandlebarsDotNet.Adapters
{
internal class LambdaReducer
{
private readonly BindingContext _context;
private readonly Action<BindingContext, TextWriter, object> _direct;
private readonly Action<BindingContext, TextWriter, object> _inverse;

public LambdaReducer(BindingContext context, Action<BindingContext, TextWriter, object> direct, Action<BindingContext, TextWriter, object> inverse)
{
_context = context;
_direct = direct;
_inverse = inverse;

Direct = (writer, arg) => _direct(_context, writer, arg);
Inverse = (writer, arg) => _inverse(_context, writer, arg);
}

public readonly Action<TextWriter, object> Direct;
public readonly Action<TextWriter, object> Inverse;
}
}
Loading