Skip to content
Closed
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
95 changes: 83 additions & 12 deletions source/Handlebars.Test/BasicIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,9 @@ public void AssertHandlebarsUndefinedBindingException()
}
};

try
{
template(data);
}
catch (HandlebarsUndefinedBindingException ex)
{
Assert.Equal("person.lastname", ex.Path);
Assert.Equal("lastname", ex.MissingKey);
return;
}

Assert.False(true, "Exception is expected.");
var exception = Assert.Throws<HandlebarsUndefinedBindingException>(() => template(data));
Assert.Equal("person.lastname", exception.Path);
Assert.Equal("lastname", exception.MissingKey);
}

[Fact]
Expand Down Expand Up @@ -365,6 +356,23 @@ public void BasicWith()
var result = template(data);
Assert.Equal("Hello, my good friend Erik!", result);
}

[Fact]
public void WithWithBlockParams()
{
var source = "{{#with person as |person|}}{{person.name}} is {{age}} years old{{/with}}.";
var template = Handlebars.Compile(source);
var data = new
{
person = new
{
name = "Erik",
age = 42
}
};
var result = template(data);
Assert.Equal("Erik is 42 years old.", result);
}

[Fact]
public void BasicWithInversion()
Expand Down Expand Up @@ -466,6 +474,23 @@ public void BasicObjectEnumeratorWithKey()
var result = template(data);
Assert.Equal("foo: hello bar: world ", result);
}

[Fact]
public void ObjectEnumeratorWithBlockParams()
{
var source = "{{#each enumerateMe as |item val|}}{{@item}}: {{@val}} {{/each}}";
var template = Handlebars.Compile(source);
var data = new
{
enumerateMe = new
{
foo = "hello",
bar = "world"
}
};
var result = template(data);
Assert.Equal("hello: foo world: bar ", result);
}

[Fact]
public void BasicDictionaryEnumerator()
Expand All @@ -484,6 +509,52 @@ public void BasicDictionaryEnumerator()
Assert.Equal("hello world ", result);
}

[Fact]
public void DictionaryEnumeratorWithBlockParams()
{
var source = "{{#each enumerateMe as |item val|}}{{item}} {{val}} {{/each}}";
var template = Handlebars.Compile(source);
var data = new
{
enumerateMe = new Dictionary<string, object>
{
{ "foo", "hello"},
{ "bar", "world"}
}
};
var result = template(data);
Assert.Equal("hello foo world bar ", result);
}

[Fact]
public void DictionaryEnumeratorWithInnerBlockParams()
{
var source = "{{#each enumerateMe as |item k|}}{{#each item as |item1 k|}}{{item1}} {{k}} {{/each}}{{/each}}";
var template = Handlebars.Compile(source);
var data = new
{
enumerateMe = new Dictionary<string, object>
{
{
"foo", new Dictionary<string, object>
{
{"foo1", "hello1"},
{"bar1", "world1"}
}
},
{
"bar", new Dictionary<string, object>
{
{"foo2", "hello2"},
{"bar2", "world2"}
}
}
}
};
var result = template(data);
Assert.Equal("hello1 foo1 world1 bar1 hello2 foo2 world2 bar2 ", result);
}

