From a1df862f0633b26ab2517949d5333412915c9a6a Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Wed, 1 Jan 2014 20:21:27 -0800 Subject: [PATCH 01/53] Add koa-hbs to "handlebars in the wild" list --- README.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.markdown b/README.markdown index 0297c3589..beefd41ef 100644 --- a/README.markdown +++ b/README.markdown @@ -371,6 +371,8 @@ Handlebars in the Wild with 100+ handlebars helpers. * [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js, from Don Park. +* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based + renderer for Handlebars.js. * [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would like to try out Handlebars.js in their browser. * [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use From 70b27bcef6648e563c8252ea3b68af50dc1afb9c Mon Sep 17 00:00:00 2001 From: Brett Bim Date: Thu, 2 Jan 2014 11:36:01 -0600 Subject: [PATCH 02/53] Fix for issue #673 - Parser task did not execute correctly on windows environments because of forward/backward slash issues --- tasks/parser.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tasks/parser.js b/tasks/parser.js index f6a72c617..47533cedf 100644 --- a/tasks/parser.js +++ b/tasks/parser.js @@ -4,7 +4,13 @@ module.exports = function(grunt) { grunt.registerTask('parser', 'Generate jison parser.', function() { var done = this.async(); - var child = childProcess.spawn('./node_modules/.bin/jison', ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'}); + var cmd = './node_modules/.bin/jison'; + + if(process.platform === 'win32'){ + cmd = 'node_modules\\.bin\\jison.cmd'; + } + + var child = childProcess.spawn(cmd, ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], {stdio: 'inherit'}); child.on('exit', function(code) { if (code != 0) { grunt.fatal('Jison failure: ' + code); From 6d996ef2706c6f0f9fd60e18ea90b5565096c568 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 4 Jan 2014 09:14:22 -0600 Subject: [PATCH 03/53] Simplify ambiguous code Remove if conditional in favor of boolean failover. --- lib/handlebars/compiler/compiler.js | 1 + lib/handlebars/compiler/javascript-compiler.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 461f98b65..8389f8482 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -364,6 +364,7 @@ Compiler.prototype = { var options = this.options; // if ambiguous, we can possibly resolve the ambiguity now + // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc. if (isEligible && !isHelper) { var name = sexpr.id.parts[0]; diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 75390685f..cdf16935e 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -220,7 +220,7 @@ JavaScriptCompiler.prototype = { // On stack, after: return value of blockHelperMissing // // The purpose of this opcode is to take a block of the form - // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and + // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and // replace it on the stack with the result of properly // invoking blockHelperMissing. blockValue: function() { @@ -244,8 +244,11 @@ JavaScriptCompiler.prototype = { ambiguousBlockValue: function() { this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; + // We're being a bit cheeky and reusing the options value from the prior exec var params = ["depth0"]; - this.setupParams(0, params); + this.setupParams(0, params, true); + + this.flushInline(); var current = this.topStack(); params.splice(1, 0, current); @@ -555,15 +558,12 @@ JavaScriptCompiler.prototype = { var helper = this.setupHelper(0, name, helperCall); var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); - var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); - var nextStack = this.nextStack(); - if (helper.paramsInit) { - this.pushSource(helper.paramsInit); - } - this.pushSource('if (helper = ' + helperName + ') { ' + nextStack + ' = helper.call(' + helper.callParams + '); }'); - this.pushSource('else { helper = ' + nonHelper + '; ' + nextStack + ' = typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper; }'); + this.push( + '((helper = ' + helperName + ' || ' + nonHelper + + (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),' + + '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))'); }, // [invokePartial] From 3d9685c6a2a6bd40bea9946454312e8f77e4cca2 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Sun, 5 Jan 2014 12:14:24 -0500 Subject: [PATCH 04/53] Remove redundant conditions. --- lib/handlebars/compiler/javascript-compiler.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index cdf16935e..8cbb9e4f2 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -930,10 +930,7 @@ for(var i=0, l=reservedWords.length; i Date: Mon, 6 Jan 2014 02:40:43 -0600 Subject: [PATCH 05/53] Fix DATA value in stringParams mode Fixes #699 --- lib/handlebars/compiler/ast.js | 1 + spec/string-params.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 002fd3be6..dda682e7e 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -189,6 +189,7 @@ var AST = { LocationInfo.call(this, locInfo); this.type = "DATA"; this.id = id; + this.stringModeValue = id.stringModeValue; }, StringNode: function(string, locInfo) { diff --git a/spec/string-params.js b/spec/string-params.js index 920b85592..1cebc6f08 100644 --- a/spec/string-params.js +++ b/spec/string-params.js @@ -158,4 +158,19 @@ describe('string params mode', function() { var result = template({}, {helpers: helpers}); equals(result, "WITH"); }); + + it('should handle DATA', function() { + var template = CompilerContext.compile('{{foo @bar}}', { stringParams: true }); + + var helpers = { + foo: function(bar, options) { + equal(bar, 'bar'); + equal(options.types[0], 'DATA'); + return 'Foo!'; + } + }; + + var result = template({}, { helpers: helpers }); + equal(result, 'Foo!'); + }); }); From 7197289364cee84a902a2c5d551f0e69637182d5 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 6 Jan 2014 02:42:08 -0600 Subject: [PATCH 06/53] Update release instructions --- README.markdown | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index beefd41ef..63edee879 100644 --- a/README.markdown +++ b/README.markdown @@ -437,8 +437,7 @@ Handlebars utilizes the [release yeoman generator][generator-release] to perform A full release may be completed with the following: ``` -yo release:notes patch -yo release:release patch +yo release npm publish yo release:publish cdnjs handlebars.js dist/cdnjs/ yo release:publish components handlebars.js dist/components/ From 607748af3bd3135fc3113bdbfdd285e5d9edb97b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 6 Jan 2014 02:45:02 -0600 Subject: [PATCH 07/53] Add better logging of test compile errors --- spec/amd.html | 11 ++++++++--- spec/env/browser.js | 7 ++++++- spec/env/node.js | 7 ++++++- spec/env/runtime.js | 7 ++++++- spec/index.html | 11 ++++++++--- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/spec/amd.html b/spec/amd.html index 544b57611..a095650f4 100644 --- a/spec/amd.html +++ b/spec/amd.html @@ -45,9 +45,14 @@ }; function safeEval(templateSpec) { - var ret; - eval('ret = ' + templateSpec); - return ret; + try { + var ret; + eval('ret = ' + templateSpec); + return ret; + } catch (err) { + console.error(templateSpec); + throw err; + } } From daeecf825cda40d5903095cb86e6b2872870c472 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 4 Jan 2014 17:09:00 -0600 Subject: [PATCH 08/53] Track stringParams mode in local state vars --- lib/handlebars/compiler/compiler.js | 5 +++-- lib/handlebars/compiler/javascript-compiler.js | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 8389f8482..32d20487a 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -73,6 +73,7 @@ Compiler.prototype = { this.children = []; this.depths = {list: []}; this.options = options; + this.stringParams = options.stringParams; // These changes will propagate to the other compiler components var knownHelpers = this.options.knownHelpers; @@ -193,7 +194,7 @@ Compiler.prototype = { pair = pairs[i]; val = pair[1]; - if (this.options.stringParams) { + if (this.stringParams) { if(val.depth) { this.addDepth(val.depth); } @@ -386,7 +387,7 @@ Compiler.prototype = { while(i--) { param = params[i]; - if(this.options.stringParams) { + if(this.stringParams) { if(param.depth) { this.addDepth(param.depth); } diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 8cbb9e4f2..213672497 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -61,6 +61,7 @@ JavaScriptCompiler.prototype = { compile: function(environment, options, context, asObject) { this.environment = environment; this.options = options || {}; + this.stringParams = this.options.stringParams; log('debug', this.environment.disassemble() + "\n\n"); @@ -418,7 +419,7 @@ JavaScriptCompiler.prototype = { emptyHash: function() { this.pushStackLiteral('{}'); - if (this.options.stringParams) { + if (this.stringParams) { this.push('{}'); // hashContexts this.push('{}'); // hashTypes } @@ -433,7 +434,7 @@ JavaScriptCompiler.prototype = { var hash = this.hash; this.hash = this.hashes.pop(); - if (this.options.stringParams) { + if (this.stringParams) { this.push('{' + hash.contexts.join(',') + '}'); this.push('{' + hash.types.join(',') + '}'); } @@ -596,7 +597,7 @@ JavaScriptCompiler.prototype = { context, type; - if (this.options.stringParams) { + if (this.stringParams) { type = this.popStack(); context = this.popStack(); } @@ -842,7 +843,7 @@ JavaScriptCompiler.prototype = { options.push("hash:" + this.popStack()); - if (this.options.stringParams) { + if (this.stringParams) { options.push("hashTypes:" + this.popStack()); options.push("hashContexts:" + this.popStack()); } @@ -871,13 +872,13 @@ JavaScriptCompiler.prototype = { param = this.popStack(); params.push(param); - if(this.options.stringParams) { + if(this.stringParams) { types.push(this.popStack()); contexts.push(this.popStack()); } } - if (this.options.stringParams) { + if (this.stringParams) { options.push("contexts:[" + contexts.join(",") + "]"); options.push("types:[" + types.join(",") + "]"); } From c73aceb3d3415e9847fa0680f42bf80d5a652bac Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 6 Jan 2014 14:41:23 -0600 Subject: [PATCH 09/53] Refactor out pushParams method Simplifies hash and param push logic --- lib/handlebars/compiler/compiler.js | 53 +++++++++++------------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 32d20487a..b428c5fc5 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -186,30 +186,14 @@ Compiler.prototype = { }, hash: function(hash) { - var pairs = hash.pairs, pair, val; + var pairs = hash.pairs, pair; this.opcode('pushHash'); for(var i=0, l=pairs.length; i Date: Sat, 4 Jan 2014 11:05:57 -0600 Subject: [PATCH 10/53] Include name option for all helper calls All helper calls will have access to `options.name` which is the first id value of the mustache operation. As part of this the helperMissing call has been simplified to remove the indexed name in order to optimize the call. This is a breaking change. Fixes #634 --- lib/handlebars/base.js | 8 ++-- lib/handlebars/compiler/compiler.js | 2 +- .../compiler/javascript-compiler.js | 26 +++++------ spec/helpers.js | 43 +++++++++++++++++-- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 00c8bf1ff..059b1c3fb 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -49,11 +49,13 @@ HandlebarsEnvironment.prototype = { }; function registerDefaultHelpers(instance) { - instance.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { + instance.registerHelper('helperMissing', function(/* [args, ]options */) { + if(arguments.length === 1) { + // A missing field in a {{foo}} constuct. return undefined; } else { - throw new Exception("Missing helper: '" + arg + "'"); + // Someone is actually trying to call something, blow up. + throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'"); } }); diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index b428c5fc5..3c4a9b790 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -170,7 +170,7 @@ Compiler.prototype = { this.opcode('pushProgram', program); this.opcode('pushProgram', inverse); this.opcode('emptyHash'); - this.opcode('blockValue'); + this.opcode('blockValue', sexpr.id.original); } else { this.ambiguousSexpr(sexpr, program, inverse); diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 213672497..809322fcf 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -224,11 +224,11 @@ JavaScriptCompiler.prototype = { // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and // replace it on the stack with the result of properly // invoking blockHelperMissing. - blockValue: function() { + blockValue: function(name) { this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; var params = ["depth0"]; - this.setupParams(0, params); + this.setupParams(name, 0, params); this.replaceStack(function(current) { params.splice(1, 0, current); @@ -247,7 +247,7 @@ JavaScriptCompiler.prototype = { // We're being a bit cheeky and reusing the options value from the prior exec var params = ["depth0"]; - this.setupParams(0, params, true); + this.setupParams('', 0, params, true); this.flushInline(); @@ -504,20 +504,15 @@ JavaScriptCompiler.prototype = { this.context.aliases.helperMissing = 'helpers.helperMissing'; this.useRegister('helper'); - var helper = this.lastHelper = this.setupHelper(paramSize, name, true); + var helper = this.lastHelper = this.setupHelper(paramSize, name); var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); - var lookup = 'helper = ' + helper.name + ' || ' + nonHelper; + var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing'; if (helper.paramsInit) { lookup += ',' + helper.paramsInit; } - this.push( - '(' - + lookup - + ',helper ' - + '? helper.call(' + helper.callParams + ') ' - + ': helperMissing.call(' + helper.helperMissingParams + '))'); + this.push('(' + lookup + ',helper.call(' + helper.callParams + '))'); // Always flush subexpressions. This is both to prevent the compounding size issue that // occurs when the code has to be duplicated for inlining and also to prevent errors @@ -826,7 +821,7 @@ JavaScriptCompiler.prototype = { setupHelper: function(paramSize, name, missingParams) { var params = [], - paramsInit = this.setupParams(paramSize, params, missingParams); + paramsInit = this.setupParams(name, paramSize, params, missingParams); var foundHelper = this.nameLookup('helpers', name, 'helper'); return { @@ -838,9 +833,10 @@ JavaScriptCompiler.prototype = { }; }, - setupOptions: function(paramSize, params) { + setupOptions: function(helper, paramSize, params) { var options = [], contexts = [], types = [], param, inverse, program; + options.push("name:" + this.quotedString(helper)); options.push("hash:" + this.popStack()); if (this.stringParams) { @@ -892,8 +888,8 @@ JavaScriptCompiler.prototype = { // the params and contexts arguments are passed in arrays // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = '{' + this.setupOptions(paramSize, params).join(',') + '}'; + setupParams: function(helperName, paramSize, params, useRegister) { + var options = '{' + this.setupOptions(helperName, paramSize, params).join(',') + '}'; if (useRegister) { this.useRegister('options'); diff --git a/spec/helpers.js b/spec/helpers.js index b0eb91e65..180fc9809 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -362,9 +362,9 @@ describe('helpers', function() { var context = { hello: "Hello", world: "world" }; var helpers = { - helperMissing: function(helper, context) { - if(helper === "link_to") { - return new Handlebars.SafeString("" + context + ""); + helperMissing: function(mesg, options) { + if(options.name === "link_to") { + return new Handlebars.SafeString("" + mesg + ""); } } }; @@ -436,6 +436,43 @@ describe('helpers', function() { }); }); + describe('name field', function() { + var context = {}; + var helpers = { + blockHelperMissing: function() { + return 'missing: ' + arguments[arguments.length-1].name; + }, + helper: function() { + return 'ran: ' + arguments[arguments.length-1].name; + } + }; + + it('should include in ambiguous mustache calls', function() { + shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper'); + }); + it('should include in helper mustache calls', function() { + shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper'); + }); + it('should include in ambiguous block calls', function() { + shouldCompileTo('{{#helper}}{{/helper}}', [context, helpers], 'ran: helper'); + }); + it('should include in simple block calls', function() { + shouldCompileTo('{{#./helper}}{{/./helper}}', [context, helpers], 'missing: ./helper'); + }); + it('should include in helper block calls', function() { + shouldCompileTo('{{#helper 1}}{{/helper}}', [context, helpers], 'ran: helper'); + }); + it('should include in known helper calls', function() { + var template = CompilerContext.compile("{{helper}}", {knownHelpers: {'helper': true}, knownHelpersOnly: true}); + + equal(template({}, {helpers: helpers}), 'ran: helper'); + }); + + it('should include full id', function() { + shouldCompileTo('{{#foo.helper}}{{/foo.helper}}', [{foo: {}}, helpers], 'missing: foo.helper'); + }); + }); + describe('name conflicts', function() { it("helpers take precedence over same-named context properties", function() { var template = CompilerContext.compile("{{goodbye}} {{cruel world}}"); From d385e2cab64d169f912348f4f4281d64ae6b7b2f Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 4 Jan 2014 17:02:37 -0600 Subject: [PATCH 11/53] Cleanup now unused code in jscompiler --- lib/handlebars/compiler/javascript-compiler.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 809322fcf..d11275d1d 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -504,7 +504,7 @@ JavaScriptCompiler.prototype = { this.context.aliases.helperMissing = 'helpers.helperMissing'; this.useRegister('helper'); - var helper = this.lastHelper = this.setupHelper(paramSize, name); + var helper = this.setupHelper(paramSize, name); var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing'; @@ -819,17 +819,16 @@ JavaScriptCompiler.prototype = { .replace(/\u2029/g, '\\u2029') + '"'; }, - setupHelper: function(paramSize, name, missingParams) { + setupHelper: function(paramSize, name, blockHelper) { var params = [], - paramsInit = this.setupParams(name, paramSize, params, missingParams); + paramsInit = this.setupParams(name, paramSize, params, blockHelper); var foundHelper = this.nameLookup('helpers', name, 'helper'); return { params: params, paramsInit: paramsInit, name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + callParams: ["depth0"].concat(params).join(", ") }; }, From 76b47e339311516e84afdc80f170dec02aa91fda Mon Sep 17 00:00:00 2001 From: Nathaniel Bibler Date: Wed, 8 Jan 2014 22:05:16 -0500 Subject: [PATCH 12/53] Add MIT license identification to the Ruby gemspec --- components/handlebars-source.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/components/handlebars-source.gemspec b/components/handlebars-source.gemspec index 4313e9581..9123fddcd 100644 --- a/components/handlebars-source.gemspec +++ b/components/handlebars-source.gemspec @@ -12,6 +12,7 @@ Gem::Specification.new do |gem| gem.summary = %q{Handlebars.js source code wrapper} gem.homepage = "https://github.com/wycats/handlebars.js/" gem.version = package["version"] + gem.license = "MIT" gem.files = [ 'handlebars.js', From 91aa96da7ddae172ee5d4629e952408989e3d348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Scott=20Gonz=C3=A1lez?= Date: Tue, 14 Jan 2014 15:35:27 -0500 Subject: [PATCH 13/53] README: Use with helper instead of relying on blockHelperMissing --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 63edee879..22dc474e1 100644 --- a/README.markdown +++ b/README.markdown @@ -132,7 +132,7 @@ into the person object you could still display the company's name with an expression like `{{../company.name}}`, so: ``` -{{#person}}{{name}} - {{../company.name}}{{/person}} +{{#with person}}{{name}} - {{../company.name}}{{/person}} ``` would render: From fe9e93fc4dab4ba41e2aa89cf2771ebffe551f5e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 15 Jan 2014 04:47:11 -0600 Subject: [PATCH 14/53] Attempt manual install of git in travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 902b7412c..daccd0b5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ env: matrix: include: - node_js: "0.10" + before_install: + - sudo apt-get install git env: - PUBLISH=true - secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw= From f650b0d69ec70f82fc58022a530b95b0e4371f1d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 15 Jan 2014 09:41:53 -0600 Subject: [PATCH 15/53] Expose the initial context via @root --- lib/handlebars/compiler/javascript-compiler.js | 2 +- lib/handlebars/runtime.js | 9 ++++++++- spec/data.js | 15 +++++++++++++++ spec/expected/empty.amd.js | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 213672497..d6baf13c5 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -121,7 +121,7 @@ JavaScriptCompiler.prototype = { var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);"; if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; } - if (this.options.data) { copies = copies + " data = data || {};"; } + if (this.options.data) { copies = copies + " data = this.initData(depth0, data);"; } out.push(copies); } else { out.push(''); diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 5ae8d8a78..d31e5f4fe 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -1,6 +1,6 @@ module Utils from "./utils"; import Exception from "./exception"; -import { COMPILER_REVISION, REVISION_CHANGES } from "./base"; +import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from "./base"; export function checkRevision(compilerInfo) { var compilerRevision = compilerInfo && compilerInfo[0] || 1, @@ -56,6 +56,13 @@ export function template(templateSpec, env) { } return programWrapper; }, + initData: function(context, data) { + data = data ? createFrame(data) : {}; + if (!('root' in data)) { + data.root = context; + } + return data; + }, merge: function(param, common) { var ret = param || common; diff --git a/spec/data.js b/spec/data.js index d84eae729..eab80ef36 100644 --- a/spec/data.js +++ b/spec/data.js @@ -236,4 +236,19 @@ describe('data', function() { equals("sad world?", result, "Overriden data output by helper"); }); + describe('@root', function() { + it('the root context can be looked up via @root', function() { + var template = CompilerContext.compile('{{@root.foo}}'); + var result = template({foo: 'hello'}, { data: {} }); + equals('hello', result); + + result = template({foo: 'hello'}, {}); + equals('hello', result); + }); + it('passed root values take priority', function() { + var template = CompilerContext.compile('{{@root.foo}}'); + var result = template({}, { data: {root: {foo: 'hello'} } }); + equals('hello', result); + }); + }); }); diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js index 657970cd0..ca80b8850 100644 --- a/spec/expected/empty.amd.js +++ b/spec/expected/empty.amd.js @@ -2,7 +2,7 @@ define(['handlebars.runtime'], function(Handlebars) { Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; return templates['empty'] = template(function (Handlebars,depth0,helpers,partials,data) { this.compilerInfo = [4,'>= 1.0.0']; -helpers = this.merge(helpers, Handlebars.helpers); data = data || {}; +helpers = this.merge(helpers, Handlebars.helpers); data = this.initData(depth0, data); var buffer = ""; From 13633e7896b9c104667102b3b5715fc1a6b0d613 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Thu, 9 Jan 2014 12:45:26 +1000 Subject: [PATCH 16/53] Improve usefulness of extend util, properly use namespace property, update setup options to use a hash helper. --- lib/handlebars/base.js | 8 ++-- .../compiler/javascript-compiler.js | 45 ++++++++++++------- lib/handlebars/runtime.js | 5 +-- lib/handlebars/utils.js | 12 +++-- 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 059b1c3fb..ff9736ca6 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -102,8 +102,8 @@ function registerDefaultHelpers(instance) { } else { for(var key in context) { if(context.hasOwnProperty(key)) { - if(data) { - data.key = key; + if(data) { + data.key = key; data.index = i; data.first = (i === 0); } @@ -174,7 +174,5 @@ export var logger = { export function log(level, obj) { logger.log(level, obj); } export var createFrame = function(object) { - var obj = {}; - Utils.extend(obj, object); - return obj; + return Utils.extend({}, object); }; diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index abdc8053b..7898bd1f3 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -21,8 +21,7 @@ JavaScriptCompiler.prototype = { ret = parent + "[" + name + "]"; } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { ret = parent + "." + name; - } - else { + } else { ret = parent + "['" + name + "']"; } @@ -168,7 +167,7 @@ JavaScriptCompiler.prototype = { this.pushSource("return buffer;"); } - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + var params = this.isChild ? ["depth0", "data"] : [this.namespace, "depth0", "helpers", "partials", "data"]; for(var i=0, l=this.environment.depths.list.length; i Date: Fri, 17 Jan 2014 17:18:11 -0600 Subject: [PATCH 17/53] Remove apt git install --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index daccd0b5e..902b7412c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,6 @@ env: matrix: include: - node_js: "0.10" - before_install: - - sudo apt-get install git env: - PUBLISH=true - secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw= From 1b8c0e3c76d8595b49ad30c2a50a1bab47b5ec70 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 17:18:26 -0600 Subject: [PATCH 18/53] Use git describe to lookup tag --- tasks/util/git.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tasks/util/git.js b/tasks/util/git.js index dc57c91b6..a6c9ec1dc 100644 --- a/tasks/util/git.js +++ b/tasks/util/git.js @@ -80,20 +80,27 @@ module.exports = { childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err, stdout, stderr) { if (err) { throw new Error('git.tag: ' + err.message); - throw err; } callback(); }); }, tagName: function(callback) { - childProcess.exec('git tag -l --points-at HEAD', {}, function(err, stdout) { + childProcess.exec('git describe --tags', {}, function(err, stdout) { if (err) { throw new Error('git.tagName: ' + err.message); } - var tags = stdout.trim().split(/\n/), - versionTags = tags.filter(function(tag) { return /^v/.test(tag); }); + var tags = stdout.trim().split(/\n/); + tags = tags.filter(function(info) { + info = info.split('-'); + return info.length == 1; + }); + + var versionTags = tags.filter(function(info) { + return /^v/.test(info[0]); + }); + callback(undefined, versionTags[0] || tags[0]); }); } From f90981adf608f98551d527c3109771d8babbba9f Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 16:31:59 -0600 Subject: [PATCH 19/53] Add partial hash parser support --- lib/handlebars/compiler/ast.js | 3 ++- lib/handlebars/compiler/printer.js | 7 ++++++- spec/ast.js | 2 +- spec/parser.js | 8 ++++++++ src/handlebars.yy | 3 ++- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index dda682e7e..5538f4057 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -97,11 +97,12 @@ var AST = { // pass or at runtime. }, - PartialNode: function(partialName, context, strip, locInfo) { + PartialNode: function(partialName, context, hash, strip, locInfo) { LocationInfo.call(this, locInfo); this.type = "partial"; this.partialName = partialName; this.context = context; + this.hash = hash; this.strip = strip; }, diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index ad55c7d4a..b7b760fac 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -82,7 +82,12 @@ PrintVisitor.prototype.mustache = function(mustache) { PrintVisitor.prototype.partial = function(partial) { var content = this.accept(partial.partialName); - if(partial.context) { content = content + " " + this.accept(partial.context); } + if(partial.context) { + content += " " + this.accept(partial.context); + } + if (partial.hash) { + content += " " + this.accept(partial.hash); + } return this.pad("{{> " + content + " }}"); }; diff --git a/spec/ast.js b/spec/ast.js index 46f0131f7..430574e22 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -193,7 +193,7 @@ describe('ast', function() { describe("PartialNode", function(){ it('stores location info', function(){ - var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, LOCATION_INFO); + var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, {}, LOCATION_INFO); testLocationInfoStorage(pn); }); }); diff --git a/spec/parser.js b/spec/parser.js index 70c16359e..0f6ed312f 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -84,6 +84,14 @@ describe('parser', function() { equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n"); }); + it('parses a partial with hash', function() { + equals(ast_for("{{> foo bar=bat}}"), "{{> PARTIAL:foo HASH{bar=ID:bat} }}\n"); + }); + + it('parses a partial with context and hash', function() { + equals(ast_for("{{> foo bar bat=baz}}"), "{{> PARTIAL:foo ID:bar HASH{bat=ID:baz} }}\n"); + }); + it('parses a partial with a complex name', function() { equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n"); }); diff --git a/src/handlebars.yy b/src/handlebars.yy index 7bff5125a..bac1cc9cb 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -63,7 +63,8 @@ mustache ; partial - : OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4), @$) + : OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, stripFlags($1, $5), @$) + | OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, stripFlags($1, $4), @$) ; simpleInverse From 45ae86a248f3da7f24ee68da12454a2444ec398f Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 16:49:44 -0600 Subject: [PATCH 20/53] Implement partial hash evaluation --- lib/handlebars/compiler/compiler.js | 10 ++++++++-- lib/handlebars/compiler/javascript-compiler.js | 2 +- lib/handlebars/runtime.js | 8 ++++++-- spec/partials.js | 8 ++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 3c4a9b790..b92289a7f 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -203,8 +203,14 @@ Compiler.prototype = { var partialName = partial.partialName; this.usePartial = true; - if(partial.context) { - this.ID(partial.context); + if (partial.hash) { + this.accept(partial.hash); + } else { + this.opcode('push', 'undefined'); + } + + if (partial.context) { + this.accept(partial.context); } else { this.opcode('push', 'depth0'); } diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 7898bd1f3..8ca0116b4 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -569,7 +569,7 @@ JavaScriptCompiler.prototype = { // This operation pops off a context, invokes a partial with that context, // and pushes the result of the invocation back. invokePartial: function(name) { - var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"]; + var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"]; if (this.options.data) { params.push("data"); diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 699499c54..5df681f82 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -29,8 +29,12 @@ export function template(templateSpec, env) { // Note: Using env.VM references rather than local var references throughout this section to allow // for external users to override these as psuedo-supported APIs. - var invokePartialWrapper = function(partial, name, context, helpers, partials, data) { - var result = env.VM.invokePartial.apply(this, arguments); + var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) { + if (hash) { + context = Utils.extend({}, context, hash); + } + + var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data); if (result != null) { return result; } if (env.compile) { diff --git a/spec/partials.js b/spec/partials.js index bea72f5b7..68c0441f2 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -23,6 +23,14 @@ describe('partials', function() { shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Empty"); }); + it("partials with parameters", function() { + var string = "Dudes: {{#dudes}}{{> dude otherDude=name}}{{/dudes}}"; + var partial = "{{otherDude}} ({{url}}) "; + var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + "Basic partials output based on current context."); + }); + it("partial in a partial", function() { var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}"; var dude = "{{name}} {{> url}} "; From e290ec24f131f89ddf2c6aeb707a4884d41c3c6d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 17:11:28 -0600 Subject: [PATCH 21/53] Test more concrete behavior in partial hash test --- spec/partials.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/partials.js b/spec/partials.js index 68c0441f2..732436af0 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -24,10 +24,10 @@ describe('partials', function() { }); it("partials with parameters", function() { - var string = "Dudes: {{#dudes}}{{> dude otherDude=name}}{{/dudes}}"; - var partial = "{{otherDude}} ({{url}}) "; - var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; - shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + var string = "Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}"; + var partial = "{{others.foo}}{{name}} ({{url}}) "; + var hash = {foo: 'bar', dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]}; + shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: barYehuda (http://yehuda) barAlan (http://alan) ", "Basic partials output based on current context."); }); From fe4880feaabb6107e39e9af6d4d0f68ff3736653 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 20:13:00 -0600 Subject: [PATCH 22/53] Allow implicit context iteration with each Fixes #671 --- lib/handlebars/base.js | 6 ++++++ spec/builtins.js | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index ff9736ca6..790228c1c 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -80,6 +80,12 @@ function registerDefaultHelpers(instance) { }); instance.registerHelper('each', function(context, options) { + // Allow for {{#each}} + if (!options) { + options = context; + context = this; + } + var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; diff --git a/spec/builtins.js b/spec/builtins.js index 766541a77..6d9fa0ba4 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -181,6 +181,11 @@ describe('builtin helpers', function() { equal(result, 'a!b!c!', 'should output data'); }); + it("each on implicit context", function() { + var string = "{{#each}}{{text}}! {{/each}}cruel world!"; + var hash = [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]; + shouldCompileTo(string, [hash], "goodbye! Goodbye! GOODBYE! cruel world!"); + }); }); it("#log", function() { From 051618c024333404746795304f5858f3ef56a215 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 20:16:10 -0600 Subject: [PATCH 23/53] Always process explicitly passed files in CLI Fixes #689 --- bin/handlebars | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/handlebars b/bin/handlebars index 247ddd540..d7c489b0d 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -139,7 +139,7 @@ if (!argv.simple) { output.push(argv.namespace); output.push(' || {};\n'); } -function processTemplate(template, root) { +function processTemplate(template, root, explicit) { var path = template, stat = fs.statSync(path); if (stat.isDirectory()) { @@ -150,7 +150,7 @@ function processTemplate(template, root) { processTemplate(path, root || template); } }); - } else if (extension.test(path)) { + } else if (explicit || extension.test(path)) { var data = fs.readFileSync(path, 'utf8'); if (argv.bom && data.indexOf('\uFEFF') === 0) { @@ -191,7 +191,7 @@ function processTemplate(template, root) { } argv._.forEach(function(template) { - processTemplate(template, argv.root); + processTemplate(template, argv.root, true); }); // Output the content From d4cfe90959c5a585e5d87e31038eb2f0432f87a5 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 20:42:02 -0600 Subject: [PATCH 24/53] Allow decimal number values Fixes #472 --- lib/handlebars/compiler/ast.js | 8 ++++---- lib/handlebars/compiler/compiler.js | 4 ++-- lib/handlebars/compiler/printer.js | 4 ++-- spec/ast.js | 4 ++-- spec/helpers.js | 11 +++++++++++ spec/parser.js | 8 ++++---- spec/tokenizer.js | 20 ++++++++++++++------ src/handlebars.l | 2 +- src/handlebars.yy | 4 ++-- 9 files changed, 42 insertions(+), 23 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 5538f4057..8fa649568 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -201,12 +201,12 @@ var AST = { this.stringModeValue = string; }, - IntegerNode: function(integer, locInfo) { + NumberNode: function(number, locInfo) { LocationInfo.call(this, locInfo); - this.type = "INTEGER"; + this.type = "NUMBER"; this.original = - this.integer = integer; - this.stringModeValue = Number(integer); + this.number = number; + this.stringModeValue = Number(number); }, BooleanNode: function(bool, locInfo) { diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index b92289a7f..83eca2408 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -321,8 +321,8 @@ Compiler.prototype = { this.opcode('pushString', string.string); }, - INTEGER: function(integer) { - this.opcode('pushLiteral', integer.integer); + NUMBER: function(number) { + this.opcode('pushLiteral', number.number); }, BOOLEAN: function(bool) { diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index b7b760fac..be66d73d3 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -108,8 +108,8 @@ PrintVisitor.prototype.STRING = function(string) { return '"' + string.string + '"'; }; -PrintVisitor.prototype.INTEGER = function(integer) { - return "INTEGER{" + integer.integer + "}"; +PrintVisitor.prototype.NUMBER = function(number) { + return "NUMBER{" + number.number + "}"; }; PrintVisitor.prototype.BOOLEAN = function(bool) { diff --git a/spec/ast.js b/spec/ast.js index 430574e22..17a982c41 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -150,10 +150,10 @@ describe('ast', function() { }); }); - describe("IntegerNode", function(){ + describe("NumberNode", function(){ it('stores location info', function(){ - var integer = new handlebarsEnv.AST.IntegerNode("6", LOCATION_INFO); + var integer = new handlebarsEnv.AST.NumberNode("6", LOCATION_INFO); testLocationInfoStorage(integer); }); }); diff --git a/spec/helpers.js b/spec/helpers.js index 180fc9809..904f56a7c 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -188,6 +188,17 @@ describe('helpers', function() { "found it! Goodbye cruel world!!"); }); + it("decimal number literals work", function() { + var string = 'Message: {{hello -1.2 1.2}}'; + var hash = {}; + var helpers = {hello: function(times, times2) { + if(typeof times !== 'number') { times = "NaN"; } + if(typeof times2 !== 'number') { times2 = "NaN"; } + return "Hello " + times + " " + times2 + " times"; + }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello -1.2 1.2 times", "template with a negative integer literal"); + }); + it("negative number literals work", function() { var string = 'Message: {{hello -12}}'; var hash = {}; diff --git a/spec/parser.js b/spec/parser.js index 0f6ed312f..5b6dc8a4e 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -41,8 +41,8 @@ describe('parser', function() { equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n'); }); - it('parses mustaches with INTEGER parameters', function() { - equals(ast_for("{{foo 1}}"), "{{ ID:foo [INTEGER{1}] }}\n"); + it('parses mustaches with NUMBER parameters', function() { + equals(ast_for("{{foo 1}}"), "{{ ID:foo [NUMBER{1}] }}\n"); }); it('parses mustaches with BOOLEAN parameters', function() { @@ -56,7 +56,7 @@ describe('parser', function() { it('parses mustaches with hash arguments', function() { equals(ast_for("{{foo bar=baz}}"), "{{ ID:foo [] HASH{bar=ID:baz} }}\n"); - equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=INTEGER{1}} }}\n"); + equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=NUMBER{1}} }}\n"); equals(ast_for("{{foo bar=true}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n"); equals(ast_for("{{foo bar=false}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n"); equals(ast_for("{{foo bar=@baz}}"), "{{ ID:foo [] HASH{bar=@ID:baz} }}\n"); @@ -67,7 +67,7 @@ describe('parser', function() { equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\n'); equals(ast_for("{{foo omg bar=baz bat=\"bam\"}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n'); - equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=INTEGER{1}} }}\n'); + equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=NUMBER{1}} }}\n'); equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n'); equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n'); }); diff --git a/spec/tokenizer.js b/spec/tokenizer.js index 841a5abc1..36a632b8a 100644 --- a/spec/tokenizer.js +++ b/spec/tokenizer.js @@ -295,12 +295,20 @@ describe('Tokenizer', function() { it('tokenizes numbers', function() { var result = tokenize('{{ foo 1 }}'); - shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']); - shouldBeToken(result[2], "INTEGER", "1"); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "1"); + + result = tokenize('{{ foo 1.1 }}'); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "1.1"); result = tokenize('{{ foo -1 }}'); - shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']); - shouldBeToken(result[2], "INTEGER", "-1"); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "-1"); + + result = tokenize('{{ foo -1.1 }}'); + shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']); + shouldBeToken(result[2], "NUMBER", "-1.1"); }); it('tokenizes booleans', function() { @@ -321,7 +329,7 @@ describe('Tokenizer', function() { shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']); result = tokenize("{{ foo bar baz=1 }}"); - shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']); + shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'NUMBER', 'CLOSE']); result = tokenize("{{ foo bar baz=true }}"); shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']); @@ -389,6 +397,6 @@ describe('Tokenizer', function() { it('tokenizes nested subexpressions: literals', function() { var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}"); - shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'INTEGER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']); + shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'NUMBER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']); }); }); diff --git a/src/handlebars.l b/src/handlebars.l index 996badb22..630840eb5 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -77,7 +77,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} "@" return 'DATA'; "true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; "false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; -\-?[0-9]+/{LITERAL_LOOKAHEAD} return 'INTEGER'; +\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER'; {ID} return 'ID'; diff --git a/src/handlebars.yy b/src/handlebars.yy index bac1cc9cb..40f68ce59 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -79,7 +79,7 @@ sexpr param : path -> $1 | STRING -> new yy.StringNode($1, @$) - | INTEGER -> new yy.IntegerNode($1, @$) + | NUMBER -> new yy.NumberNode($1, @$) | BOOLEAN -> new yy.BooleanNode($1, @$) | dataName -> $1 | OPEN_SEXPR sexpr CLOSE_SEXPR {$2.isHelper = true; $$ = $2;} @@ -96,7 +96,7 @@ hashSegment partialName : path -> new yy.PartialNameNode($1, @$) | STRING -> new yy.PartialNameNode(new yy.StringNode($1, @$), @$) - | INTEGER -> new yy.PartialNameNode(new yy.IntegerNode($1, @$)) + | NUMBER -> new yy.PartialNameNode(new yy.NumberNode($1, @$)) ; dataName From 33921beaa08649f251e9e8b77124cb8fd2c42b1c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 22:56:40 -0600 Subject: [PATCH 25/53] Fix missing parameters for pathed mustaches Fixes #658 --- lib/handlebars/compiler/ast.js | 10 +++---- lib/handlebars/compiler/compiler.js | 4 ++- .../compiler/javascript-compiler.js | 2 +- spec/basic.js | 28 +++++++++++++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 8fa649568..678988394 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -83,14 +83,14 @@ var AST = { var id = this.id = rawParams[0]; var params = this.params = rawParams.slice(1); - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; - // a mustache is definitely a helper if: // * it is an eligible helper, and // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); + this.isHelper = params.length || hash; + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + this.eligibleHelper = this.isHelper || id.isSimple; // if a mustache is an eligible helper but not a definite // helper, it is ambiguous, and will be resolved in a later diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 83eca2408..3f7aa3bff 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -265,13 +265,15 @@ Compiler.prototype = { helperSexpr: function(sexpr, program, inverse) { var params = this.setupFullMustacheParams(sexpr, program, inverse), - name = sexpr.id.parts[0]; + id = sexpr.id, + name = id.parts[0]; if (this.options.knownHelpers[name]) { this.opcode('invokeKnownHelper', params.length, name); } else if (this.options.knownHelpersOnly) { throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr); } else { + this.ID(id); this.opcode('invokeHelper', params.length, name, sexpr.isRoot); } }, diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 8ca0116b4..96a8a0eae 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -503,8 +503,8 @@ JavaScriptCompiler.prototype = { this.context.aliases.helperMissing = 'helpers.helperMissing'; this.useRegister('helper'); + var nonHelper = this.popStack(); var helper = this.setupHelper(paramSize, name); - var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing'; if (helper.paramsInit) { diff --git a/spec/basic.js b/spec/basic.js index 245022c61..8aa54b98c 100644 --- a/spec/basic.js +++ b/spec/basic.js @@ -97,6 +97,18 @@ describe("basic context", function() { frank: "Frank"}, "Frank", "functions are called with context arguments"); }); + it("pathed functions with context argument", function() { + shouldCompileTo("{{bar.awesome frank}}", + {bar: {awesome: function(context) { return context; }}, + frank: "Frank"}, + "Frank", "functions are called with context arguments"); + }); + it("depthed functions with context argument", function() { + shouldCompileTo("{{#with frank}}{{../awesome .}}{{/with}}", + {awesome: function(context) { return context; }, + frank: "Frank"}, + "Frank", "functions are called with context arguments"); + }); it("block functions with context argument", function() { shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}", @@ -104,11 +116,27 @@ describe("basic context", function() { "inner 1", "block functions are called with context and options"); }); + it("depthed block functions with context argument", function() { + shouldCompileTo("{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}", + {value: true, awesome: function(context, options) { return options.fn(context); }}, + "inner 1", "block functions are called with context and options"); + }); + it("block functions without context argument", function() { shouldCompileTo("{{#awesome}}inner{{/awesome}}", {awesome: function(options) { return options.fn(this); }}, "inner", "block functions are called with options"); }); + it("pathed block functions without context argument", function() { + shouldCompileTo("{{#foo.awesome}}inner{{/foo.awesome}}", + {foo: {awesome: function(options) { return this; }}}, + "inner", "block functions are called with options"); + }); + it("depthed block functions without context argument", function() { + shouldCompileTo("{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}", + {value: true, awesome: function(options) { return this; }}, + "inner", "block functions are called with options"); + }); it("paths with hyphens", function() { From a14daca1c6bff532146cd4bae66fe12c94c44117 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 23:11:13 -0600 Subject: [PATCH 26/53] Simplify sauce test environments --- Gruntfile.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 3468e4d38..ae4ff7b1f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -141,14 +141,11 @@ module.exports = function(grunt) { {browserName: 'firefox', version: '3.6'}, {browserName: 'safari', version: 7, platform: 'OS X 10.9'}, {browserName: 'safari', version: 6, platform: 'OS X 10.8'}, - {browserName: 'safari', version: 5}, {browserName: 'opera', version: 12}, {browserName: 'opera', version: 11}, {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'}, {browserName: 'internet explorer', version: 10, platform: 'Windows 8'}, {browserName: 'internet explorer', version: 9, platform: 'Windows 7'}, - {browserName: 'internet explorer', version: 8, platform: 'XP'}, - {browserName: 'internet explorer', version: 7, platform: 'XP'}, {browserName: 'internet explorer', version: 6, platform: 'XP'} ] } From 103e5f8409ad624227ff99b8e5fbbc276cadee7b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 23:11:35 -0600 Subject: [PATCH 27/53] Only lint 1 set of release content --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index ae4ff7b1f..29611c7f5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -10,7 +10,7 @@ module.exports = function(grunt) { jshintrc: '.jshintrc' }, files: [ - 'dist/**/!(*.min|parser).js' + 'dist/cjs/**/!(*.min|parser).js' ] }, From 9df919083d4e25512fb6dc6fe1a050e63ad12c80 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 16 Jan 2014 13:59:02 -0600 Subject: [PATCH 28/53] Create track ids test stub --- spec/track-ids.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 spec/track-ids.js diff --git a/spec/track-ids.js b/spec/track-ids.js new file mode 100644 index 000000000..9d9847ced --- /dev/null +++ b/spec/track-ids.js @@ -0,0 +1,6 @@ +describe('track ids', function() { + it('should include argument ids'); + it('should include hash ids'); + it('should note ../ references'); + it('should update the path when using built helpers'); +}); From ace2896ec876104f5bd220744124e7370fe6b9a2 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 19:14:36 -0600 Subject: [PATCH 29/53] Add trackIds compiler flag Allows helpers that care about where a particular field came from derive this data while maintaining backward compatibility with existing helpers. --- lib/handlebars/compiler/ast.js | 6 +- lib/handlebars/compiler/compiler.js | 4 + .../compiler/javascript-compiler.js | 39 ++++++- spec/track-ids.js | 110 +++++++++++++++++- 4 files changed, 151 insertions(+), 8 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 678988394..5a5f514db 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -149,7 +149,8 @@ var AST = { var original = "", dig = [], - depth = 0; + depth = 0, + depthString = ''; for(var i=0,l=parts.length; i Date: Fri, 17 Jan 2014 20:01:43 -0600 Subject: [PATCH 30/53] Add contextPath tracking in builtin helpers --- lib/handlebars/base.js | 34 +++++++++++++++++++++++-- lib/handlebars/utils.js | 4 +++ spec/track-ids.js | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 790228c1c..9dc49fc8e 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -70,12 +70,19 @@ function registerDefaultHelpers(instance) { return inverse(this); } else if (isArray(context)) { if(context.length > 0) { + options.ids = [options.name]; return instance.helpers.each(context, options); } else { return inverse(this); } } else { - return fn(context); + if (options.ids) { + var data = createFrame(options.data); + data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); + options = {data: data}; + } + + return fn(context, options); } }); @@ -89,6 +96,11 @@ function registerDefaultHelpers(instance) { var fn = options.fn, inverse = options.inverse; var i = 0, ret = "", data; + var contextPath; + if (options.ids) { + contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + if (isFunction(context)) { context = context.call(this); } if (options.data) { @@ -102,6 +114,10 @@ function registerDefaultHelpers(instance) { data.index = i; data.first = (i === 0); data.last = (i === (context.length-1)); + + if (contextPath) { + data.contextPath = contextPath + i; + } } ret = ret + fn(context[i], { data: data }); } @@ -112,6 +128,10 @@ function registerDefaultHelpers(instance) { data.key = key; data.index = i; data.first = (i === 0); + + if (contextPath) { + data.contextPath = contextPath + key; + } } ret = ret + fn(context[key], {data: data}); i++; @@ -147,7 +167,17 @@ function registerDefaultHelpers(instance) { instance.registerHelper('with', function(context, options) { if (isFunction(context)) { context = context.call(this); } - if (!Utils.isEmpty(context)) return options.fn(context); + var fn = options.fn; + + if (!Utils.isEmpty(context)) { + if (options.ids) { + var data = createFrame(options.data); + data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]); + options = {data:data}; + } + + return fn(context, options); + } }); instance.registerHelper('log', function(context, options) { diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index 63148e460..f2f1a5470 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -75,3 +75,7 @@ export function isEmpty(value) { return false; } } + +export function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; +} diff --git a/spec/track-ids.js b/spec/track-ids.js index 6bd97d5b3..938f98bb5 100644 --- a/spec/track-ids.js +++ b/spec/track-ids.js @@ -105,4 +105,59 @@ describe('track ids', function() { equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS 1'); }); + + describe('builtin helpers', function() { + var helpers = { + wycats: function(name, options) { + return name + ':' + options.data.contextPath + '\n'; + } + }; + + describe('#each', function() { + it('should track contextPath for arrays', function() { + var template = CompilerContext.compile('{{#each array}}{{wycats name}}{{/each}}', {trackIds: true}); + + equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:array.0\nbar:array.1\n'); + }); + it('should track contextPath for keys', function() { + var template = CompilerContext.compile('{{#each object}}{{wycats name}}{{/each}}', {trackIds: true}); + + equals(template({object: {foo: {name: 'foo'}, bar: {name: 'bar'}}}, {helpers: helpers}), 'foo:object.foo\nbar:object.bar\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}', {trackIds: true}); + + equals(template({array: [{name: 'foo'}, {name: 'bar'}]}, {helpers: helpers}), 'foo:.array..0\nbar:.array..1\n'); + }); + }); + describe('#with', function() { + it('should track contextPath', function() { + var template = CompilerContext.compile('{{#with field}}{{wycats name}}{{/with}}', {trackIds: true}); + + equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}', {trackIds: true}); + + equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n'); + }); + }); + describe('#blockHelperMissing', function() { + it('should track contextPath for arrays', function() { + var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true}); + + equals(template({field: [{name: 'foo'}]}, {helpers: helpers}), 'foo:field.0\n'); + }); + it('should track contextPath for keys', function() { + var template = CompilerContext.compile('{{#field}}{{wycats name}}{{/field}}', {trackIds: true}); + + equals(template({field: {name: 'foo'}}, {helpers: helpers}), 'foo:field\n'); + }); + it('should handle nesting', function() { + var template = CompilerContext.compile('{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}', {trackIds: true}); + + equals(template({bat: {field: {name: 'foo'}}}, {helpers: helpers}), 'foo:bat.field\n'); + }); + }); + }); }); From 5a0bcb932de840b32910ebe65cb851b764425e66 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 17 Jan 2014 23:24:35 -0600 Subject: [PATCH 31/53] Fix handler execution in nondata/nonid mode --- lib/handlebars/base.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 9dc49fc8e..2dc2fea6d 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -70,13 +70,16 @@ function registerDefaultHelpers(instance) { return inverse(this); } else if (isArray(context)) { if(context.length > 0) { - options.ids = [options.name]; + if (options.ids) { + options.ids = [options.name]; + } + return instance.helpers.each(context, options); } else { return inverse(this); } } else { - if (options.ids) { + if (options.data && options.ids) { var data = createFrame(options.data); data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); options = {data: data}; @@ -97,7 +100,7 @@ function registerDefaultHelpers(instance) { var i = 0, ret = "", data; var contextPath; - if (options.ids) { + if (options.data && options.ids) { contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; } @@ -170,7 +173,7 @@ function registerDefaultHelpers(instance) { var fn = options.fn; if (!Utils.isEmpty(context)) { - if (options.ids) { + if (options.data && options.ids) { var data = createFrame(options.data); data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]); options = {data:data}; From 311be67bdedd6c27472937b046dd131848259d79 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 18 Jan 2014 09:22:29 -0600 Subject: [PATCH 32/53] Add partial and helper unregister APIs Fixes #669 --- lib/handlebars/base.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 2dc2fea6d..7ebfc5567 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -38,6 +38,9 @@ HandlebarsEnvironment.prototype = { this.helpers[name] = fn; } }, + unregisterHelper: function(name) { + delete this.helpers[name]; + }, registerPartial: function(name, str) { if (toString.call(name) === objectType) { @@ -45,6 +48,9 @@ HandlebarsEnvironment.prototype = { } else { this.partials[name] = str; } + }, + unregisterPartial: function(name) { + delete this.partials[name]; } }; From cea57c05e28e0c2f5af2fab1c67792613f3728c9 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 18 Jan 2014 09:23:11 -0600 Subject: [PATCH 33/53] Optimize initData for root defined case --- lib/handlebars/runtime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 5df681f82..6014691ac 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -61,8 +61,8 @@ export function template(templateSpec, env) { return programWrapper; }, initData: function(context, data) { - data = data ? createFrame(data) : {}; - if (!('root' in data)) { + if (!data || !('root' in data)) { + data = data ? createFrame(data) : {}; data.root = context; } return data; From 4679b1d9314a022ad305bab9e6cfed66088428b9 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 18 Jan 2014 09:29:47 -0600 Subject: [PATCH 34/53] Include content on binary output test failure --- tasks/test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tasks/test.js b/tasks/test.js index cd5f00e80..b3f2056fd 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -10,8 +10,9 @@ module.exports = function(grunt) { throw err; } - if (stdout.toString() !== fs.readFileSync('./spec/expected/empty.amd.js').toString()) { - throw new Error('Expected binary output differed'); + var expected = fs.readFileSync('./spec/expected/empty.amd.js'); + if (stdout.toString() !== expected.toString()) { + throw new Error('Expected binary output differed:\n\n' + stdout + '\n\n' + expected); } done(); From 8f07bbabeabaed5d8100261b952c9de07fad7a8c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 19 Jan 2014 19:20:55 -0600 Subject: [PATCH 35/53] Add quotes to exception --- tasks/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/test.js b/tasks/test.js index b3f2056fd..664af60d8 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -12,7 +12,7 @@ module.exports = function(grunt) { var expected = fs.readFileSync('./spec/expected/empty.amd.js'); if (stdout.toString() !== expected.toString()) { - throw new Error('Expected binary output differed:\n\n' + stdout + '\n\n' + expected); + throw new Error('Expected binary output differed:\n\n"' + stdout + '"\n\n"' + expected + '"'); } done(); From 6ca06d41156a43d29790d101d6aa97ed922ca8ad Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 6 Feb 2014 22:50:12 -0800 Subject: [PATCH 36/53] Add depth benchmark cases --- bench/templates/depth-1.js | 6 ++++++ bench/templates/depth-2.js | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 bench/templates/depth-1.js create mode 100644 bench/templates/depth-2.js diff --git a/bench/templates/depth-1.js b/bench/templates/depth-1.js new file mode 100644 index 000000000..74809bca8 --- /dev/null +++ b/bench/templates/depth-1.js @@ -0,0 +1,6 @@ +module.exports = { + context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}], foo: 'bar' }, + handlebars: "{{#each names}}{{../foo}}{{/each}}", + mustache: "{{#names}}{{foo}}{{/names}}", + eco: "<% for item in @names: %><%= @foo %><% end %>" +}; diff --git a/bench/templates/depth-2.js b/bench/templates/depth-2.js new file mode 100644 index 000000000..1d38baa4e --- /dev/null +++ b/bench/templates/depth-2.js @@ -0,0 +1,6 @@ +module.exports = { + context: { names: [{bat: 'foo', name: ["Moe"]}, {bat: 'foo', name: ["Larry"]}, {bat: 'foo', name: ["Curly"]}, {bat: 'foo', name: ["Shemp"]}], foo: 'bar' }, + handlebars: "{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}", + mustache: "{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}", + eco: "<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>" +}; From b0e72985239504e7d70643bfd767cf14fd14920b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Thu, 6 Feb 2014 23:01:43 -0800 Subject: [PATCH 37/53] Remove unnecessary conditional --- lib/handlebars/compiler/javascript-compiler.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index ff6bc476c..55edb9af7 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -17,9 +17,7 @@ JavaScriptCompiler.prototype = { wrap = true; } - if (/^[0-9]+$/.test(name)) { - ret = parent + "[" + name + "]"; - } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { + if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { ret = parent + "." + name; } else { ret = parent + "['" + name + "']"; From 2812fe27758e38dbefad8b02c61ffb19aabf2410 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 19 Jan 2014 17:32:25 -0600 Subject: [PATCH 38/53] Convert template spec to object literal This allows for metadata to be associated with the template and a simplification of the template init logic. --- .../compiler/javascript-compiler.js | 112 +++++++++--------- lib/handlebars/runtime.js | 62 ++++++---- spec/expected/empty.amd.js | 8 +- spec/javascript-compiler.js | 2 +- 4 files changed, 95 insertions(+), 89 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index ff6bc476c..f0ae5ac85 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -35,7 +35,7 @@ JavaScriptCompiler.prototype = { compilerInfo: function() { var revision = COMPILER_REVISION, versions = REVISION_CHANGES[revision]; - return "this.compilerInfo = ["+revision+",'"+versions+"'];\n"; + return [revision, versions]; }, appendToBuffer: function(string) { @@ -62,6 +62,7 @@ JavaScriptCompiler.prototype = { this.options = options || {}; this.stringParams = this.options.stringParams; this.trackIds = this.options.trackIds; + this.precompile = !asObject; log('debug', this.environment.disassemble() + "\n\n"); @@ -69,14 +70,14 @@ JavaScriptCompiler.prototype = { this.isChild = !!context; this.context = context || { programs: [], - environments: [], - aliases: { } + environments: [] }; this.preamble(); this.stackSlot = 0; this.stackVars = []; + this.aliases = {}; this.registers = { list: [] }; this.hashes = []; this.compileStack = []; @@ -110,22 +111,38 @@ JavaScriptCompiler.prototype = { throw new Exception('Compile completed with content left on stack'); } - return this.createFunctionContext(asObject); - }, + var fn = this.createFunctionContext(asObject); + if (!this.isChild) { + var ret = { + compiler: this.compilerInfo(), + main: fn + }; + this.context.programs.map(function(program, index) { + if (program) { + ret[index] = program; + } + }); - preamble: function() { - var out = []; + if (this.environment.usePartial) { + ret.usePartial = true; + } + if (this.options.data) { + ret.useData = true; + } - if (!this.isChild) { - var namespace = this.namespace; + if (!asObject) { + ret.compiler = JSON.stringify(ret.compiler); + ret = this.objectLiteral(ret); + } - var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);"; - if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; } - if (this.options.data) { copies = copies + " data = this.initData(depth0, data);"; } - out.push(copies); + return ret; } else { - out.push(''); + return fn; } + }, + + preamble: function() { + var out = []; if (!this.environment.isSimple) { out.push(", buffer = " + this.initializeBuffer()); @@ -143,32 +160,25 @@ JavaScriptCompiler.prototype = { var locals = this.stackVars.concat(this.registers.list); if(locals.length > 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); + this.source[0] += ", " + locals.join(", "); } // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } + for (var alias in this.aliases) { + if (this.aliases.hasOwnProperty(alias)) { + this.source[0] += ', ' + alias + '=' + this.aliases[alias]; } } - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + if (this.source[0]) { + this.source[0] = "var " + this.source[0].substring(2) + ";"; } if (!this.environment.isSimple) { this.pushSource("return buffer;"); } - var params = this.isChild ? ["depth0", "data"] : [this.namespace, "depth0", "helpers", "partials", "data"]; + var params = ["depth0", "helpers", "partials", "data"]; for(var i=0, l=this.environment.depths.list.length; i= 1.0.0']; -helpers = this.merge(helpers, Handlebars.helpers); data = this.initData(depth0, data); +return templates['empty'] = template({"compiler":[4,">= 1.0.0"],"main":function(depth0,helpers,partials,data) { var buffer = ""; - - return buffer; - }); + },"useData":true}); }); diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js index 884abf1c9..a9ae0f8a9 100644 --- a/spec/javascript-compiler.js +++ b/spec/javascript-compiler.js @@ -32,7 +32,7 @@ describe('javascript-compiler api', function() { }); it('should allow compilerInfo override', function() { handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() { - return 'this.compilerInfo = "crazy";'; + return 'crazy'; }; handlebarsEnv.VM.checkRevision = function(compilerInfo) { if (compilerInfo !== 'crazy') { From dd365eaede63b2a1ae2e58778647ffe42e0539c3 Mon Sep 17 00:00:00 2001 From: PatrickJS Date: Mon, 3 Feb 2014 22:31:02 -0800 Subject: [PATCH 39/53] update copyright year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f466a93fd..e9563e18b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2011 by Yehuda Katz +Copyright (C) 2014 by Yehuda Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 7a6f706dc41a39b360e33c83fe31684316d5d3f9 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 9 Feb 2014 12:19:55 -0600 Subject: [PATCH 40/53] Add initial copyright year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e9563e18b..a2d22cbb4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2014 by Yehuda Katz +Copyright (C) 2011-2014 by Yehuda Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From d6e86af41ab987b5ad8be0d4a98f0cc408cb5e1d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 9 Feb 2014 13:09:38 -0600 Subject: [PATCH 41/53] Update to latest packager version Fixes #705 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6403c5d46..0ce1d5bdc 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-watch": "~0.5.3", "grunt-saucelabs": "~4.1.2", - "es6-module-packager": "0.x", + "es6-module-packager": "1.x", "jison": "~0.3.0", "keen.io": "0.0.3", "mocha": "*", From 7a7ad74ff1c4fe8685d33cceebe03f1474eb865d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 19 Jan 2014 18:54:13 -0600 Subject: [PATCH 42/53] Optimize buffer generate first and all edge cases --- .../compiler/javascript-compiler.js | 65 +++++++++++-------- spec/expected/empty.amd.js | 5 +- spec/javascript-compiler.js | 2 + 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 8222506d2..878181901 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -140,42 +140,27 @@ JavaScriptCompiler.prototype = { }, preamble: function() { - var out = []; - - if (!this.environment.isSimple) { - out.push(", buffer = " + this.initializeBuffer()); - } else { - out.push(""); - } - // track the last context pushed into place to allow skipping the // getContext opcode when it would be a noop this.lastContext = 0; - this.source = out; + this.source = []; }, createFunctionContext: function(asObject) { - var locals = this.stackVars.concat(this.registers.list); + var varDeclarations = ''; + var locals = this.stackVars.concat(this.registers.list); if(locals.length > 0) { - this.source[0] += ", " + locals.join(", "); + varDeclarations += ", " + locals.join(", "); } // Generate minimizer alias mappings for (var alias in this.aliases) { if (this.aliases.hasOwnProperty(alias)) { - this.source[0] += ', ' + alias + '=' + this.aliases[alias]; + varDeclarations += ', ' + alias + '=' + this.aliases[alias]; } } - if (this.source[0]) { - this.source[0] = "var " + this.source[0].substring(2) + ";"; - } - - if (!this.environment.isSimple) { - this.pushSource("return buffer;"); - } - var params = ["depth0", "helpers", "partials", "data"]; for(var i=0, l=this.environment.depths.list.length; i= 1.0.0"],"main":function(depth0,helpers,partials,data) { - var buffer = ""; - return buffer; - },"useData":true}); + return ""; +},"useData":true}); }); diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js index a9ae0f8a9..16058680b 100644 --- a/spec/javascript-compiler.js +++ b/spec/javascript-compiler.js @@ -45,10 +45,12 @@ describe('javascript-compiler api', function() { describe('buffer', function() { var $superAppend, $superCreate; beforeEach(function() { + handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true; $superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer; $superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer; }); afterEach(function() { + handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false; handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = $superAppend; handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = $superCreate; }); From 954253305a0deadf25236e087dd4d2908aee715a Mon Sep 17 00:00:00 2001 From: Jesse Ezell Date: Thu, 11 Jul 2013 12:28:37 -0700 Subject: [PATCH 43/53] Add {{{{ }}}} for raw blocks --- spec/blocks.js | 8 ++++++++ src/handlebars.l | 5 +++++ src/handlebars.yy | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/blocks.js b/spec/blocks.js index d72a44ec4..b9672af87 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -21,6 +21,14 @@ describe('blocks', function() { equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used"); }); + + it("raw block", function() { + var string = "{{{{ {{test}} }}}}"; + var hash = { test: "hello" }; + shouldCompileTo(string, hash, " {{test}} ", + "raw block ignores blocks"); + }); + it("empty block", function() { var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"; var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"}; diff --git a/src/handlebars.l b/src/handlebars.l index 630840eb5..51e921aec 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -54,6 +54,11 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} "(" return 'OPEN_SEXPR'; ")" return 'CLOSE_SEXPR'; +"{{{{"[^\x00]*"}}}}" { + yytext = yytext.substr(4, yyleng-8); + this.popState(); + return 'RAW_BLOCK'; + } "{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL'; "{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK'; "{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK'; diff --git a/src/handlebars.yy b/src/handlebars.yy index 40f68ce59..8b5b77455 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -35,7 +35,8 @@ statements ; statement - : openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$) + : RAW_BLOCK { $$ = new yy.ContentNode($1, @$); } + | openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$) | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$) | mustache -> $1 | partial -> $1 From 9b14dc40a5ff5c3db0b62278dc0ea794c9a16593 Mon Sep 17 00:00:00 2001 From: Jesse Ezell Date: Thu, 11 Jul 2013 14:50:14 -0700 Subject: [PATCH 44/53] raw block helpers --- lib/handlebars/compiler/ast.js | 9 +++++++++ spec/blocks.js | 8 -------- spec/helpers.js | 20 ++++++++++++++++++++ src/handlebars.l | 7 ++++++- src/handlebars.yy | 6 +++++- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 5a5f514db..b9e67d7fe 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -131,6 +131,15 @@ var AST = { } }, + RawBlockNode: function(mustache, content, locInfo) { + LocationInfo.call(this, locInfo); + + this.type = 'block'; + this.mustache = mustache; + mustache.sexpr.isHelper = mustache.isHelper = true; + mustache.params.splice(0, 0, new AST.StringNode(content, locInfo)); + }, + ContentNode: function(string, locInfo) { LocationInfo.call(this, locInfo); this.type = "content"; diff --git a/spec/blocks.js b/spec/blocks.js index b9672af87..8f7c242fa 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -20,14 +20,6 @@ describe('blocks', function() { equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used"); }); - - - it("raw block", function() { - var string = "{{{{ {{test}} }}}}"; - var hash = { test: "hello" }; - shouldCompileTo(string, hash, " {{test}} ", - "raw block ignores blocks"); - }); it("empty block", function() { var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"; diff --git a/spec/helpers.js b/spec/helpers.js index 904f56a7c..b1d972291 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -9,6 +9,26 @@ describe('helpers', function() { shouldCompileTo(string, [hash, helpers], "Goodbye"); }); + it("helper for raw block gets raw content", function() { + var string = "{{{{raw}}}} {{test}} {{{{/raw}}}}"; + var hash = { test: "hello" }; + var helpers = { raw: function(content) { + return content; + } }; + shouldCompileTo(string, [hash, helpers], " {{test}} ", + "raw block helper gets raw content"); + }); + + it("helper for raw block gets parameters", function() { + var string = "{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}"; + var hash = { test: "hello" }; + var helpers = { raw: function(content, a, b, c) { + return content + a + b + c; + } }; + shouldCompileTo(string, [hash, helpers], " {{test}} 123", + "raw block helper gets raw content"); + }); + it("helper block with complex lookup expression", function() { var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"; var hash = {name: "Alan"}; diff --git a/src/handlebars.l b/src/handlebars.l index 51e921aec..9b1266526 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -1,5 +1,5 @@ -%x mu emu com +%x mu emu com raw %{ @@ -49,11 +49,16 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} return 'CONTENT'; } +"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { yytext = yytext.substr(5, yyleng-9); this.popState(); return 'END_RAW_BLOCK'; } +[^\x00]*?/("{{{{/") { return 'CONTENT'; } + [\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT'; "(" return 'OPEN_SEXPR'; ")" return 'CLOSE_SEXPR'; +"{{{{" { return 'OPEN_RAW_BLOCK'; } +"}}}}" { this.popState(); this.begin('raw'); return 'CLOSE_RAW_BLOCK'; } "{{{{"[^\x00]*"}}}}" { yytext = yytext.substr(4, yyleng-8); this.popState(); diff --git a/src/handlebars.yy b/src/handlebars.yy index 8b5b77455..464d1bab0 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -35,7 +35,7 @@ statements ; statement - : RAW_BLOCK { $$ = new yy.ContentNode($1, @$); } + : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, @$) | openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$) | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$) | mustache -> $1 @@ -44,6 +44,10 @@ statement | COMMENT -> new yy.CommentNode($1, @$) ; +openRawBlock + : OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> new yy.MustacheNode($2, null, '', '', @$) + ; + openBlock : OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$) ; From 14b7ef9066d107dc83deedc8e6791947811cc764 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 9 Feb 2014 15:11:12 -0600 Subject: [PATCH 45/53] Make raw blocks operate like blocks --- lib/handlebars/compiler/ast.js | 11 ++++++++--- spec/helpers.js | 8 ++++---- spec/parser.js | 4 ++++ src/handlebars.l | 12 ++++++++++-- src/handlebars.yy | 2 +- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index b9e67d7fe..286917dbe 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -131,13 +131,18 @@ var AST = { } }, - RawBlockNode: function(mustache, content, locInfo) { + RawBlockNode: function(mustache, content, close, locInfo) { LocationInfo.call(this, locInfo); + if (mustache.sexpr.id.original !== close) { + throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this); + } + + content = new AST.ContentNode(content, locInfo); + this.type = 'block'; this.mustache = mustache; - mustache.sexpr.isHelper = mustache.isHelper = true; - mustache.params.splice(0, 0, new AST.StringNode(content, locInfo)); + this.program = new AST.ProgramNode([content], locInfo); }, ContentNode: function(string, locInfo) { diff --git a/spec/helpers.js b/spec/helpers.js index b1d972291..ccde982ba 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -12,8 +12,8 @@ describe('helpers', function() { it("helper for raw block gets raw content", function() { var string = "{{{{raw}}}} {{test}} {{{{/raw}}}}"; var hash = { test: "hello" }; - var helpers = { raw: function(content) { - return content; + var helpers = { raw: function(options) { + return options.fn(); } }; shouldCompileTo(string, [hash, helpers], " {{test}} ", "raw block helper gets raw content"); @@ -22,8 +22,8 @@ describe('helpers', function() { it("helper for raw block gets parameters", function() { var string = "{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}"; var hash = { test: "hello" }; - var helpers = { raw: function(content, a, b, c) { - return content + a + b + c; + var helpers = { raw: function(a, b, c, options) { + return options.fn() + a + b + c; } }; shouldCompileTo(string, [hash, helpers], " {{test}} 123", "raw block helper gets raw content"); diff --git a/spec/parser.js b/spec/parser.js index 5b6dc8a4e..097bb15b7 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -157,6 +157,10 @@ describe('parser', function() { shouldThrow(function() { ast_for("{{#goodbyes}}{{/hellos}}"); }, Error, /goodbyes doesn't match hellos/); + + shouldThrow(function() { + ast_for("{{{{goodbyes}}}} {{{{/hellos}}}}"); + }, Error, /goodbyes doesn't match hellos/); }); it('knows how to report the correct line number in errors', function() { diff --git a/src/handlebars.l b/src/handlebars.l index 9b1266526..cafdd72c7 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -49,7 +49,11 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} return 'CONTENT'; } -"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { yytext = yytext.substr(5, yyleng-9); this.popState(); return 'END_RAW_BLOCK'; } +"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { + yytext = yytext.substr(5, yyleng-9); + this.popState(); + return 'END_RAW_BLOCK'; + } [^\x00]*?/("{{{{/") { return 'CONTENT'; } [\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT'; @@ -58,7 +62,11 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} ")" return 'CLOSE_SEXPR'; "{{{{" { return 'OPEN_RAW_BLOCK'; } -"}}}}" { this.popState(); this.begin('raw'); return 'CLOSE_RAW_BLOCK'; } +"}}}}" { + this.popState(); + this.begin('raw'); + return 'CLOSE_RAW_BLOCK'; + } "{{{{"[^\x00]*"}}}}" { yytext = yytext.substr(4, yyleng-8); this.popState(); diff --git a/src/handlebars.yy b/src/handlebars.yy index 464d1bab0..51796ec6d 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -35,7 +35,7 @@ statements ; statement - : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, @$) + : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$) | openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$) | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$) | mustache -> $1 From a9f76e1475e4b74572bbdf6abc07ba7893bf349e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 9 Feb 2014 17:21:49 -0600 Subject: [PATCH 46/53] jshint changes --- spec/builtins.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/builtins.js b/spec/builtins.js index 6d9fa0ba4..53f46ccd5 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -1,4 +1,4 @@ -/*global CompilerContext, shouldCompileTo, compileWithPartials */ +/*global CompilerContext, shouldCompileTo, compileWithPartials, handlebarsEnv */ describe('builtin helpers', function() { describe('#if', function() { it("if", function() { @@ -189,12 +189,14 @@ describe('builtin helpers', function() { }); it("#log", function() { - var string = "{{log blah}}"; var hash = { blah: "whee" }; var levelArg, logArg; - handlebarsEnv.log = function(level, arg){ levelArg = level, logArg = arg; }; + handlebarsEnv.log = function(level, arg){ + levelArg = level; + logArg = arg; + }; shouldCompileTo(string, hash, "", "log should not display"); equals(1, levelArg, "should call log with 1"); From 306feb497c72d539ce0f6f926fc8844fb35844fe Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 9 Feb 2014 17:22:18 -0600 Subject: [PATCH 47/53] Implement lookup helper --- lib/handlebars/base.js | 4 ++++ lib/handlebars/compiler/compiler.js | 3 ++- spec/builtins.js | 21 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 7ebfc5567..02d274d3a 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -193,6 +193,10 @@ function registerDefaultHelpers(instance) { var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; instance.log(level, context); }); + + instance.registerHelper('lookup', function(obj, field, options) { + return obj && obj[field]; + }); } export var logger = { diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index cfcc70ac5..21e1024f2 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -85,7 +85,8 @@ Compiler.prototype = { 'if': true, 'unless': true, 'with': true, - 'log': true + 'log': true, + 'lookup': true }; if (knownHelpers) { for (var name in knownHelpers) { diff --git a/spec/builtins.js b/spec/builtins.js index 53f46ccd5..bbe494e08 100644 --- a/spec/builtins.js +++ b/spec/builtins.js @@ -203,4 +203,25 @@ describe('builtin helpers', function() { equals("whee", logArg, "should call log with 'whee'"); }); + + describe('#lookup', function() { + it('should lookup arbitrary content', function() { + var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}', + hash = {goodbyes: [0, 1], data: ['foo', 'bar']}; + + var template = CompilerContext.compile(string); + var result = template(hash); + + equal(result, 'foobar'); + }); + it('should not fail on undefined value', function() { + var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}', + hash = {goodbyes: [0, 1], data: ['foo', 'bar']}; + + var template = CompilerContext.compile(string); + var result = template(hash); + + equal(result, ''); + }); + }); }); From 16f135835eb55dcbb7fa26dab63a9c20fd3981ac Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 9 Feb 2014 18:35:22 -0600 Subject: [PATCH 48/53] Add support for depthed resolution of data fields --- lib/handlebars/base.js | 4 ++- lib/handlebars/compiler/compiler.js | 6 +--- .../compiler/javascript-compiler.js | 8 +++-- lib/handlebars/runtime.js | 6 ++++ spec/data.js | 35 ++++++++++--------- spec/parser.js | 4 +++ 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 02d274d3a..26eb1a884 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -223,5 +223,7 @@ export var logger = { export function log(level, obj) { logger.log(level, obj); } export var createFrame = function(object) { - return Utils.extend({}, object); + var frame = Utils.extend({}, object); + frame._parent = object; + return frame; }; diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 21e1024f2..be17ac338 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -310,11 +310,7 @@ Compiler.prototype = { DATA: function(data) { this.options.data = true; - if (data.id.isScoped || data.id.depth) { - throw new Exception('Scoped data references are not supported: ' + data.original, data); - } - - this.opcode('lookupData'); + this.opcode('lookupData', data.id.depth); var parts = data.id.parts; for(var i=0, l=parts.length; i Date: Sun, 9 Feb 2014 22:47:40 -0600 Subject: [PATCH 49/53] Better docs on how to file issues --- README.markdown | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 22dc474e1..1a284c1f0 100644 --- a/README.markdown +++ b/README.markdown @@ -352,9 +352,17 @@ See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/relea Known Issues ------------ +* Runtime/precompiler mismatches: Often result in errors like "can not find method match of object" or similar. Please verify the version of the runtime and the version used to precompile templates if odd issues occur after upgrading one component or another. * Handlebars.js can be cryptic when there's an error while rendering. * Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`) +Reporting Issues +---------------- + +Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released the bitly link will be updated to point to a fiddle template with the latest version. + +We also accept [pull requests][pull-request]! + Handlebars in the Wild ---------------------- @@ -447,7 +455,7 @@ gem build handlebars-source.gemspec gem push handlebars-source-*.gem ``` -After this point the handlebars site needs to be updated to point to the new version numbers. +After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle bitly link should be updated to point to the most recent distribution. License ------- @@ -457,3 +465,5 @@ Handlebars.js is released under the MIT license. [builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc [generator-release]: https://github.com/walmartlabs/generator-release [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master +[issue]: https://github.com/wycats/handlebars.js/issues/new +[jsfiddle]: http://l.kde.cc/hbs-bug From c72a8d5e844bf94d048630396b27728826e1b1a1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 19 Jan 2014 21:18:21 -0600 Subject: [PATCH 50/53] Add child accessor API --- lib/handlebars/compiler/compiler.js | 9 ++++++++- lib/handlebars/runtime.js | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index be17ac338..7acff1fee 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -453,10 +453,17 @@ export function compile(input, options, env) { } // Template is only compiled on first use and cached after that point. - return function(context, options) { + var ret = function(context, options) { if (!compiled) { compiled = compileInput(); } return compiled.call(this, context, options); }; + ret.child = function(i) { + if (!compiled) { + compiled = compileInput(); + } + return compiled.child(i); + }; + return ret; } diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 9974c713a..b179620c9 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -97,7 +97,7 @@ export function template(templateSpec, env) { compilerInfo: templateSpec.compiler }; - return function(context, options) { + var ret = function(context, options) { options = options || {}; var namespace = options.partial ? options : env, helpers, @@ -119,6 +119,11 @@ export function template(templateSpec, env) { } return templateSpec.main.call(container, context, helpers, partials, data); }; + + ret.child = function(i) { + return container.programWithDepth(i); + }; + return ret; } export function programWithDepth(i, data /*, $depth */) { From 6da3c314943d66aeec325b1c1d263ff4ace6406d Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 10 Feb 2014 01:48:12 -0600 Subject: [PATCH 51/53] Update compiler revision info --- lib/handlebars/base.js | 5 +++-- spec/expected/empty.amd.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 26eb1a884..c26d658d0 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -2,13 +2,14 @@ module Utils from "./utils"; import Exception from "./exception"; export var VERSION = "1.3.0"; -export var COMPILER_REVISION = 4; +export var COMPILER_REVISION = 5; export var REVISION_CHANGES = { 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 2: '== 1.0.0-rc.3', 3: '== 1.0.0-rc.4', - 4: '>= 1.0.0' + 4: '== 1.x.x', + 5: '>= 2.0.0' }; var isArray = Utils.isArray, diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js index d80446a1a..e9616c344 100644 --- a/spec/expected/empty.amd.js +++ b/spec/expected/empty.amd.js @@ -1,6 +1,6 @@ define(['handlebars.runtime'], function(Handlebars) { Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; -return templates['empty'] = template({"compiler":[4,">= 1.0.0"],"main":function(depth0,helpers,partials,data) { +return templates['empty'] = template({"compiler":[5,">= 2.0.0"],"main":function(depth0,helpers,partials,data) { return ""; },"useData":true}); }); From b22a150a375170ab17fddbf0ee47fa7db8686e7e Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 10 Feb 2014 02:01:20 -0600 Subject: [PATCH 52/53] Update release notes --- release-notes.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 3f0fac995..43f97d709 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,41 @@ ## Development -[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...master) +[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...master) + +## v2.0.0-alpha.1 - February 10th, 2014 +- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://api.github.com/users/kayleg)) +- [#569](https://github.com/wycats/handlebars.js/pull/569) - Unable to lookup array values using @index ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#491](https://github.com/wycats/handlebars.js/pull/491) - For nested helpers: get the @ variables of the outer helper from the inner one ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#669](https://github.com/wycats/handlebars.js/issues/669) - Ability to unregister a helper ([@dbachrach](https://api.github.com/users/dbachrach)) +- [#730](https://github.com/wycats/handlebars.js/pull/730) - Raw block helpers ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#634](https://github.com/wycats/handlebars.js/pull/634) - It would be great to have the helper name passed to `blockHelperMissing` ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#729](https://github.com/wycats/handlebars.js/pull/729) - Convert template spec to object literal ([@kpdecker](https://api.github.com/users/kpdecker)) + +- [#658](https://github.com/wycats/handlebars.js/issues/658) - Depthed helpers do not work after an upgrade from 1.0.0 ([@xibxor](https://api.github.com/users/xibxor)) +- [#671](https://github.com/wycats/handlebars.js/issues/671) - Crashes on no-parameter {{#each}} ([@stepancheg](https://api.github.com/users/stepancheg)) +- [#689](https://github.com/wycats/handlebars.js/issues/689) - broken template precompilation ([@AAS](https://api.github.com/users/AAS)) +- [#698](https://github.com/wycats/handlebars.js/pull/698) - Fix parser generation under windows ([@osiris43](https://api.github.com/users/osiris43)) +- [#699](https://github.com/wycats/handlebars.js/issues/699) - @DATA not compiles to invalid JS in stringParams mode ([@kpdecker](https://api.github.com/users/kpdecker)) +- [#705](https://github.com/wycats/handlebars.js/issues/705) - 1.3.0 can not be wrapped in an IIFE ([@craigteegarden](https://api.github.com/users/craigteegarden)) +- [#706](https://github.com/wycats/handlebars.js/pull/706) - README: Use with helper instead of relying on blockHelperMissing ([@scottgonzalez](https://api.github.com/users/scottgonzalez)) + +- [#700](https://github.com/wycats/handlebars.js/pull/700) - Remove redundant conditions ([@blakeembrey](https://api.github.com/users/blakeembrey)) +- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://api.github.com/users/blakeembrey)) + +Compatibility notes: +- `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`. +- Precompiler output has changed, which breaks compatibility with prior versions of the runtime and precompiled output. +- `JavaScriptCompiler.compilerInfo` now returns generic objects rather than javascript source. +- AST changes + - INTEGER -> NUMBER + - Additional PartialNode hash parameter + - New RawBlockNode type +- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons. + +[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1) ## v1.3.0 - January 1st, 2014 - [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://api.github.com/users/machty)) From a5ff1f3d22178b3889983be29b31b850a4528719 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 10 Feb 2014 02:11:07 -0600 Subject: [PATCH 53/53] vv2.0.0-alpha.1 --- components/bower.json | 2 +- components/handlebars.js.nuspec | 2 +- lib/handlebars/base.js | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/bower.json b/components/bower.json index 25923a475..571a7b87b 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "1.3.0", + "version": "v2.0.0-alpha.1", "main": "handlebars.js", "dependencies": {} } diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec index e4a169103..586a211a2 100644 --- a/components/handlebars.js.nuspec +++ b/components/handlebars.js.nuspec @@ -2,7 +2,7 @@ handlebars.js - 1.3.0 + v2.0.0-alpha.1 handlebars.js Authors https://github.com/wycats/handlebars.js/blob/master/LICENSE https://github.com/wycats/handlebars.js/ diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index c26d658d0..827bcb7c2 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,7 +1,7 @@ module Utils from "./utils"; import Exception from "./exception"; -export var VERSION = "1.3.0"; +export var VERSION = "v2.0.0-alpha.1"; export var COMPILER_REVISION = 5; export var REVISION_CHANGES = { diff --git a/package.json b/package.json index 0ce1d5bdc..9bfc9575b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "1.3.0", + "version": "v2.0.0-alpha.1", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [