From 99b49a38da8dd8242bf92440d726d0141d02d43e Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 5 Apr 2015 14:51:43 +0100 Subject: [PATCH 01/21] Added another delegate for Lambdas without args --- Nustache.Core/Block.cs | 2 +- Nustache.Core/RenderContext.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Nustache.Core/Block.cs b/Nustache.Core/Block.cs index 9b7fafb..40f70f4 100644 --- a/Nustache.Core/Block.cs +++ b/Nustache.Core/Block.cs @@ -13,7 +13,7 @@ public override void Render(RenderContext context) { var value = context.GetValue(Name); - var lambda = value as Lambda; + var lambda = value as Lambda; if (lambda != null) { diff --git a/Nustache.Core/RenderContext.cs b/Nustache.Core/RenderContext.cs index 5519d8b..5a27dac 100644 --- a/Nustache.Core/RenderContext.cs +++ b/Nustache.Core/RenderContext.cs @@ -9,7 +9,8 @@ namespace Nustache.Core { public delegate Template TemplateLocator(string name); - public delegate Object Lambda(string text); + public delegate Object Lambda(T text); + public delegate Object Lambda(); public class RenderContext { From e8e95a05bb7b72b65d096513291dbe2cca18f72d Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 5 Apr 2015 14:52:26 +0100 Subject: [PATCH 02/21] Added test covering lambda without args For wrapping lambdas you have to pass the parameter type currently, not too nice --- Nustache.Core.Tests/Describe_Lambda.cs | 13 ++++++++++++- Nustache.Core/VariableReference.cs | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Nustache.Core.Tests/Describe_Lambda.cs b/Nustache.Core.Tests/Describe_Lambda.cs index b9a07f0..b1d74e2 100644 --- a/Nustache.Core.Tests/Describe_Lambda.cs +++ b/Nustache.Core.Tests/Describe_Lambda.cs @@ -10,9 +10,20 @@ public void The_text_passed_is_the_literal_block_unrendered() { var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new { - wrapped = (Lambda)((s) => string.Format("{0}", s)) + wrapped = (Lambda)((s) => string.Format("{0}", s)) }); Assert.AreEqual("{{name}} is awesome.", result); } + + [Test] + public void Lambdas_Return_Should_Be_Interpolated() + { + var result = Render.StringToString("Hello, {{lambda}}!", new + { + lambda = (Lambda)(() => "World") + }); + + Assert.AreEqual("Hello, World!", result); + } } } \ No newline at end of file diff --git a/Nustache.Core/VariableReference.cs b/Nustache.Core/VariableReference.cs index ebcd105..32d35c1 100644 --- a/Nustache.Core/VariableReference.cs +++ b/Nustache.Core/VariableReference.cs @@ -40,6 +40,11 @@ public override void Render(RenderContext context) { var value = context.GetValue(_path); + if (value is Lambda) + { + value = (value as Lambda)(); + } + var helper = value as HelperProxy; if (helper != null) From fa8da215d8b40d5079169134bd2019d5d14849e9 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 5 Apr 2015 14:53:46 +0100 Subject: [PATCH 03/21] Fixed broken test, type should be object --- Nustache.Core.Tests/Describe_Lambda.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nustache.Core.Tests/Describe_Lambda.cs b/Nustache.Core.Tests/Describe_Lambda.cs index b1d74e2..33e8365 100644 --- a/Nustache.Core.Tests/Describe_Lambda.cs +++ b/Nustache.Core.Tests/Describe_Lambda.cs @@ -10,7 +10,7 @@ public void The_text_passed_is_the_literal_block_unrendered() { var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new { - wrapped = (Lambda)((s) => string.Format("{0}", s)) + wrapped = (Lambda)((s) => string.Format("{0}", s)) }); Assert.AreEqual("{{name}} is awesome.", result); } From 47bd85d98a7e125d01e5440a9a3e596c814b9f2e Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 16:39:46 +0100 Subject: [PATCH 04/21] Refactored Lambda delegates to be typed #84 This brings the functionality of Func to 2.0 --- Nustache.Compilation/CompilePartVisitor.cs | 2 +- Nustache.Core/RenderContext.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Nustache.Compilation/CompilePartVisitor.cs b/Nustache.Compilation/CompilePartVisitor.cs index ff41351..d86a177 100644 --- a/Nustache.Compilation/CompilePartVisitor.cs +++ b/Nustache.Compilation/CompilePartVisitor.cs @@ -56,7 +56,7 @@ public void Visit(Block block) { context.Push(block, value); - if (typeof(Lambda).BaseType.IsAssignableFrom(value.Type)) + if (typeof(Lambda).BaseType.IsAssignableFrom(value.Type)) { return Expression.Call( diff --git a/Nustache.Core/RenderContext.cs b/Nustache.Core/RenderContext.cs index a68177f..0501cc5 100644 --- a/Nustache.Core/RenderContext.cs +++ b/Nustache.Core/RenderContext.cs @@ -10,8 +10,8 @@ namespace Nustache.Core { public delegate Template TemplateLocator(string name); - public delegate Object Lambda(); - public delegate Object Lambda(string text, RenderContext context, RenderFunc render); + public delegate TResult Lambda(); + public delegate TResult Lambda(T arg); public delegate string RenderFunc(RenderContext context); From 167790adb87cef36ba75962c368fb69b0372e568 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 16:41:23 +0100 Subject: [PATCH 05/21] Blocks and Variables call Lambdas #84 The result of this is then interpolated using a Template and rendered apropriately --- Nustache.Core/Block.cs | 18 +++++++++--------- Nustache.Core/VariableReference.cs | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Nustache.Core/Block.cs b/Nustache.Core/Block.cs index 527f1ff..2830212 100644 --- a/Nustache.Core/Block.cs +++ b/Nustache.Core/Block.cs @@ -16,19 +16,19 @@ public override void Render(RenderContext context) { var value = context.GetValue(Name); - var lambda = value as Lambda; + var lambda = value as Lambda; if (lambda != null) { - RenderFunc render = c => + var lambdaResult = lambda(InnerSource()); + using(TextReader sr = new StringReader(lambdaResult)) { - var textWriter = new StringWriter(); - var lambdaContext = new RenderContext(context, textWriter); - RenderParts(lambdaContext); - return textWriter.GetStringBuilder().ToString(); - }; - - context.Write(lambda(InnerSource(), context, render).ToString()); + var template = new Template(); + template.Load(sr); + context.Enter(template); + template.Render(context); + context.Exit(); + } return; } diff --git a/Nustache.Core/VariableReference.cs b/Nustache.Core/VariableReference.cs index 6dcd006..dcd58bf 100644 --- a/Nustache.Core/VariableReference.cs +++ b/Nustache.Core/VariableReference.cs @@ -40,9 +40,20 @@ public override void Render(RenderContext context) { var value = context.GetValue(_path); - if (value is Lambda) + var lambda = value as Lambda; + if(lambda != null) { - value = (value as Lambda)(); + var lambdaResult = lambda(); + using (System.IO.TextReader sr = new System.IO.StringReader(lambdaResult)) + { + var template = new Template(); + template.Load(sr); + context.Enter(template); + template.Render(context); + context.Exit(); + + return; + } } var helper = value as HelperProxy; From e16f3eccf66bae4b00c0638c0fc708b6e0ff5226 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 16:42:12 +0100 Subject: [PATCH 06/21] Updated Lambda tests to use new Syntax #84 --- Nustache.Core.Tests/Describe_Lambda.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Nustache.Core.Tests/Describe_Lambda.cs b/Nustache.Core.Tests/Describe_Lambda.cs index 0e859c5..db83ca5 100644 --- a/Nustache.Core.Tests/Describe_Lambda.cs +++ b/Nustache.Core.Tests/Describe_Lambda.cs @@ -10,20 +10,10 @@ public void The_text_passed_is_the_literal_block_unrendered() { var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new { - wrapped = (Lambda)((body, context, render) => string.Format("{0}", body)) + wrapped = (Lambda)((body) => string.Format("{0}", body)), + name = "Nustache" }); - Assert.AreEqual("{{name}} is awesome.", result); - } - - [Test] - public void It_can_use_context_and_render_delegate_inside_lambda() - { - var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new - { - wrapped = (Lambda)((body, context, render) => string.Format("{0}", render(context))), - name = "Lukasz" - }); - Assert.AreEqual("Lukasz is awesome.", result); + Assert.AreEqual("Nustache is awesome.", result); } [Test] @@ -31,7 +21,7 @@ public void Lambdas_Return_Should_Be_Interpolated() { var result = Render.StringToString("Hello, {{lambda}}!", new { - lambda = (Lambda)(() => "World") + lambda = (Lambda)(() => "World") }); Assert.AreEqual("Hello, World!", result); From 90e52af7c07aa0e85aabfa6f86abb910b8f817ff Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 18:05:27 +0100 Subject: [PATCH 07/21] Began trying to load lambda defintions from spec #84 --- Nustache.Core.Tests/Mustache_Spec.cs | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Nustache.Core.Tests/Mustache_Spec.cs b/Nustache.Core.Tests/Mustache_Spec.cs index 7f86fe5..a6e734a 100644 --- a/Nustache.Core.Tests/Mustache_Spec.cs +++ b/Nustache.Core.Tests/Mustache_Spec.cs @@ -1,8 +1,15 @@ using System; +using System.CodeDom.Compiler; using System.Collections.Generic; +using System.Data; using System.IO; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; using System.Text.RegularExpressions; +using Microsoft.CSharp; using NUnit.Framework; using YamlDotNet.RepresentationModel.Serialization; @@ -18,6 +25,7 @@ public class Mustache_Spec [TestCaseSource("Inverted")] [TestCaseSource("Partials")] [TestCaseSource("Sections")] + [TestCaseSource("Lambdas")] public void AllTests(string name, Dictionary data, string template, Dictionary partials, string expected) { FixData(data); @@ -46,6 +54,7 @@ private void FixData(Dictionary data) { FixNumbers(data); FixFalseValues(data); + FixLambdas(data); } private void FixNumbers(Dictionary data) @@ -62,6 +71,44 @@ private void FixFalseValues(Dictionary data) value => false); } + private void FixLambdas(Dictionary data) + { + if (data.ContainsKey("lambda")) + { + var res = (Dictionary)data["lambda"]; + data["lambda"] = res["js"]; + } + + Visit(data, + value => (value.Contains("function()") || value.Contains("function(txt)")), + value => + { + if (value.Contains("function()")) + { + var match = Regex.Match(value, @"function\(\)\s* {\s*return\s*([A-Za-z \"">={(}?:)]*)\s* }"); + if(match.Success) + { + var body = match.Groups[1].Value; + var lambda = Expression.Lambda>(Expression.Constant(body)); + return lambda.Compile(); + } + + } + else if (value.Contains("function(txt)")) + { + var match = Regex.Match(value, @"function\((\w*)\)\s*{\s*([A-Za-z \"">={(}?:)]*)\s* }"); + if(match.Success) + { + var argumentName = match.Groups[1].Value; + var body = match.Groups[2].Value; + var lambda = Expression.Lambda>(Expression.Constant(body), ParameterExpression.Parameter(typeof(string), argumentName)); + return lambda.Compile(); + } + } + return null; + }); + } + private void Visit(object value, Func pred, Func func) { if (value is List) @@ -108,6 +155,7 @@ private void Visit(object value, Func pred, Func f public IEnumerable Inverted() { return GetTestCases("inverted"); } public IEnumerable Partials() { return GetTestCases("partials"); } public IEnumerable Sections() { return GetTestCases("sections"); } + public IEnumerable Lambdas() { return GetTestCases("~lambdas"); } public IEnumerable GetTestCases(string file) { From 1904c8bc579568114092e655c28c60ef7c6e57e0 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 18:46:49 +0100 Subject: [PATCH 08/21] Added DynamicExpresso.Core to Core.Tests #84 Used to generate delegates on the fly for lambda definitions. --- Nustache.Core.Tests/Mustache_Spec.cs | 7 +++++-- Nustache.Core.Tests/Nustache.Core.Tests.csproj | 3 +++ Nustache.Core.Tests/packages.config | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Nustache.Core.Tests/Mustache_Spec.cs b/Nustache.Core.Tests/Mustache_Spec.cs index a6e734a..14fc876 100644 --- a/Nustache.Core.Tests/Mustache_Spec.cs +++ b/Nustache.Core.Tests/Mustache_Spec.cs @@ -9,6 +9,7 @@ using System.Reflection.Emit; using System.Text; using System.Text.RegularExpressions; +using DynamicExpresso; using Microsoft.CSharp; using NUnit.Framework; using YamlDotNet.RepresentationModel.Serialization; @@ -89,8 +90,10 @@ private void FixLambdas(Dictionary data) if(match.Success) { var body = match.Groups[1].Value; - var lambda = Expression.Lambda>(Expression.Constant(body)); - return lambda.Compile(); + + var interpreter = new Interpreter(); + + return interpreter.ParseAsDelegate>(body); } } diff --git a/Nustache.Core.Tests/Nustache.Core.Tests.csproj b/Nustache.Core.Tests/Nustache.Core.Tests.csproj index 13e4806..a7e5685 100644 --- a/Nustache.Core.Tests/Nustache.Core.Tests.csproj +++ b/Nustache.Core.Tests/Nustache.Core.Tests.csproj @@ -38,6 +38,9 @@ AllRules.ruleset + + ..\packages\DynamicExpresso.Core.1.3.0.0\lib\net40\DynamicExpresso.Core.dll + False ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll diff --git a/Nustache.Core.Tests/packages.config b/Nustache.Core.Tests/packages.config index 16c3a48..becd9d9 100644 --- a/Nustache.Core.Tests/packages.config +++ b/Nustache.Core.Tests/packages.config @@ -1,5 +1,6 @@  + From df66c14fa87b40c5cb74963e444dc06da2066a5e Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 18:47:43 +0100 Subject: [PATCH 09/21] Fixed escaping characters when using Lambda #84 --- Nustache.Core/VariableReference.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Nustache.Core/VariableReference.cs b/Nustache.Core/VariableReference.cs index dcd58bf..9d75e42 100644 --- a/Nustache.Core/VariableReference.cs +++ b/Nustache.Core/VariableReference.cs @@ -44,6 +44,11 @@ public override void Render(RenderContext context) if(lambda != null) { var lambdaResult = lambda(); + + lambdaResult = _escaped + ? Encoders.HtmlEncode(lambdaResult.ToString()) + : lambdaResult.ToString(); + using (System.IO.TextReader sr = new System.IO.StringReader(lambdaResult)) { var template = new Template(); From 8e8b9d3cb4735ab9bdc140e4ed9546c35f1e4e18 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 20:52:51 +0100 Subject: [PATCH 10/21] Added Start and End Delimiters for Override #84 This is because block lambdas should inherit their parent delimiters --- Nustache.Core/Block.cs | 7 +++++-- Nustache.Core/RenderContext.cs | 2 ++ Nustache.Core/Scanner.cs | 18 +++++++++++++++++- Nustache.Core/Template.cs | 19 ++++++++++++++++--- Nustache.Core/VariableReference.cs | 9 ++++++--- 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Nustache.Core/Block.cs b/Nustache.Core/Block.cs index 2830212..1ec48dd 100644 --- a/Nustache.Core/Block.cs +++ b/Nustache.Core/Block.cs @@ -16,14 +16,17 @@ public override void Render(RenderContext context) { var value = context.GetValue(Name); - var lambda = value as Lambda; + var lambda = value as Lambda; if (lambda != null) { - var lambdaResult = lambda(InnerSource()); + var lambdaResult = lambda(InnerSource()).ToString(); using(TextReader sr = new StringReader(lambdaResult)) { var template = new Template(); + template.StartDelimiter = context.ActiveStartDelimiter; + template.EndDelimiter = context.ActiveEndDelimiter; + template.Load(sr); context.Enter(template); template.Render(context); diff --git a/Nustache.Core/RenderContext.cs b/Nustache.Core/RenderContext.cs index 0501cc5..ff3f905 100644 --- a/Nustache.Core/RenderContext.cs +++ b/Nustache.Core/RenderContext.cs @@ -27,6 +27,8 @@ public class RenderContext private string _indent; private bool _lineEnded; private readonly Regex _indenter = new Regex("\n(?!$)"); + public string ActiveStartDelimiter { get; set; } + public string ActiveEndDelimiter { get; set; } public RenderContext(Section section, object data, TextWriter writer, TemplateLocator templateLocator, RenderContextBehaviour renderContextBehaviour = null) { diff --git a/Nustache.Core/Scanner.cs b/Nustache.Core/Scanner.cs index 575ec30..4ae6df4 100644 --- a/Nustache.Core/Scanner.cs +++ b/Nustache.Core/Scanner.cs @@ -6,10 +6,23 @@ namespace Nustache.Core public class Scanner { private static readonly Regex DelimitersRegex = new Regex(@"^=\s*(\S+)\s+(\S+)\s*=$"); + public string StartDelimiter { get; set; } + public string EndDelimiter { get; set; } + + public Scanner() + : this("{{", "}}") + { + } + + public Scanner(string startDelimiter, string endDelimiter) + { + StartDelimiter = startDelimiter; + EndDelimiter = endDelimiter; + } public IEnumerable Scan(string template) { - var regex = MakeRegex("{{", "}}"); + var regex = MakeRegex(StartDelimiter, EndDelimiter); var i = 0; var lineEnded = false; @@ -40,6 +53,9 @@ public IEnumerable Scan(string template) { var start = delimiters.Groups[1].Value; var end = delimiters.Groups[2].Value; + this.StartDelimiter = start; + this.EndDelimiter = end; + regex = MakeRegex(start, end); } } diff --git a/Nustache.Core/Template.cs b/Nustache.Core/Template.cs index d76ee9c..3434633 100644 --- a/Nustache.Core/Template.cs +++ b/Nustache.Core/Template.cs @@ -4,14 +4,24 @@ namespace Nustache.Core { public class Template : Section { + public string StartDelimiter { get; set; } + public string EndDelimiter { get; set; } + public Template() : this("#template") // I'm not happy about this fake name. { } public Template(string name) + : this(name, "{{", "}}") + { + } + + public Template(string name, string startDelimiter, string endDelimiter) : base(name) { + StartDelimiter = startDelimiter; + EndDelimiter = endDelimiter; } /// @@ -29,10 +39,12 @@ public void Load(TextReader reader) { string template = reader.ReadToEnd(); - var scanner = new Scanner(); + var scanner = new Scanner(StartDelimiter, EndDelimiter); var parser = new Parser(); - parser.Parse(this, scanner.Scan(template)); + // After load get the last state of the delimiters to save in the context. + StartDelimiter = scanner.StartDelimiter; + EndDelimiter = scanner.EndDelimiter; } /// @@ -52,11 +64,12 @@ public void Render(object data, TextWriter writer, TemplateLocator templateLocat public void Render(object data, TextWriter writer, TemplateLocator templateLocator, RenderContextBehaviour renderContextBehaviour) { var context = new RenderContext(this, data, writer, templateLocator, renderContextBehaviour); + context.ActiveStartDelimiter = StartDelimiter; + context.ActiveEndDelimiter = EndDelimiter; Render(context); writer.Flush(); } - } } \ No newline at end of file diff --git a/Nustache.Core/VariableReference.cs b/Nustache.Core/VariableReference.cs index 9d75e42..ceb5881 100644 --- a/Nustache.Core/VariableReference.cs +++ b/Nustache.Core/VariableReference.cs @@ -40,10 +40,10 @@ public override void Render(RenderContext context) { var value = context.GetValue(_path); - var lambda = value as Lambda; + var lambda = value as Lambda; if(lambda != null) { - var lambdaResult = lambda(); + var lambdaResult = lambda().ToString(); lambdaResult = _escaped ? Encoders.HtmlEncode(lambdaResult.ToString()) @@ -51,7 +51,10 @@ public override void Render(RenderContext context) using (System.IO.TextReader sr = new System.IO.StringReader(lambdaResult)) { - var template = new Template(); + Template template = new Template(); + template.StartDelimiter = "{{"; + template.EndDelimiter = "}}"; + template.Load(sr); context.Enter(template); template.Render(context); From 45812b8970e7b924e4c97960358b77d3310d8752 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 20:54:53 +0100 Subject: [PATCH 11/21] Fixed test for Interpolation across muliple calls #84. This currently has a hack since the library doesn't support updating static variables. --- Nustache.Core.Tests/Describe_Lambda.cs | 11 ---------- Nustache.Core.Tests/Mustache_Spec.cs | 29 +++++++++++++++++++------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Nustache.Core.Tests/Describe_Lambda.cs b/Nustache.Core.Tests/Describe_Lambda.cs index db83ca5..3da5dc1 100644 --- a/Nustache.Core.Tests/Describe_Lambda.cs +++ b/Nustache.Core.Tests/Describe_Lambda.cs @@ -15,16 +15,5 @@ public void The_text_passed_is_the_literal_block_unrendered() }); Assert.AreEqual("Nustache is awesome.", result); } - - [Test] - public void Lambdas_Return_Should_Be_Interpolated() - { - var result = Render.StringToString("Hello, {{lambda}}!", new - { - lambda = (Lambda)(() => "World") - }); - - Assert.AreEqual("Hello, World!", result); - } } } \ No newline at end of file diff --git a/Nustache.Core.Tests/Mustache_Spec.cs b/Nustache.Core.Tests/Mustache_Spec.cs index 14fc876..8f898ce 100644 --- a/Nustache.Core.Tests/Mustache_Spec.cs +++ b/Nustache.Core.Tests/Mustache_Spec.cs @@ -19,6 +19,8 @@ namespace Nustache.Core.Tests [TestFixture] public class Mustache_Spec { + public static Int32 GlobalCalls; + [Test] [TestCaseSource("Comments")] [TestCaseSource("Delimiters")] @@ -77,7 +79,20 @@ private void FixLambdas(Dictionary data) if (data.ContainsKey("lambda")) { var res = (Dictionary)data["lambda"]; - data["lambda"] = res["js"]; + + //Hack for Interpolation Multiple calls as it uses globals which the library doesn't support entirely. + if (((String)res["js"]).Contains(".calls")) + { + data["lambda"] = (Lambda)(() => + { + return ++Mustache_Spec.GlobalCalls; + }); + } + else + { + data["lambda"] = res["js"]; + } + } Visit(data, @@ -86,26 +101,24 @@ private void FixLambdas(Dictionary data) { if (value.Contains("function()")) { - var match = Regex.Match(value, @"function\(\)\s* {\s*return\s*([A-Za-z \"">={(}?:)]*)\s* }"); + var match = Regex.Match(value, @"function\(\)\s*{\s*return\s*([A-Za-z0-9 \"">=|{(}?+#._:;)]*)\s* }"); if(match.Success) { var body = match.Groups[1].Value; - var interpreter = new Interpreter(); - - return interpreter.ParseAsDelegate>(body); + return new Interpreter().ParseAsDelegate>(body); } } else if (value.Contains("function(txt)")) { - var match = Regex.Match(value, @"function\((\w*)\)\s*{\s*([A-Za-z \"">={(}?:)]*)\s* }"); + var match = Regex.Match(value, @"function\((\w*)\)\s*{\s*return\s*([A-Za-z0-9 \"">=|{(}?+#._:;)]*)\s* }"); if(match.Success) { var argumentName = match.Groups[1].Value; var body = match.Groups[2].Value; - var lambda = Expression.Lambda>(Expression.Constant(body), ParameterExpression.Parameter(typeof(string), argumentName)); - return lambda.Compile(); + + return new Interpreter().ParseAsDelegate>(body, argumentName); } } return null; From 050391d22a08653a2cd1635f0f01e552bcd75cdb Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 21:04:14 +0100 Subject: [PATCH 12/21] Added pre-parse to lambdas spec file #84 --- Nustache.Core.Tests/Mustache_Spec.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Nustache.Core.Tests/Mustache_Spec.cs b/Nustache.Core.Tests/Mustache_Spec.cs index 8f898ce..1dc596b 100644 --- a/Nustache.Core.Tests/Mustache_Spec.cs +++ b/Nustache.Core.Tests/Mustache_Spec.cs @@ -176,6 +176,8 @@ private void Visit(object value, Func pred, Func f public IEnumerable GetTestCases(string file) { var text = File.ReadAllText(string.Format("../../../spec/specs/{0}.yml", file)); + if (file.Equals("~lambdas")) text = CleanLambdaFile(text); + var deserializer = new Deserializer(); var doc = deserializer.Deserialize(new StringReader(text)); @@ -183,6 +185,11 @@ public IEnumerable GetTestCases(string file) .Select(test => new TestCaseData(test.name, test.data, test.template, test.partials, test.expected) .SetName(file + ": " + test.name)); } + + private string CleanLambdaFile(String fileContents) + { + return fileContents.Replace("!code", ""); + } } public class SpecDoc From 97e17b62540db44da87854f338c4762a9522f868 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sun, 19 Apr 2015 21:04:41 +0100 Subject: [PATCH 13/21] Fixed some Typos during development #84 All tests running successfully --- Nustache.Core.Tests/Describe_Lambda.cs | 2 +- Nustache.Core.Tests/Mustache_Spec.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Nustache.Core.Tests/Describe_Lambda.cs b/Nustache.Core.Tests/Describe_Lambda.cs index 3da5dc1..7743993 100644 --- a/Nustache.Core.Tests/Describe_Lambda.cs +++ b/Nustache.Core.Tests/Describe_Lambda.cs @@ -10,7 +10,7 @@ public void The_text_passed_is_the_literal_block_unrendered() { var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new { - wrapped = (Lambda)((body) => string.Format("{0}", body)), + wrapped = (Lambda)((body) => string.Format("{0}", body)), name = "Nustache" }); Assert.AreEqual("Nustache is awesome.", result); diff --git a/Nustache.Core.Tests/Mustache_Spec.cs b/Nustache.Core.Tests/Mustache_Spec.cs index 1dc596b..44e9b1d 100644 --- a/Nustache.Core.Tests/Mustache_Spec.cs +++ b/Nustache.Core.Tests/Mustache_Spec.cs @@ -118,7 +118,7 @@ private void FixLambdas(Dictionary data) var argumentName = match.Groups[1].Value; var body = match.Groups[2].Value; - return new Interpreter().ParseAsDelegate>(body, argumentName); + return new Interpreter().ParseAsDelegate>(body, argumentName); } } return null; From c4097d17fdde0ced4486f21d078ba799514985c3 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sat, 25 Apr 2015 11:34:24 +0100 Subject: [PATCH 14/21] Added handling for non typed delegate lambdas #33 Also relates to #84. This is slower since it uses dynamic invoke around the func as casting delegates isn't simple due to possible variation in types. --- Nustache.Core.Tests/Describe_Render.cs | 12 ++++++++++++ Nustache.Core/VariableReference.cs | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Nustache.Core.Tests/Describe_Render.cs b/Nustache.Core.Tests/Describe_Render.cs index 2210569..09a6e88 100644 --- a/Nustache.Core.Tests/Describe_Render.cs +++ b/Nustache.Core.Tests/Describe_Render.cs @@ -76,5 +76,17 @@ public void It_can_use_an_ampersand_to_render_unencoded_text() Assert.AreEqual("", result); } + + [Test] + public void It_can_render_nontyped_delegate_functions() + { + var result = Render.StringToString("{{foo}}, {{bar}}", new + { + foo = "bar", + bar = new System.Func(() => { return "foo"; }) + }); + + Assert.AreEqual("bar, foo", result); + } } } \ No newline at end of file diff --git a/Nustache.Core/VariableReference.cs b/Nustache.Core/VariableReference.cs index ceb5881..a9e1e1d 100644 --- a/Nustache.Core/VariableReference.cs +++ b/Nustache.Core/VariableReference.cs @@ -40,7 +40,7 @@ public override void Render(RenderContext context) { var value = context.GetValue(_path); - var lambda = value as Lambda; + var lambda = CheckValueIsDelegateOrLambda(value); if(lambda != null) { var lambdaResult = lambda().ToString(); @@ -78,6 +78,20 @@ public override void Render(RenderContext context) } } + public Lambda CheckValueIsDelegateOrLambda(object value) + { + var lambda = value as Lambda; + if(lambda != null) return lambda; + + if (value is Delegate) + { + var delegateValue = (Delegate)value; + return (Lambda)(() => (object)delegateValue.DynamicInvoke()); + } + + return null; + } + public override string Source() { return "{{" + (!Escaped ? "&" : "") + _path + "}}"; From 59d5bb1acdb31bbf9604edf3de31efb10d83803f Mon Sep 17 00:00:00 2001 From: Romanx Date: Sat, 25 Apr 2015 11:43:10 +0100 Subject: [PATCH 15/21] Added non typed delegate functions for blocks #33 --- Nustache.Core.Tests/Describe_Render.cs | 11 +++++++++++ Nustache.Core/Block.cs | 16 +++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Nustache.Core.Tests/Describe_Render.cs b/Nustache.Core.Tests/Describe_Render.cs index 09a6e88..4328cbd 100644 --- a/Nustache.Core.Tests/Describe_Render.cs +++ b/Nustache.Core.Tests/Describe_Render.cs @@ -88,5 +88,16 @@ public void It_can_render_nontyped_delegate_functions() Assert.AreEqual("bar, foo", result); } + + [Test] + public void It_can_render_nontyped_delegate_functions_with_body() + { + var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new + { + wrapped = new System.Func((body) => string.Format("{0}", body)), + name = "Nustache" + }); + Assert.AreEqual("Nustache is awesome.", result); + } } } \ No newline at end of file diff --git a/Nustache.Core/Block.cs b/Nustache.Core/Block.cs index 1ec48dd..80917dd 100644 --- a/Nustache.Core/Block.cs +++ b/Nustache.Core/Block.cs @@ -16,7 +16,7 @@ public override void Render(RenderContext context) { var value = context.GetValue(Name); - var lambda = value as Lambda; + var lambda = CheckValueIsDelegateOrLambda(value); if (lambda != null) { @@ -78,6 +78,20 @@ public override void Render(RenderContext context) } } + public Lambda CheckValueIsDelegateOrLambda(object value) + { + var lambda = value as Lambda; + if (lambda != null) return lambda; + + if (value is Delegate) + { + var delegateValue = (Delegate)value; + return (Lambda)((body) => (object)delegateValue.DynamicInvoke(body)); + } + + return null; + } + public override string ToString() { return string.Format("Block(\"{0}\")", Name); From 8ecd09200f37b8df9767ffa31b1d9cc1d91be865 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sat, 25 Apr 2015 11:51:28 +0100 Subject: [PATCH 16/21] Broke helper proxies added check to delegates type #33 --- Nustache.Core/Block.cs | 2 +- Nustache.Core/VariableReference.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Nustache.Core/Block.cs b/Nustache.Core/Block.cs index 80917dd..3cb04ef 100644 --- a/Nustache.Core/Block.cs +++ b/Nustache.Core/Block.cs @@ -83,7 +83,7 @@ public Lambda CheckValueIsDelegateOrLambda(object value) var lambda = value as Lambda; if (lambda != null) return lambda; - if (value is Delegate) + if (value is Delegate && !(value is HelperProxy)) { var delegateValue = (Delegate)value; return (Lambda)((body) => (object)delegateValue.DynamicInvoke(body)); diff --git a/Nustache.Core/VariableReference.cs b/Nustache.Core/VariableReference.cs index a9e1e1d..d205721 100644 --- a/Nustache.Core/VariableReference.cs +++ b/Nustache.Core/VariableReference.cs @@ -83,7 +83,7 @@ public Lambda CheckValueIsDelegateOrLambda(object value) var lambda = value as Lambda; if(lambda != null) return lambda; - if (value is Delegate) + if (value is Delegate && !(value is HelperProxy)) { var delegateValue = (Delegate)value; return (Lambda)(() => (object)delegateValue.DynamicInvoke()); From 0491971d3325560cb8cbf5305230c61cb08c2990 Mon Sep 17 00:00:00 2001 From: Romanx Date: Sat, 25 Apr 2015 11:52:40 +0100 Subject: [PATCH 17/21] Moved the untyped delegate tests to Lambda tests #33 --- Nustache.Core.Tests/Describe_Lambda.cs | 23 +++++++++++++++++++++++ Nustache.Core.Tests/Describe_Render.cs | 23 ----------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Nustache.Core.Tests/Describe_Lambda.cs b/Nustache.Core.Tests/Describe_Lambda.cs index 7743993..e7e5531 100644 --- a/Nustache.Core.Tests/Describe_Lambda.cs +++ b/Nustache.Core.Tests/Describe_Lambda.cs @@ -15,5 +15,28 @@ public void The_text_passed_is_the_literal_block_unrendered() }); Assert.AreEqual("Nustache is awesome.", result); } + + [Test] + public void It_can_render_nontyped_delegate_functions() + { + var result = Render.StringToString("{{foo}}, {{bar}}", new + { + foo = "bar", + bar = new System.Func(() => { return "foo"; }) + }); + + Assert.AreEqual("bar, foo", result); + } + + [Test] + public void It_can_render_nontyped_delegate_functions_with_body() + { + var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new + { + wrapped = new System.Func((body) => string.Format("{0}", body)), + name = "Nustache" + }); + Assert.AreEqual("Nustache is awesome.", result); + } } } \ No newline at end of file diff --git a/Nustache.Core.Tests/Describe_Render.cs b/Nustache.Core.Tests/Describe_Render.cs index 4328cbd..2210569 100644 --- a/Nustache.Core.Tests/Describe_Render.cs +++ b/Nustache.Core.Tests/Describe_Render.cs @@ -76,28 +76,5 @@ public void It_can_use_an_ampersand_to_render_unencoded_text() Assert.AreEqual("", result); } - - [Test] - public void It_can_render_nontyped_delegate_functions() - { - var result = Render.StringToString("{{foo}}, {{bar}}", new - { - foo = "bar", - bar = new System.Func(() => { return "foo"; }) - }); - - Assert.AreEqual("bar, foo", result); - } - - [Test] - public void It_can_render_nontyped_delegate_functions_with_body() - { - var result = Render.StringToString("{{#wrapped}}{{name}} is awesome.{{/wrapped}}", new - { - wrapped = new System.Func((body) => string.Format("{0}", body)), - name = "Nustache" - }); - Assert.AreEqual("Nustache is awesome.", result); - } } } \ No newline at end of file From e62557d4f4076352d94d74c007b483eeee185378 Mon Sep 17 00:00:00 2001 From: Romanx Date: Mon, 27 Apr 2015 20:27:46 +0100 Subject: [PATCH 18/21] Reordered ValueGetterFactories to avoid error #86 --- Nustache.Core.Tests/Describe_ValueGetter.cs | 10 ++++++++++ Nustache.Core/ValueGetterFactory.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Nustache.Core.Tests/Describe_ValueGetter.cs b/Nustache.Core.Tests/Describe_ValueGetter.cs index 2611dcc..e3cde06 100644 --- a/Nustache.Core.Tests/Describe_ValueGetter.cs +++ b/Nustache.Core.Tests/Describe_ValueGetter.cs @@ -194,6 +194,16 @@ public void It_gets_case_insensitive_DataRow_Values() Assert.AreEqual(123, ValueGetter.GetValue(target, "intcolumn")); } + [Test] + public void It_gets_DataRow_values_using_property_names() + { + DataTable dt = new DataTable(); + dt.Columns.Add("item", typeof(int)); + dt.Rows.Add(new object[] { 123 }); + var target = dt.Rows[0]; + Assert.AreEqual(123, ValueGetter.GetValue(target, "item")); + } + [Test] public void It_gets_NameValueCollection_values() { diff --git a/Nustache.Core/ValueGetterFactory.cs b/Nustache.Core/ValueGetterFactory.cs index 859a797..0ce4690 100644 --- a/Nustache.Core/ValueGetterFactory.cs +++ b/Nustache.Core/ValueGetterFactory.cs @@ -84,13 +84,13 @@ public static class ValueGetterFactories new XmlNodeValueGetterFactory(), new PropertyDescriptorValueGetterFactory(), new GenericDictionaryValueGetterFactory(), + new DataRowGetterFactory(), new DictionaryValueGetterFactory(), new MethodInfoValueGatterFactory(), new PropertyInfoValueGetterFactory(), new FieldInfoValueGetterFactory(), new ListValueByIndexGetterFactory(), - new NameValueCollectionGetterFactory(), - new DataRowGetterFactory() + new NameValueCollectionGetterFactory() }; public static ValueGetterFactoryCollection Factories From fe6409ada80edf392e0209efdfcf310bd0c9c20d Mon Sep 17 00:00:00 2001 From: Romanx Date: Wed, 29 Apr 2015 23:06:03 +0100 Subject: [PATCH 19/21] ValueGetters returning IEnumerables with no values For context of error please see #89 --- Nustache.Core/RenderContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nustache.Core/RenderContext.cs b/Nustache.Core/RenderContext.cs index 4a02a87..3c3c8ea 100644 --- a/Nustache.Core/RenderContext.cs +++ b/Nustache.Core/RenderContext.cs @@ -186,7 +186,7 @@ public IEnumerable GetValues(string path) yield return value; } } - else if (value is IEnumerable) + else if (value is IEnumerable && IsTruthy(value)) //Use IsTruthy to determine if it has IEnumberable values. { foreach (var item in ((IEnumerable)value)) { From d68d80000f44076a6b60490869c5e8cb2731c867 Mon Sep 17 00:00:00 2001 From: Romanx Date: Wed, 29 Apr 2015 23:10:55 +0100 Subject: [PATCH 20/21] Added test for JSON.net IEnumberable Error #89 --- Nustache.Core.Tests/Describe_Render.cs | 16 ++++++++++++++++ Nustache.Core.Tests/Nustache.Core.Tests.csproj | 3 +++ Nustache.Core.Tests/packages.config | 1 + 3 files changed, 20 insertions(+) diff --git a/Nustache.Core.Tests/Describe_Render.cs b/Nustache.Core.Tests/Describe_Render.cs index 2210569..fcf7aa0 100644 --- a/Nustache.Core.Tests/Describe_Render.cs +++ b/Nustache.Core.Tests/Describe_Render.cs @@ -76,5 +76,21 @@ public void It_can_use_an_ampersand_to_render_unencoded_text() Assert.AreEqual("", result); } + + [Test] + public void It_ignores_Newtonsoft_IEnumerable_results_with_no_values() + { + var template = @"{{#link}}{{{title}}}{{/link}}"; + var json = @"{ + ""link"": { + ""url"": ""https://github.com/jdiamond/Nustache/"", + ""title"": ""Nustache Main"", + ""classname"": ""nustache--logo"" + } + }"; + var result = Render.StringToString(template, Newtonsoft.Json.Linq.JObject.Parse(json)); + + Assert.AreEqual(@"Nustache Main", result); + } } } \ No newline at end of file diff --git a/Nustache.Core.Tests/Nustache.Core.Tests.csproj b/Nustache.Core.Tests/Nustache.Core.Tests.csproj index 13e4806..bd07ea6 100644 --- a/Nustache.Core.Tests/Nustache.Core.Tests.csproj +++ b/Nustache.Core.Tests/Nustache.Core.Tests.csproj @@ -42,6 +42,9 @@ False ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + ..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll + False ..\packages\NUnit.2.6.2\lib\nunit.framework.dll diff --git a/Nustache.Core.Tests/packages.config b/Nustache.Core.Tests/packages.config index 16c3a48..b9e929c 100644 --- a/Nustache.Core.Tests/packages.config +++ b/Nustache.Core.Tests/packages.config @@ -1,6 +1,7 @@  + From 70c00a6337d1ec90729e6d401aa5018c88fd312b Mon Sep 17 00:00:00 2001 From: Alex McAuliffe Date: Thu, 30 Apr 2015 11:13:24 +0100 Subject: [PATCH 21/21] Previous fix broke other tests #89 Now looks for the type of JToken by string as i didn't want to include JSON.net for a check. --- Nustache.Core.Tests/Describe_Render.cs | 4 ++-- Nustache.Core/RenderContext.cs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Nustache.Core.Tests/Describe_Render.cs b/Nustache.Core.Tests/Describe_Render.cs index fcf7aa0..7b7699d 100644 --- a/Nustache.Core.Tests/Describe_Render.cs +++ b/Nustache.Core.Tests/Describe_Render.cs @@ -80,8 +80,8 @@ public void It_can_use_an_ampersand_to_render_unencoded_text() [Test] public void It_ignores_Newtonsoft_IEnumerable_results_with_no_values() { - var template = @"{{#link}}{{{title}}}{{/link}}"; - var json = @"{ + const string template = @"{{#link}}{{{title}}}{{/link}}"; + const string json = @"{ ""link"": { ""url"": ""https://github.com/jdiamond/Nustache/"", ""title"": ""Nustache Main"", diff --git a/Nustache.Core/RenderContext.cs b/Nustache.Core/RenderContext.cs index 9e48408..63354e9 100644 --- a/Nustache.Core/RenderContext.cs +++ b/Nustache.Core/RenderContext.cs @@ -174,6 +174,10 @@ public IEnumerable GetValues(string path) yield return value; } } + else if (value.GetType().ToString().Equals("Newtonsoft.Json.Linq.JValue")) + { + yield return value; + } else if (GenericIDictionaryUtil.IsInstanceOfGenericIDictionary(value)) { if ((value as IEnumerable).GetEnumerator().MoveNext()) @@ -189,7 +193,7 @@ public IEnumerable GetValues(string path) yield return value; } } - else if (value is IEnumerable && IsTruthy(value)) //Use IsTruthy to determine if it has IEnumberable values. + else if (value is IEnumerable) { foreach (var item in ((IEnumerable)value)) {