[Fact]
public void DictionaryWithLastEnumerator()
{
Expand Down
27 changes: 27 additions & 0 deletions source/Handlebars.Test/HelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using HandlebarsDotNet.Compiler;

namespace HandlebarsDotNet.Test
{
Expand All @@ -28,6 +30,31 @@ public void HelperWithLiteralArguments()

Assert.Equal(expected, output);
}

[Fact]
public void BlockHelperWithBlockParams()
{
Handlebars.RegisterHelper("myHelper", (writer, options, context, args) => {
var count = 0;
options.BlockParams((parameters, binder) =>
binder(parameters.ElementAtOrDefault(0), () => ++count));

foreach(var arg in args)
{
options.Template(writer, arg);
}
});

var source = "Here are some things: {{#myHelper 'foo' 'bar' as |counter|}}{{counter}}:{{this}}\n{{/myHelper}}";

var template = Handlebars.Compile(source);

var output = template(new { });

var expected = "Here are some things: 1:foo\n2:bar\n";

Assert.Equal(expected, output);
}

[Fact]
public void HelperWithLiteralArgumentsWithQuotes()
Expand Down
8 changes: 6 additions & 2 deletions source/Handlebars/BuiltinHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using HandlebarsDotNet.Compiler;

namespace HandlebarsDotNet
{
internal static class BuiltinHelpers
internal class BuiltinHelpers
{
[Description("with")]
public static void With(TextWriter output, HelperOptions options, dynamic context, params object[] arguments)
Expand All @@ -16,6 +17,9 @@ public static void With(TextWriter output, HelperOptions options, dynamic contex
throw new HandlebarsException("{{with}} helper must have exactly one argument");
}

options.BlockParams((parameters, binder) =>
binder(parameters.ElementAtOrDefault(0), () => arguments[0]));

if (HandlebarsUtils.IsTruthyOrNonEmpty(arguments[0]))
{
options.Template(output, arguments[0]);
Expand Down
1 change: 1 addition & 0 deletions source/Handlebars/Compiler/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public IEnumerable<Expression> ConvertTokensToExpressions(IEnumerable<object> to
tokens = LiteralConverter.Convert(tokens);
tokens = HashParameterConverter.Convert(tokens);
tokens = PathConverter.Convert(tokens);
tokens = BlockParamsConverter.Convert(tokens);
tokens = SubExpressionConverter.Convert(tokens);
tokens = HashParametersAccumulator.Accumulate(tokens);
tokens = PartialConverter.Convert(tokens);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public override Expression GetAccumulatedBlock()

var resultExpr = HandlebarsExpression.BlockHelper(
_startingNode.HelperName,
_startingNode.Arguments,
_startingNode.Arguments.Where(o => o.NodeType != (ExpressionType)HandlebarsExpressionType.BlockParamsExpression),
_startingNode.Arguments.OfType<BlockParamsExpression>().SingleOrDefault() ?? BlockParamsExpression.Empty(),
_accumulatedBody,
_accumulatedInversion,
_startingNode.IsRaw);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public override void HandleElement(Expression item)
if (IsElseBlock(item))
{
_accumulatedExpression = HandlebarsExpression.Iterator(
_startingNode.Arguments.Single(),
_startingNode.Arguments.Single(o => o.NodeType != (ExpressionType)HandlebarsExpressionType.BlockParamsExpression),
_startingNode.Arguments.OfType<BlockParamsExpression>().SingleOrDefault() ?? BlockParamsExpression.Empty(),
Expression.Block(_body));
_body = new List<Expression>();
}
Expand All @@ -44,13 +45,15 @@ public override bool IsClosingElement(Expression item)
if (_accumulatedExpression == null)
{
_accumulatedExpression = HandlebarsExpression.Iterator(
_startingNode.Arguments.Single(),
_startingNode.Arguments.Single(o => o.NodeType != (ExpressionType)HandlebarsExpressionType.BlockParamsExpression),
_startingNode.Arguments.OfType<BlockParamsExpression>().SingleOrDefault() ?? BlockParamsExpression.Empty(),
Expression.Block(bodyStatements));
}
else
{
_accumulatedExpression = HandlebarsExpression.Iterator(
((IteratorExpression)_accumulatedExpression).Sequence,
((IteratorExpression)_accumulatedExpression).BlockParams,
((IteratorExpression)_accumulatedExpression).Template,
Expression.Block(bodyStatements));
}
Expand Down
55 changes: 55 additions & 0 deletions source/Handlebars/Compiler/Lexer/Converter/BlockParamsConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using HandlebarsDotNet.Compiler.Lexer;

namespace HandlebarsDotNet.Compiler
{
internal class BlockParamsConverter : TokenConverter
{
public static IEnumerable<object> Convert(IEnumerable<object> sequence)
{
return new BlockParamsConverter().ConvertTokens(sequence).ToList();
}

private BlockParamsConverter()
{
}

public override IEnumerable<object> ConvertTokens(IEnumerable<object> sequence)
{
var result = new List<object>();
bool foundBlockParams = false;
foreach (var item in sequence)
{
switch (item)
{
case BlockParameterToken blockParameterToken when foundBlockParams:
throw new HandlebarsCompilerException("multiple blockParams expressions are not supported");

case BlockParameterToken blockParameterToken:
foundBlockParams = true;
if (!(result.Last() is PathExpression pathExpression))
throw new HandlebarsCompilerException("blockParams definition has incorrect syntax");
if (!string.Equals("as", pathExpression.Path, StringComparison.OrdinalIgnoreCase))
throw new HandlebarsCompilerException("blockParams definition has incorrect syntax");

result[result.Count - 1] =
HandlebarsExpression.BlockParams(pathExpression.Path, blockParameterToken.Value);
break;

case EndExpressionToken _:
foundBlockParams = false;
result.Add(item);
break;

default:
result.Add(item);
break;
}
}

return result;
}
}
}
39 changes: 39 additions & 0 deletions source/Handlebars/Compiler/Lexer/Parsers/BlockParamsParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace HandlebarsDotNet.Compiler.Lexer
{
internal class BlockParamsParser : Parser
{
public override Token Parse(TextReader reader)
{
var buffer = AccumulateWord(reader);
return !string.IsNullOrEmpty(buffer)
? Token.BlockParams(buffer)
: null;
}

private static string AccumulateWord(TextReader reader)
{
var buffer = new StringBuilder();

if (reader.Peek() != '|') return null;

reader.Read();

while (reader.Peek() != '|')
{
buffer.Append((char) reader.Read());
}

reader.Read();

var accumulateWord = buffer.ToString().Trim();
if(string.IsNullOrEmpty(accumulateWord)) throw new HandlebarsParserException($"BlockParams expression is not valid");

return accumulateWord;
}
}
}
Loading