diff --git a/src/CommonAssemblyInfo.cs b/src/CommonAssemblyInfo.cs index 56f4bee7..29fa6682 100644 --- a/src/CommonAssemblyInfo.cs +++ b/src/CommonAssemblyInfo.cs @@ -14,10 +14,10 @@ [assembly: ComVisibleAttribute(false)] [assembly: AssemblyVersionAttribute("1.0")] -[assembly: AssemblyFileVersionAttribute("1.0.295.0")] +[assembly: AssemblyFileVersionAttribute("1.0.296.0")] [assembly: AssemblyCopyrightAttribute("Copyright © Louis DeJardin 2008-2009")] [assembly: AssemblyProductAttribute("Spark")] [assembly: AssemblyCompanyAttribute("Louis DeJardin")] [assembly: AssemblyConfigurationAttribute("release")] -[assembly: AssemblyInformationalVersionAttribute("1.0.295.0")] +[assembly: AssemblyInformationalVersionAttribute("1.0.296.0")] diff --git a/src/CommonVersionInfo.h b/src/CommonVersionInfo.h index 73251269..2d38e7f0 100644 --- a/src/CommonVersionInfo.h +++ b/src/CommonVersionInfo.h @@ -1,8 +1,8 @@ // this is an auto-generated file -#define VERSIONINFO_VERSIONSTRING "1.0.295.0" +#define VERSIONINFO_VERSIONSTRING "1.0.296.0" #define VERSIONINFO_MAJOR 1 #define VERSIONINFO_MINOR 0 -#define VERSIONINFO_BUILD 295 +#define VERSIONINFO_BUILD 296 #define VERSIONINFO_REVISION 0 #define VERSIONINFO_COPYRIGHT "Copyright © Louis DeJardin 2008-2009" #define VERSIONINFO_COMPANY "Louis DeJardin" diff --git a/src/Spark.Tests/Parser/CodeGrammarTester.cs b/src/Spark.Tests/Parser/CodeGrammarTester.cs index d561db3f..c89788f2 100644 --- a/src/Spark.Tests/Parser/CodeGrammarTester.cs +++ b/src/Spark.Tests/Parser/CodeGrammarTester.cs @@ -208,5 +208,24 @@ public void ClassKeywordUsedAsIdentifier() Assert.AreEqual(@"var @class=1;", Combine(result7.Value)); } + [Test] + public void LateBoundSyntaxBecomesEvalFunction() + { + var result1 = _grammar.Expression(Source("#foo.bar")); + Assert.AreEqual(@"Eval(""foo.bar"")", (string) result1.Value); + + var result2 = _grammar.Expression(Source("#foo .bar")); + Assert.AreEqual(@"Eval(""foo"") .bar", (string)result2.Value); + + var result3 = _grammar.Expression(Source("(string)#foo+'bar'")); + Assert.AreEqual(@"(string)Eval(""foo"")+""bar""", (string)result3.Value); + + var result4 = _grammar.Statement1(Source("Logger.Warn(#some.thing)")); + Assert.AreEqual(@"Logger.Warn(Eval(""some.thing""))", (string)new Snippets(result4.Value)); + + var result5 = _grammar.Statement1(Source("Logger.Warn(#some.thing)")); + Assert.AreEqual(@"Logger.Warn(Eval(""some.thing""))", (string)new Snippets(result5.Value)); + } + } } \ No newline at end of file diff --git a/src/Spark.Tests/Spark.Tests.Views/Home/LateBoundEvalResolvesViewData.spark b/src/Spark.Tests/Spark.Tests.Views/Home/LateBoundEvalResolvesViewData.spark new file mode 100644 index 00000000..9d375f2b --- /dev/null +++ b/src/Spark.Tests/Spark.Tests.Views/Home/LateBoundEvalResolvesViewData.spark @@ -0,0 +1,5 @@ + +

