diff options
author | kpdecker <kpdecker@gmail.com> | 2014-08-13 20:46:07 -0500 |
---|---|---|
committer | kpdecker <kpdecker@gmail.com> | 2014-08-13 20:46:42 -0500 |
commit | c98613b711a70a82a960c20c00d8d92d5eed1024 (patch) | |
tree | 364862d90bc601284d35f5c19e8fed1abad7e4f4 | |
parent | 625b00e1dafccc8653bf4cb4dc3dac55f6b3b020 (diff) | |
download | handlebars.js-c98613b711a70a82a960c20c00d8d92d5eed1024.zip handlebars.js-c98613b711a70a82a960c20c00d8d92d5eed1024.tar.gz handlebars.js-c98613b711a70a82a960c20c00d8d92d5eed1024.tar.bz2 |
Implement recursive field lookup in compat mode
Provides the mustache behavior of recursive lookup without the use of depthed paths.
Note that this does incur a fairly dramatic performance penalty for depthed queries.
-rw-r--r-- | bench/throughput.js | 8 | ||||
-rw-r--r-- | lib/handlebars/compiler/compiler.js | 8 | ||||
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 24 | ||||
-rw-r--r-- | lib/handlebars/runtime.js | 8 | ||||
-rw-r--r-- | spec/blocks.js | 21 | ||||
-rw-r--r-- | spec/env/common.js | 2 |
6 files changed, 62 insertions, 9 deletions
diff --git a/bench/throughput.js b/bench/throughput.js index 308446a..d27a94d 100644 --- a/bench/throughput.js +++ b/bench/throughput.js @@ -28,11 +28,13 @@ function makeSuite(bench, name, template, handlebarsOnly) { partials = template.partials, handlebarsOut, + compatOut, dustOut, ecoOut, mustacheOut; var handlebar = Handlebars.compile(template.handlebars, {data: false}), + compat = Handlebars.compile(template.handlebars, {data: false, compat: true}), options = {helpers: template.helpers}; _.each(template.partials && template.partials.handlebars, function(partial, name) { Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false})); @@ -43,6 +45,11 @@ function makeSuite(bench, name, template, handlebarsOnly) { handlebar(context, options); }); + compatOut = compat(context, options); + bench("compat", function() { + compat(context, options); + }); + if (handlebarsOnly) { return; } @@ -107,6 +114,7 @@ function makeSuite(bench, name, template, handlebarsOnly) { } } + compare(compatOut, 'compat'); compare(dustOut, 'dust'); compare(ecoOut, 'eco'); compare(mustacheOut, 'mustache'); diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 48575d4..ffddec4 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -308,7 +308,7 @@ Compiler.prototype = { // Context reference, i.e. `{{foo .}}` or `{{foo ..}}` this.opcode('pushContext'); } else { - this.opcode('lookupOnContext', id.parts, id.falsy); + this.opcode('lookupOnContext', id.parts, id.falsy, id.isScoped); } }, @@ -424,6 +424,9 @@ export function precompile(input, options, env) { if (!('data' in options)) { options.data = true; } + if (options.compat) { + options.useDepths = true; + } var ast = env.parse(input); var environment = new env.Compiler().compile(ast, options); @@ -440,6 +443,9 @@ export function compile(input, options, env) { if (!('data' in options)) { options.data = true; } + if (options.compat) { + options.useDepths = true; + } var compiled; diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index f1b6d36..b9b858d 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -17,6 +17,11 @@ JavaScriptCompiler.prototype = { return parent + "['" + name + "']"; } }, + depthedLookup: function(name) { + this.aliases.lookup = 'this.lookup'; + + return 'lookup(depths, "' + name + '")'; + }, compilerInfo: function() { var revision = COMPILER_REVISION, @@ -69,7 +74,7 @@ JavaScriptCompiler.prototype = { this.compileChildren(environment, options); - this.useDepths = this.useDepths || environment.depths.list.length; + this.useDepths = this.useDepths || environment.depths.list.length || this.options.compat; var opcodes = environment.opcodes, opcode, @@ -356,15 +361,20 @@ JavaScriptCompiler.prototype = { // // Looks up the value of `name` on the current context and pushes // it onto the stack. - lookupOnContext: function(parts, falsy) { + lookupOnContext: function(parts, falsy, scoped) { /*jshint -W083 */ - this.pushContext(); - if (!parts) { - return; + var i = 0, + len = parts.length; + + if (!scoped && this.isChild && this.options.compat && !this.lastContext) { + // The depthed query is expected to handle the undefined logic for the root level that + // is implemented below, so we evaluate that directly in compat mode + this.pushStackLiteral(this.depthedLookup(parts[i++])); + } else { + this.pushContext(); } - var len = parts.length; - for (var i = 0; i < len; i++) { + for (; i < len; i++) { this.replaceStack(function(current) { var lookup = this.nameLookup(current, parts[i], 'context'); // We want to ensure that zero and false are handled properly for the first element diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index 530dbd2..ec54f53 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -63,6 +63,14 @@ export function template(templateSpec, env) { // Just add water var container = { + lookup: function(depths, name) { + var len = depths.length; + for (var i = 0; i < len; i++) { + if (depths[i] && depths[i][name] != null) { + return depths[i][name]; + } + } + }, lambda: function(current, context) { return typeof current === 'function' ? current.call(context) : current; }, diff --git a/spec/blocks.js b/spec/blocks.js index c57a67e..a13cce2 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -94,4 +94,25 @@ describe('blocks', function() { 'No people\n'); }); }); + + describe('compat mode', function() { + it("block with deep recursive lookup lookup", function() { + var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}"; + var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] }; + + shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!"); + }); + it("block with deep recursive pathed lookup", function() { + var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}"; + var hash = {omg: {yes: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] }; + + shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!"); + }); + it("block with missed recursive lookup", function() { + var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}"; + var hash = {omg: {no: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] }; + + shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel "); + }); + }); }); diff --git a/spec/env/common.js b/spec/env/common.js index 03eef93..a603fc0 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -17,7 +17,7 @@ global.compileWithPartials = function(string, hashOrArray, partials) { if(Object.prototype.toString.call(hashOrArray) === "[object Array]") { ary = []; ary.push(hashOrArray[0]); - ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2], compat: hashOrArray[3] }); + ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] }); options = {compat: hashOrArray[3]}; } else { ary = [hashOrArray]; |