From bd9a84a0b74958ac9ca3fab45c125a6211c378fa Mon Sep 17 00:00:00 2001 From: Brian Palmer Date: Wed, 29 Feb 2012 11:25:51 -0700 Subject: [PATCH 01/20] properly handle amperstands when HTML escaping escapeExpression, when given a string like ">", was simply returning ">", not escaping the amperstand. This is incorrect, and makes it impossible to have Handlebars properly escape a string like "Escaped, looks like: <b>" If the intention of the user is to not escape these characters, then {{{}}} or {{&}} should be used --- lib/handlebars/utils.js | 3 ++- spec/qunit_spec.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index bd5d0eb8..b53c9ef4 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -22,6 +22,7 @@ Handlebars.SafeString.prototype.toString = function() { (function() { var escape = { + "&": "&", "<": "<", ">": ">", '"': """, @@ -29,7 +30,7 @@ Handlebars.SafeString.prototype.toString = function() { "`": "`" }; - var badChars = /&(?!\w+;)|[<>"'`]/g; + var badChars = /[&<>"'`]/g; var possible = /[&<>"'`]/; var escapeChar = function(chr) { diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index 3a87d6c7..b0ea551d 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -96,6 +96,8 @@ test("escaping expressions", function() { shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>', "by default expressions should be escaped"); + shouldCompileTo("{{awesome}}", {awesome: "Escaped, looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;', + "escaping should properly handle amperstands"); }); test("functions returning safestrings shouldn't be escaped", function() { From 4cf08693301cb5d7ee385965e26761434bfa0bfb Mon Sep 17 00:00:00 2001 From: fancyoung Date: Tue, 14 Aug 2012 17:09:36 +0800 Subject: [PATCH 02/20] Update README.markdown fix doc demo bug. --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index ed0b022d..27066f83 100644 --- a/README.markdown +++ b/README.markdown @@ -112,7 +112,7 @@ instance: ```js Handlebars.registerHelper('link_to', function(title, context) { - return "" + title + "" + return "" + title + "!" }); var context = { posts: [{url: "/hello-world", body: "Hello World!"}] }; @@ -124,7 +124,7 @@ template(context); // Would render: // // ``` From acc04c282635adab2a4d70a4811d46829fe272d8 Mon Sep 17 00:00:00 2001 From: Karl Westin Date: Mon, 27 Aug 2012 12:55:55 -0700 Subject: [PATCH 03/20] Fixed an issue where {{#array}} {{/array}} wouldn't pass in an @index data variable. I just thought it would be nice to add in this feature. I'm using the two of these a little interchangable, so if other people are doing that as well, it might help usability. --- lib/handlebars/base.js | 7 ++----- spec/qunit_spec.js | 10 ++++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index ca4b1596..d22eb40b 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -44,13 +44,10 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) { return inverse(this); } else if(type === "[object Array]") { if(context.length > 0) { - for(var i=0, j=context.length; i Date: Wed, 12 Sep 2012 11:15:31 +0100 Subject: [PATCH 04/20] Update Rakefile to use a locally installed version of jison The means root access is no longer required to build. --- .gitignore | 1 + Rakefile | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a98a3e7f..46570588 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ lib/handlebars/compiler/parser.js node_modules *.sublime-project *.sublime-workspace +npm-debug.log diff --git a/Rakefile b/Rakefile index b6043c29..d815d9ca 100644 --- a/Rakefile +++ b/Rakefile @@ -2,7 +2,7 @@ require "rubygems" require "bundler/setup" def compile_parser - system "jison src/handlebars.yy src/handlebars.l" + system "./node_modules/jison/lib/jison/cli-wrapper.js src/handlebars.yy src/handlebars.l" if $?.success? File.open("lib/handlebars/compiler/parser.js", "w") do |file| file.puts File.read("handlebars.js") + ";" @@ -15,11 +15,11 @@ def compile_parser end file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do - if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')} + if File.exists?('./node_modules/jison/lib/jison/cli-wrapper.js') compile_parser else puts "Jison is not installed. Trying `npm install jison`." - sh "npm install jison -g" + sh "npm install jison" compile_parser end end From 5a6e4f1ddde219d4043648816256342a447536c5 Mon Sep 17 00:00:00 2001 From: tomhuda Date: Wed, 12 Sep 2012 20:44:48 -0700 Subject: [PATCH 05/20] Fix repeated delimiter escaping --- spec/tokenizer_spec.rb | 9 +++++++++ src/handlebars.l | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index 2517adef..335536a0 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -51,6 +51,15 @@ describe "Tokenizer" do result[4].should be_token("CONTENT", "{{bar}} ") end + it "supports escaping multiple delimiters" do + result = tokenize("{{foo}} \\{{bar}} \\{{baz}}") + result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT)) + + result[3].should be_token("CONTENT", " ") + result[4].should be_token("CONTENT", "{{bar}} ") + result[5].should be_token("CONTENT", "{{baz}}") + end + it "supports escaping a triple stash" do result = tokenize("{{foo}} \\{{{bar}}} {{baz}}") result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE)) diff --git a/src/handlebars.l b/src/handlebars.l index 2e0c4f7a..4fc09819 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -11,7 +11,11 @@ [^\x00]+ { return 'CONTENT'; } -[^\x00]{2,}?/("{{") { this.popState(); return 'CONTENT'; } +[^\x00]{2,}?/("{{"|<>) { + if(yytext.slice(-1) !== "\\") this.popState(); + if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1); + return 'CONTENT'; + } "{{>" { return 'OPEN_PARTIAL'; } "{{#" { return 'OPEN_BLOCK'; } From 967c69b2da62aba541ca1e8bc9e1d3fda6abff59 Mon Sep 17 00:00:00 2001 From: Kiall Mac Innes Date: Thu, 13 Sep 2012 15:04:34 +0100 Subject: [PATCH 06/20] Ensure plain text partials supplied to registerPartials are compiled using data: true if necessary. --- lib/handlebars/runtime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index eb6d7570..f29efe45 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -56,7 +56,7 @@ Handlebars.VM = { } else if (!Handlebars.compile) { throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); } else { - partials[name] = Handlebars.compile(partial); + partials[name] = Handlebars.compile(partial, {data: data !== undefined}); return partials[name](context, options); } } From aba2269ddbf54ae89eaad67f7f370bd074155756 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 13 Sep 2012 17:51:12 -0500 Subject: [PATCH 07/20] Add nested hyphen test cases --- spec/qunit_spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index cbbc138c..f579d574 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -138,6 +138,8 @@ test("functions", function() { test("paths with hyphens", function() { shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)"); + shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)"); + shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)"); }); test("nested paths", function() { From ed8c2b95b804e855874463df782d31e4c6374c8a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 13 Sep 2012 17:54:07 -0500 Subject: [PATCH 08/20] Update npm repo to root repo --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8388941..8ed2c478 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ ], "repository": { "type": "git", - "url": "git://github.com/kpdecker/handlebars.js.git" + "url": "git://github.com/wycats/handlebars.js.git" }, "engines": { "node": ">=0.4.7" From d117b50fd3271c72c80db542158e45fdf019c314 Mon Sep 17 00:00:00 2001 From: Kiall Mac Innes Date: Fri, 14 Sep 2012 15:27:13 +0100 Subject: [PATCH 09/20] Ensure `rake spec` exits with a meaningful exit code --- Rakefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index d815d9ca..55c78d28 100644 --- a/Rakefile +++ b/Rakefile @@ -28,7 +28,8 @@ task :compile => "lib/handlebars/compiler/parser.js" desc "run the spec suite" task :spec => [:release] do - system "rspec -cfs spec" + rc = system "rspec -cfs spec" + fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0) end task :default => [:compile, :spec] From ce74c36118ffed1779889d97e6a2a1028ae61510 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Sat, 15 Sep 2012 20:53:11 -0700 Subject: [PATCH 10/20] Bumped package.json version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ed2c478..3f0050bf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "description": "Extension of the Mustache logicless template language", - "version": "1.0.6beta", + "version": "1.0.0-rc.1", "homepage": "http://www.handlebarsjs.com/", "keywords": [ "handlebars mustache template html" From b5074a88ec17917309f5482de004076f3f676ad1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 17 Sep 2012 19:27:23 -0500 Subject: [PATCH 11/20] Rev node version to 1.0.7 Per discussion with @wycats. Due to the version history of the node package and the manner in which npm's semver implementation works we need to continue the patch numbers utilized previously. Once the upstream implementation moves to 1.1 versioning we can sync the implementations again. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f0050bf..b037cdaa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "description": "Extension of the Mustache logicless template language", - "version": "1.0.0-rc.1", + "version": "1.0.7", "homepage": "http://www.handlebarsjs.com/", "keywords": [ "handlebars mustache template html" From 42120d1177cdb089c414e2b51ce441f95c53faad Mon Sep 17 00:00:00 2001 From: Tyson Tate Date: Wed, 19 Sep 2012 16:03:46 -0700 Subject: [PATCH 12/20] Bind functions to the context properly. The regression was introduced in 1.0.rc.1. This fixes issue #317. --- lib/handlebars/compiler/compiler.js | 4 ++-- spec/qunit_spec.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 108d0551..7578dd2d 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -642,7 +642,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.context.aliases.functionType = '"function"'; this.replaceStack(function(current) { - return "typeof " + current + " === functionType ? " + current + "() : " + current; + return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current; }); }, @@ -787,7 +787,7 @@ Handlebars.JavaScriptCompiler = function() {}; var nextStack = this.nextStack(); this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }'); - this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '() : ' + nextStack + '; }'); + this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }'); }, // [invokePartial] diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index f8238634..b1dd1efa 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -136,6 +136,8 @@ test("functions returning safestrings shouldn't be escaped", function() { test("functions", function() { shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome", "functions are called and render their output"); + shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome", + "functions are bound to the context"); }); test("paths with hyphens", function() { @@ -616,6 +618,11 @@ test("Invert blocks work in knownHelpers only mode", function() { var result = template({foo: false}); equal(result, "bar", "'bar' should === '" + result); }); +test("Functions are bound to the context in knownHelpers only mode", function() { + var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true}); + var result = template({foo: function() { return this.bar; }, bar: 'bar'}); + equal(result, "bar", "'bar' should === '" + result); +}); suite("blockHelperMissing"); @@ -624,6 +631,11 @@ test("lambdas are resolved by blockHelperMissing, not handlebars proper", functi var data = { truthy: function() { return true; } }; shouldCompileTo(string, data, "yep"); }); +test("lambdas resolved by blockHelperMissing are bound to the context", function() { + var string = "{{#truthy}}yep{{/truthy}}"; + var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } }; + shouldCompileTo(string, boundData, ""); +}); var teardown; suite("built-in helpers", { From a1c9acb8b13d769ac3ae88ba9962f544a377c83f Mon Sep 17 00:00:00 2001 From: Les Hill Date: Sat, 22 Sep 2012 08:09:33 -0700 Subject: [PATCH 13/20] Escaped single quotes in hash arguments --- spec/tokenizer_spec.rb | 12 ++++++++++++ src/handlebars.l | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index 335536a0..23480de1 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -195,6 +195,12 @@ describe "Tokenizer" do result[3].should be_token("STRING", "baz") end + it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do + result = tokenize("{{ foo bar \'baz\' }}") + result.should match_tokens(%w(OPEN ID ID STRING CLOSE)) + result[3].should be_token("STRING", "baz") + end + it "tokenizes String params with spaces inside as 'STRING'" do result = tokenize("{{ foo bar \"baz bat\" }}") result.should match_tokens(%w(OPEN ID ID STRING CLOSE)) @@ -207,6 +213,12 @@ describe "Tokenizer" do result[2].should be_token("STRING", %{bar"baz}) end + it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do + result = tokenize(%|{{ foo 'bar\\'baz' }}|) + result.should match_tokens(%w(OPEN ID STRING CLOSE)) + result[2].should be_token("STRING", %{bar'baz}) + end + it "tokenizes numbers" do result = tokenize(%|{{ foo 1 }}|) result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) diff --git a/src/handlebars.l b/src/handlebars.l index 4fc09819..68440bbb 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -35,7 +35,7 @@ "}}}" { this.popState(); return 'CLOSE'; } "}}" { this.popState(); return 'CLOSE'; } '"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; } -"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; } +"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; } "@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; } "true"/[}\s] { return 'BOOLEAN'; } "false"/[}\s] { return 'BOOLEAN'; } From 9e582fa0ebf167a0f2c9fa32cd43c214917e1760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=20Burc=C4=83?= Date: Fri, 28 Sep 2012 03:35:36 +0300 Subject: [PATCH 14/20] fix Block Helpers example --- README.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index ed0b022d..44727ed5 100644 --- a/README.markdown +++ b/README.markdown @@ -137,9 +137,9 @@ gets passed to the helper function. Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example: ```js -var source = "
    {{#people}}
  • {{{#link}}}{{name}}{{/link}}
  • {{/people}}
"; -Handlebars.registerHelper('link', function(context, fn) { - return '' + fn(this) + ''; +var source = "
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
"; +Handlebars.registerHelper('link', function(context, options) { + return '' + context.fn(this) + ''; }); var template = Handlebars.compile(source); From 1b197be7fcec82bc2e779aa9f5a09c629255582d Mon Sep 17 00:00:00 2001 From: Anton Rudeshko Date: Sun, 7 Oct 2012 06:20:06 -0700 Subject: [PATCH 15/20] Added AMD option doc --- README.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 44727ed5..5fdb62a5 100644 --- a/README.markdown +++ b/README.markdown @@ -19,7 +19,7 @@ In general, the syntax of Handlebars.js templates is a superset of Mustache temp Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template. ```js -var source = "

Hello, my name is {{name}}. I am from {{hometown}}. I have " + +var source = "

Hello, my name is {{name}}. I am from {{hometown}}. I have " + "{{kids.length}} kids:

" + "
    {{#kids}}
  • {{name}} is {{age}}
  • {{/kids}}
"; var template = Handlebars.compile(source); @@ -220,6 +220,7 @@ Precompile handlebar templates. Usage: handlebars template... Options: + -a, --amd Create an AMD format function (allows loading with RequireJS) [boolean] -f, --output Output File [string] -k, --known Known helpers [string] -o, --knownOnly Known helpers only [boolean] From 9589ab89492c23e1d201f61617ae2a3bf54a0afc Mon Sep 17 00:00:00 2001 From: Ross Hadden Date: Tue, 3 Jul 2012 16:53:26 -0400 Subject: [PATCH 16/20] Implemented ability to iterate over objects, ala for-in. Also added the 'key' key to looped objects. My goal is to make this {{@key}}, but am still working on it. I would also like to unobtrusively make @key or @index work for arrays. --- lib/handlebars/base.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index d22eb40b..03f41ef5 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -64,20 +64,33 @@ Handlebars.createFrame = Object.create || function(object) { Handlebars.registerHelper('each', function(context, options) { var fn = options.fn, inverse = options.inverse; - var ret = "", data; + var i = 0, ret = "", data; if (options.data) { data = Handlebars.createFrame(options.data); } - if(context && context.length > 0) { - for(var i=0, j=context.length; i Date: Sun, 14 Oct 2012 14:24:59 -0400 Subject: [PATCH 17/20] Added unit tests for #each with objects --- spec/qunit_spec.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index b1dd1efa..293c4131 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -33,7 +33,13 @@ Handlebars.registerHelper('helperMissing', function(helper, context) { function shouldCompileTo(string, hashOrArray, expected, message) { shouldCompileToWithPartials(string, hashOrArray, false, expected, message); } + function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) { + var result = compileWithPartials(string, hashOrArray, partials); + equal(result, expected, "'" + expected + "' should === '" + result + "': " + message); +} + +function compileWithPartials(string, hashOrArray, partials) { var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary; if(Object.prototype.toString.call(hashOrArray) === "[object Array]") { var helpers = hashOrArray[1]; @@ -51,8 +57,7 @@ function shouldCompileToWithPartials(string, hashOrArray, partials, expected, me ary = [hashOrArray]; } - var result = template.apply(this, ary); - equal(result, expected, "'" + expected + "' should === '" + result + "': " + message); + return template.apply(this, ary); } function shouldThrow(fn, exception, message) { @@ -685,6 +690,22 @@ test("each", function() { "each with array argument ignores the contents when empty"); }); +test("each with an object and @key", function() { + var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!"; + var hash = {goodbyes: {"#1": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"}; + + // Object property iteration order is undefined according to ECMA spec, + // so we need to check both possible orders + // @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop + var actual = compileWithPartials(string, hash); + var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!"; + var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!"; + + ok(actual === expected1 || actual === expected2, "each with object argument iterates over the contents when not empty"); + shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", + "each with object argument ignores the contents when empty"); +}); + test("each with @index", function() { var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!"; var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; From a927a9b0adc39660f0794b9b210c9db2f7ddecd9 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 15 Oct 2012 13:51:53 -0400 Subject: [PATCH 18/20] Add block comment syntax: {{!-- can contain {{handlebars expressions}} --}} --- spec/tokenizer_spec.rb | 12 ++++++++++++ src/handlebars.l | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index 23480de1..7a771ba2 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -158,6 +158,18 @@ describe "Tokenizer" do result[1].should be_token("COMMENT", " this is a comment ") end + it "tokenizes a block comment as 'COMMENT'" do + result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}") + result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE)) + result[1].should be_token("COMMENT", " this is a {{comment}} ") + end + + it "tokenizes a block comment with whitespace as 'COMMENT'" do + result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}") + result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE)) + result[1].should be_token("COMMENT", " this is a\n{{comment}}\n") + end + it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do result = tokenize("{{#foo}}content{{/foo}}") result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE)) diff --git a/src/handlebars.l b/src/handlebars.l index 68440bbb..87dce261 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -1,5 +1,5 @@ -%x mu emu +%x mu emu com %% @@ -17,6 +17,8 @@ return 'CONTENT'; } +[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; } + "{{>" { return 'OPEN_PARTIAL'; } "{{#" { return 'OPEN_BLOCK'; } "{{/" { return 'OPEN_ENDBLOCK'; } @@ -24,6 +26,7 @@ "{{"\s*"else" { return 'OPEN_INVERSE'; } "{{{" { return 'OPEN_UNESCAPED'; } "{{&" { return 'OPEN_UNESCAPED'; } +"{{!--" { this.popState(); this.begin('com'); } "{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; } "{{" { return 'OPEN'; } From 39832c06338c3412bae22597acfc98002d4519bf Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Fri, 2 Nov 2012 10:39:49 -0700 Subject: [PATCH 19/20] Fix handling of Errors in Chrome --- lib/handlebars/utils.js | 10 ++++++---- spec/qunit_spec.js | 24 +++++++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index b53c9ef4..3bc7e9b5 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -1,14 +1,16 @@ var Handlebars = require("./base"); // BEGIN(BROWSER) + +var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + Handlebars.Exception = function(message) { var tmp = Error.prototype.constructor.apply(this, arguments); - for (var p in tmp) { - if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; } + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; } - - this.message = tmp.message; }; Handlebars.Exception.prototype = new Error(); diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index 293c4131..5c942539 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -61,13 +61,27 @@ function compileWithPartials(string, hashOrArray, partials) { } function shouldThrow(fn, exception, message) { - var caught = false; + var caught = false, + exType, exMessage; + + if (exception instanceof Array) { + exType = exception[0]; + exMessage = exception[1]; + } else if (typeof exception === 'string') { + exType = Error; + exMessage = exception; + } else { + exType = exception; + } + try { fn(); } catch (e) { - if (e instanceof exception) { - caught = true; + if (e instanceof exType) { + if (!exMessage || e.message === exMessage) { + caught = true; + } } } @@ -480,14 +494,14 @@ test("rendering undefined partial throws an exception", function() { shouldThrow(function() { var template = CompilerContext.compile("{{> whatever}}"); template(); - }, Handlebars.Exception, "Should throw exception"); + }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception"); }); test("rendering template partial in vm mode throws an exception", function() { shouldThrow(function() { var template = CompilerContext.compile("{{> whatever}}"); template(); - }, Handlebars.Exception, "Should throw exception"); + }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception"); }); test("rendering function partial in vm mode", function() { From c3f92c402e2ce1c5d31be9c5e67b2abab4a5f81d Mon Sep 17 00:00:00 2001 From: cpojer Date: Thu, 22 Nov 2012 12:07:58 +0100 Subject: [PATCH 20/20] Add CommonJS export option for template compilation. --- bin/handlebars | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/handlebars b/bin/handlebars index 7a2fc1ca..dd6e0711 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -12,11 +12,17 @@ var optimist = require('optimist') 'description': 'Exports amd style (require.js)', 'alias': 'amd' }, + 'c': { + 'type': 'string', + 'description': 'Exports CommonJS style, path to Handlebars module', + 'alias': 'commonjs', + 'default': null + }, 'h': { 'type': 'string', 'description': 'Path to handlebar.js (only valid for amd-style)', 'alias': 'handlebarPath', - 'default': '' + 'default': '' }, 'k': { 'type': 'string', @@ -91,6 +97,8 @@ var output = []; if (!argv.simple) { if (argv.amd) { output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n'); + } else if (argv.commonjs) { + output.push('var Handlebars = require("' + argv.commonjs + '");'); } else { output.push('(function() {\n'); } @@ -139,7 +147,7 @@ argv._.forEach(function(template) { if (!argv.simple) { if (argv.amd) { output.push('});'); - } else { + } else if (!argv.commonjs) { output.push('})();'); } }