Implement block decorators
These allow for a given block to be wrapped in helper methods or metadata and allow for more control over the current container and method before the code is run.
This commit is contained in:
@@ -69,6 +69,9 @@ function CodeGen(srcFile) {
|
||||
}
|
||||
|
||||
CodeGen.prototype = {
|
||||
isEmpty() {
|
||||
return !this.source.length;
|
||||
},
|
||||
prepend: function(source, loc) {
|
||||
this.source.unshift(this.wrap(source, loc));
|
||||
},
|
||||
|
||||
@@ -156,6 +156,15 @@ Compiler.prototype = {
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
DecoratorBlock(decorator) {
|
||||
let program = decorator.program && this.compileProgram(decorator.program);
|
||||
let params = this.setupFullMustacheParams(decorator, program, undefined),
|
||||
path = decorator.path;
|
||||
|
||||
this.useDecorators = true;
|
||||
this.opcode('registerDecorator', params.length, path.original);
|
||||
},
|
||||
|
||||
PartialStatement: function(partial) {
|
||||
this.usePartial = true;
|
||||
|
||||
@@ -201,6 +210,10 @@ Compiler.prototype = {
|
||||
this.opcode('append');
|
||||
}
|
||||
},
|
||||
Decorator(decorator) {
|
||||
this.DecoratorBlock(decorator);
|
||||
},
|
||||
|
||||
|
||||
ContentStatement: function(content) {
|
||||
if (content.value) {
|
||||
|
||||
@@ -64,6 +64,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.name = this.environment.name;
|
||||
this.isChild = !!context;
|
||||
this.context = context || {
|
||||
decorators: [],
|
||||
programs: [],
|
||||
environments: []
|
||||
};
|
||||
@@ -81,7 +82,7 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
this.useDepths = this.useDepths || environment.useDepths || this.options.compat;
|
||||
this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
|
||||
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
|
||||
|
||||
let opcodes = environment.opcodes,
|
||||
@@ -107,16 +108,43 @@ JavaScriptCompiler.prototype = {
|
||||
throw new Exception('Compile completed with content left on stack');
|
||||
}
|
||||
|
||||
if (!this.decorators.isEmpty()) {
|
||||
this.useDecorators = true;
|
||||
|
||||
this.decorators.prepend('var decorators = container.decorators;\n');
|
||||
this.decorators.push('return fn;');
|
||||
|
||||
if (asObject) {
|
||||
this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
|
||||
} else {
|
||||
this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
|
||||
this.decorators.push('}\n');
|
||||
this.decorators = this.decorators.merge();
|
||||
}
|
||||
} else {
|
||||
this.decorators = undefined;
|
||||
}
|
||||
|
||||
let fn = this.createFunctionContext(asObject);
|
||||
if (!this.isChild) {
|
||||
let ret = {
|
||||
compiler: this.compilerInfo(),
|
||||
main: fn
|
||||
};
|
||||
let programs = this.context.programs;
|
||||
|
||||
if (this.decorators) {
|
||||
ret.main_d = this.decorators; // eslint-disable-line camelcase
|
||||
ret.useDecorators = true;
|
||||
}
|
||||
|
||||
let {programs, decorators} = this.context;
|
||||
for (i = 0, l = programs.length; i < l; i++) {
|
||||
if (programs[i]) {
|
||||
ret[i] = programs[i];
|
||||
if (decorators[i]) {
|
||||
ret[i + '_d'] = decorators[i];
|
||||
ret.useDecorators = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +191,7 @@ JavaScriptCompiler.prototype = {
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = new CodeGen(this.options.srcName);
|
||||
this.decorators = new CodeGen(this.options.srcName);
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
@@ -561,6 +590,24 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
// [registerDecorator]
|
||||
//
|
||||
// On stack, before: hash, program, params..., ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Pops off the decorator's parameters, invokes the decorator,
|
||||
// and inserts the decorator into the decorators list.
|
||||
registerDecorator(paramSize, name) {
|
||||
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
||||
options = this.setupHelperArgs(name, paramSize);
|
||||
|
||||
this.decorators.push([
|
||||
'fn = ',
|
||||
this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
|
||||
' || fn;'
|
||||
]);
|
||||
},
|
||||
|
||||
// [invokeHelper]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
@@ -738,6 +785,7 @@ JavaScriptCompiler.prototype = {
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
|
||||
this.context.decorators[index] = compiler.decorators;
|
||||
this.context.environments[index] = child;
|
||||
|
||||
this.useDepths = this.useDepths || compiler.useDepths;
|
||||
@@ -946,7 +994,16 @@ JavaScriptCompiler.prototype = {
|
||||
},
|
||||
|
||||
setupParams: function(helper, paramSize, params) {
|
||||
let options = {}, contexts = [], types = [], ids = [], param;
|
||||
let options = {},
|
||||
contexts = [],
|
||||
types = [],
|
||||
ids = [],
|
||||
objectArgs = !params,
|
||||
param;
|
||||
|
||||
if (objectArgs) {
|
||||
params = [];
|
||||
}
|
||||
|
||||
options.name = this.quotedString(helper);
|
||||
options.hash = this.popStack();
|
||||
@@ -985,6 +1042,10 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (objectArgs) {
|
||||
options.args = this.source.generateArray(params);
|
||||
}
|
||||
|
||||
if (this.trackIds) {
|
||||
options.ids = this.source.generateArray(ids);
|
||||
}
|
||||
@@ -1009,9 +1070,11 @@ JavaScriptCompiler.prototype = {
|
||||
this.useRegister('options');
|
||||
params.push('options');
|
||||
return ['options=', options];
|
||||
} else {
|
||||
} else if (params) {
|
||||
params.push(options);
|
||||
return '';
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,7 +90,9 @@ export function template(templateSpec, env) {
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
return templateSpec[i];
|
||||
let ret = templateSpec[i];
|
||||
ret.decorator = templateSpec[i + '_d'];
|
||||
return ret;
|
||||
},
|
||||
|
||||
programs: [],
|
||||
@@ -142,7 +144,17 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
}
|
||||
|
||||
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
|
||||
function main(context/*, options*/) {
|
||||
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
|
||||
}
|
||||
|
||||
if (templateSpec.main_d) {
|
||||
// Note that we are ignoring the props value here as we apply things slightly differently
|
||||
// when applying decorators to the root function.
|
||||
main = templateSpec.main_d(main, {}, container, undefined, data, blockParams, depths);
|
||||
}
|
||||
|
||||
return main(context, options);
|
||||
}
|
||||
ret.isTop = true;
|
||||
|
||||
@@ -190,6 +202,13 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
|
||||
blockParams && [options.blockParams].concat(blockParams),
|
||||
currentDepths);
|
||||
}
|
||||
|
||||
if (fn.decorator) {
|
||||
let props = {};
|
||||
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
|
||||
Utils.extend(prog, props);
|
||||
}
|
||||
|
||||
prog.program = i;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
prog.blockParams = declaredBlockParams || 0;
|
||||
|
||||
Reference in New Issue
Block a user