Merge pull request #819 from wycats/compat-mode
Implement recursive field lookup
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -214,7 +214,8 @@ Compiler.prototype = {
|
||||
if (partial.context) {
|
||||
this.accept(partial.context);
|
||||
} else {
|
||||
this.opcode('push', 'depth0');
|
||||
this.opcode('getContext', 0);
|
||||
this.opcode('pushContext');
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', partialName.name, partial.indent || '');
|
||||
@@ -307,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);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -423,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);
|
||||
@@ -439,6 +443,9 @@ export function compile(input, options, env) {
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
if (options.compat) {
|
||||
options.useDepths = true;
|
||||
}
|
||||
|
||||
var compiled;
|
||||
|
||||
|
||||
@@ -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,6 +74,8 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
this.useDepths = this.useDepths || environment.depths.list.length || this.options.compat;
|
||||
|
||||
var opcodes = environment.opcodes,
|
||||
opcode,
|
||||
i,
|
||||
@@ -115,6 +122,9 @@ JavaScriptCompiler.prototype = {
|
||||
if (this.options.data) {
|
||||
ret.useData = true;
|
||||
}
|
||||
if (this.useDepths) {
|
||||
ret.depths = true;
|
||||
}
|
||||
|
||||
if (!asObject) {
|
||||
ret.compiler = JSON.stringify(ret.compiler);
|
||||
@@ -151,8 +161,8 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
var params = ["depth0", "helpers", "partials", "data"];
|
||||
|
||||
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
||||
params.push("depth" + this.environment.depths.list[i]);
|
||||
if (this.useDepths) {
|
||||
params.push('depths');
|
||||
}
|
||||
|
||||
// Perform a second pass over the output to merge content when possible
|
||||
@@ -230,7 +240,7 @@ JavaScriptCompiler.prototype = {
|
||||
blockValue: function(name) {
|
||||
this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
var params = ["depth0"];
|
||||
var params = [this.contextName(0)];
|
||||
this.setupParams(name, 0, params);
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
@@ -249,7 +259,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
// We're being a bit cheeky and reusing the options value from the prior exec
|
||||
var params = ["depth0"];
|
||||
var params = [this.contextName(0)];
|
||||
this.setupParams('', 0, params, true);
|
||||
|
||||
this.flushInline();
|
||||
@@ -341,7 +351,7 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// Pushes the value of the current context onto the stack.
|
||||
pushContext: function() {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
this.pushStackLiteral(this.contextName(this.lastContext));
|
||||
},
|
||||
|
||||
// [lookupOnContext]
|
||||
@@ -351,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
|
||||
@@ -410,7 +425,7 @@ JavaScriptCompiler.prototype = {
|
||||
resolvePossibleLambda: function() {
|
||||
this.aliases.lambda = 'this.lambda';
|
||||
|
||||
this.push('lambda(' + this.popStack() + ', depth0)');
|
||||
this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')');
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
@@ -422,8 +437,7 @@ JavaScriptCompiler.prototype = {
|
||||
// provides the string value of a parameter along with its
|
||||
// depth rather than resolving it immediately.
|
||||
pushStringParam: function(string, type) {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
|
||||
this.pushContext();
|
||||
this.pushString(type);
|
||||
|
||||
// If it's a subexpression, the string result
|
||||
@@ -669,6 +683,8 @@ JavaScriptCompiler.prototype = {
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
|
||||
this.context.environments[index] = child;
|
||||
|
||||
this.useDepths = this.useDepths || compiler.useDepths;
|
||||
} else {
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
@@ -690,17 +706,17 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
|
||||
var child = this.environment.children[guid],
|
||||
depths = child.depths.list, depth;
|
||||
depths = child.depths.list,
|
||||
useDepths = this.useDepths,
|
||||
depth;
|
||||
|
||||
var programParams = [child.index, 'data'];
|
||||
|
||||
for(var i=0, l = depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
programParams.push('depth' + (depth - 1));
|
||||
if (useDepths) {
|
||||
programParams.push('depths');
|
||||
}
|
||||
|
||||
return (depths.length === 0 ? 'this.program(' : 'this.programWithDepth(') + programParams.join(', ') + ')';
|
||||
return 'this.program(' + programParams.join(', ') + ')';
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
@@ -847,6 +863,14 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
contextName: function(context) {
|
||||
if (this.useDepths && context) {
|
||||
return 'depths[' + context + ']';
|
||||
} else {
|
||||
return 'depth' + context;
|
||||
}
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/g, '\\\\')
|
||||
@@ -878,7 +902,7 @@ JavaScriptCompiler.prototype = {
|
||||
params: params,
|
||||
paramsInit: paramsInit,
|
||||
name: foundHelper,
|
||||
callParams: ["depth0"].concat(params).join(", ")
|
||||
callParams: [this.contextName(0)].concat(params).join(", ")
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
+24
-24
@@ -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;
|
||||
},
|
||||
@@ -75,17 +83,16 @@ export function template(templateSpec, env) {
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, data) {
|
||||
program: function(i, data, depths) {
|
||||
var programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
if(data) {
|
||||
programWrapper = program(this, i, fn, data);
|
||||
if (data || depths) {
|
||||
programWrapper = program(this, i, fn, data, depths);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = program(this, i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
programWithDepth: env.VM.programWithDepth,
|
||||
|
||||
data: function(data, depth) {
|
||||
while (data && depth--) {
|
||||
@@ -117,7 +124,12 @@ export function template(templateSpec, env) {
|
||||
if (!options.partial && templateSpec.useData) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data);
|
||||
var depths;
|
||||
if (templateSpec.depths) {
|
||||
depths = [context];
|
||||
}
|
||||
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
|
||||
};
|
||||
|
||||
ret._setup = function(options) {
|
||||
@@ -134,35 +146,23 @@ export function template(templateSpec, env) {
|
||||
};
|
||||
|
||||
ret._child = function(i) {
|
||||
if (templateSpec.depth) {
|
||||
throw new Exception('_child can not be used with depthed methods');
|
||||
}
|
||||
|
||||
return container.programWithDepth(i);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function programWithDepth(i, data /*, $depth */) {
|
||||
/*jshint -W040 */
|
||||
var args = Array.prototype.slice.call(arguments, 2),
|
||||
container = this,
|
||||
fn = container.fn(i);
|
||||
|
||||
export function program(container, i, fn, data, depths) {
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(container, [context, container.helpers, container.partials, options.data || data].concat(args));
|
||||
return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = args.length;
|
||||
return prog;
|
||||
}
|
||||
|
||||
export function program(container, i, fn, data) {
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.call(container, context, container.helpers, container.partials, options.data || data);
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = 0;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+1
-1
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user