${#alpha}

+

${H(#alpha)}

+# Output.Write(#beta); +

4${#nosuchthing}2

diff --git a/src/Spark.Tests/Spark.Tests.csproj b/src/Spark.Tests/Spark.Tests.csproj index 52409b41..4ce29f2e 100644 --- a/src/Spark.Tests/Spark.Tests.csproj +++ b/src/Spark.Tests/Spark.Tests.csproj @@ -254,6 +254,9 @@ Always + + PreserveNewest + PreserveNewest diff --git a/src/Spark.Tests/SparkViewFactoryTester.cs b/src/Spark.Tests/SparkViewFactoryTester.cs index 08bbf800..814c1321 100644 --- a/src/Spark.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Tests/SparkViewFactoryTester.cs @@ -875,56 +875,56 @@ public void OnceAttribute() } - [Test] - public void EachAttributeWorksOnSpecialNodes() - { - mocks.ReplayAll(); - var viewContext = MakeViewContext("EachAttributeWorksOnSpecialNodes", null); - factory.RenderView(viewContext); - mocks.VerifyAll(); - string content = sb.ToString(); - - ContainsInOrder(content, - "

name-0-alpha

", - "

name-1-beta

", - "

name-2-gamma

", - "one", - "two", - "three"); - } - - [Test] - public void IfAttributeWorksOnSpecialNodes() - { - mocks.ReplayAll(); - var viewContext = MakeViewContext("IfAttributeWorksOnSpecialNodes", null); - factory.RenderView(viewContext); - mocks.VerifyAll(); - string content = sb.ToString(); - - ContainsInOrder(content, - "

name-0-alpha

", - "

name-2-gamma

", - "one", - "three"); - - Assert.IsFalse(content.Contains("beta")); - Assert.IsFalse(content.Contains("two")); - } - - [Test] + [Test] + public void EachAttributeWorksOnSpecialNodes() + { + mocks.ReplayAll(); + var viewContext = MakeViewContext("EachAttributeWorksOnSpecialNodes", null); + factory.RenderView(viewContext); + mocks.VerifyAll(); + string content = sb.ToString(); + + ContainsInOrder(content, + "

name-0-alpha

", + "

name-1-beta

", + "

name-2-gamma

", + "one", + "two", + "three"); + } + + [Test] + public void IfAttributeWorksOnSpecialNodes() + { + mocks.ReplayAll(); + var viewContext = MakeViewContext("IfAttributeWorksOnSpecialNodes", null); + factory.RenderView(viewContext); + mocks.VerifyAll(); + string content = sb.ToString(); + + ContainsInOrder(content, + "

name-0-alpha

", + "

name-2-gamma

", + "one", + "three"); + + Assert.IsFalse(content.Contains("beta")); + Assert.IsFalse(content.Contains("two")); + } + + [Test] public void OnceAttributeWorksOnSpecialNodes() - { - mocks.ReplayAll(); + { + mocks.ReplayAll(); var viewContext = MakeViewContext("OnceAttributeWorksOnSpecialNodes", null); - factory.RenderView(viewContext); - mocks.VerifyAll(); - string content = sb.ToString(); - - ContainsInOrder(content, - "

name-0-alpha

", - "foo1", - "bar0", + factory.RenderView(viewContext); + mocks.VerifyAll(); + string content = sb.ToString(); + + ContainsInOrder(content, + "

name-0-alpha

", + "foo1", + "bar0", "quux2"); Assert.IsFalse(content.Contains("name-1")); @@ -934,5 +934,28 @@ public void OnceAttributeWorksOnSpecialNodes() Assert.IsFalse(content.Contains("bar1")); Assert.IsFalse(content.Contains("bar3")); } + + [Test] + public void LateBoundEvalResolvesViewData() + { + mocks.ReplayAll(); + var viewData = new StubViewData() + { + {"alpha", "hi"}, + {"beta", "yadda"} + }; + var viewContext = MakeViewContext("LateBoundEvalResolvesViewData", null, viewData); + factory.RenderView(viewContext); + mocks.VerifyAll(); + string content = sb.ToString(); + + ContainsInOrder(content, + "

hi

", + "

<strong>hi</strong>

", + "yadda", + "

42

"); + + } + } } diff --git a/src/Spark.Tests/Stubs/StubSparkView.cs b/src/Spark.Tests/Stubs/StubSparkView.cs index c3d33111..b71bf197 100644 --- a/src/Spark.Tests/Stubs/StubSparkView.cs +++ b/src/Spark.Tests/Stubs/StubSparkView.cs @@ -45,6 +45,11 @@ public string H(object content) { return HttpUtility.HtmlEncode(Convert.ToString(content)); } + + public object Eval(string expression) + { + return ViewData.Eval(expression); + } } public abstract class StubSparkView : StubSparkView diff --git a/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithAnonModel.spark b/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithAnonModel.spark new file mode 100644 index 00000000..5264e3b4 --- /dev/null +++ b/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithAnonModel.spark @@ -0,0 +1,4 @@ + +

${#Foo} ${#Bar.Text}

+ + diff --git a/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithFormatString.spark b/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithFormatString.spark new file mode 100644 index 00000000..701baa76 --- /dev/null +++ b/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithFormatString.spark @@ -0,0 +1,5 @@ + +

${#cost "#,##0.00"}

+ +

${#terms.Due 'yyyy/MM/dd'}

+ diff --git a/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithViewDataModel.spark b/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithViewDataModel.spark new file mode 100644 index 00000000..f0cbb67d --- /dev/null +++ b/src/Spark.Web.Mvc.Tests/AspNetMvc.Tests.Views/Home/EvalWithViewDataModel.spark @@ -0,0 +1,3 @@ + + +

${#Text}

diff --git a/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj b/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj index fc68e366..c4a43c83 100644 --- a/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj +++ b/src/Spark.Web.Mvc.Tests/Spark.Web.Mvc.Tests.csproj @@ -3,7 +3,7 @@ Debug AnyCPU - 9.0.21022 + 9.0.30729 2.0 {C8E58F51-8630-4420-8630-8E634EC8900E} Library @@ -90,6 +90,15 @@ Always + + Always + + + Always + + + Always + Always diff --git a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs index 11c49bc4..3383ae0d 100644 --- a/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs +++ b/src/Spark.Web.Mvc.Tests/SparkViewFactoryTester.cs @@ -470,5 +470,42 @@ public void CreatingViewEngineWithSimpleContainer() Assert.AreSame(viewFolder, viewEngine.ViewFolder); Assert.AreSame(viewFolder, viewFactory.ViewFolder); } + + + [Test] + public void EvalWithViewDataModel() + { + mocks.ReplayAll(); + FindViewAndRender("EvalWithViewDataModel", new Comment { Text = "Hello" }); + mocks.VerifyAll(); + + var content = output.ToString(); + Assert.That(content.Contains("

Hello

")); + } + + [Test] + public void EvalWithAnonModel() + { + mocks.ReplayAll(); + FindViewAndRender("EvalWithAnonModel", new { Foo = 42, Bar = new Comment { Text = "Hello" } }); + mocks.VerifyAll(); + + var content = output.ToString(); + Assert.That(content.Contains("

42 Hello

")); + } + + + [Test] + public void EvalWithFormatString() + { + mocks.ReplayAll(); + FindViewAndRender("EvalWithFormatString", new { Cost = 134567.89, terms = new { due = new DateTime(1971, 10, 14) } }); + mocks.VerifyAll(); + + var content = output.ToString(); + Assert.That(content.Contains("

134,567.89

")); + Assert.That(content.Contains("

1971/10/14

")); + } + } } diff --git a/src/Spark.Web.Mvc/SparkView.cs b/src/Spark.Web.Mvc/SparkView.cs index 0807862d..fa58ed55 100644 --- a/src/Spark.Web.Mvc/SparkView.cs +++ b/src/Spark.Web.Mvc/SparkView.cs @@ -140,6 +140,15 @@ public string H(object value) return Html.Encode(value); } + public object Eval(string expression) + { + return ViewData.Eval(expression); + } + public string Eval(string expression, string format) + { + return ViewData.Eval(expression, format); + } + protected virtual void SetViewData(ViewDataDictionary viewData) { _viewData = viewData; diff --git a/src/Spark/Parser/Code/CodeGrammar.cs b/src/Spark/Parser/Code/CodeGrammar.cs index d097ff76..254b936f 100644 --- a/src/Spark/Parser/Code/CodeGrammar.cs +++ b/src/Spark/Parser/Code/CodeGrammar.cs @@ -107,9 +107,33 @@ public CodeGrammar() var identifier = availableIdentifier .Or(Snip(Ch('@').And(identifierOrKeyword), hit => "@" + hit.Down)); + // parsing late bound properties #a.b.c into Eval("a.b.c") + + var dotProperty = Snip(Ch('.').And(identifierOrKeyword), + hit => hit.Left + hit.Down); + + var formatAppendage = Swap(Opt(Rep(Ch(' ', '\t'))) + .And(Ch('\"').And(Rep1(ChNot('\"'))).And(Ch('\"')) + .Or(Ch('\'').And(Rep1(ChNot('\''))).And(Ch('\'')))), + hit => ", \"{0:" + new string(hit.Down.Left.Down.ToArray()) + "}\""); + + var lateBound = Ch('#') + .And(Snip(identifierOrKeyword)) + .And(Snip(Rep(dotProperty))) + .And(Opt(formatAppendage)) + .Build(hit => (IList) new Snippets("Eval(\"") + .Concat(hit.Left.Left.Down) + .Concat(hit.Left.Down) + .Concat(new Snippets("\"")) + .Concat(hit.Down ?? new Snippet[0]) + .Concat(new Snippets(")")) + .ToList()); + + var codeStretch = Snip(Rep1( Swap(Ch("[["), "<") .Or(Swap(Ch("]]"), ">")) + .Or(lateBound) .Or(Snip(ChNot('\"', '\'', '{', '}'))) .Unless(identifier.Or(keyword).Or(SpecialCharCast)) .Unless(Ch("%>").Or(Ch("@\"")).Or(Ch("@'")).Or(Ch("//")).Or(Ch("/*"))))); @@ -137,6 +161,7 @@ public CodeGrammar() var statementPiece = Swap(Ch("[["), "<") .Or(Swap(Ch("]]"), ">")) + .Or(lateBound) .Or(Snip(ChNot('\"', '\''))) .Unless(SpecialCharCast) .Unless(Ch("@\"").Or(Ch("@'")).Or(Ch("//")).Or(Ch("/*"))); @@ -261,6 +286,16 @@ static ParseAction> Swap(ParseAction parser, stri return new ParseResult>(result.Rest, new[] { snippet }); }; } + static ParseAction> Swap(ParseAction parser, Func replacement) + { + return position => + { + var result = parser(position); + if (result == null) return null; + var snippet = new Snippet { Value = replacement(result.Value) }; + return new ParseResult>(result.Rest, new[] { snippet }); + }; + } public ParseAction> ExpressionTerms; public ParseAction Expression;