summaryrefslogtreecommitdiffstats
path: root/lib/handlebars
diff options
context:
space:
mode:
Diffstat (limited to 'lib/handlebars')
-rw-r--r--lib/handlebars/base.js17
-rw-r--r--lib/handlebars/compiler/code-gen.js3
-rw-r--r--lib/handlebars/compiler/compiler.js13
-rw-r--r--lib/handlebars/compiler/helpers.js11
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js71
-rw-r--r--lib/handlebars/compiler/printer.js8
-rw-r--r--lib/handlebars/compiler/visitor.js2
-rw-r--r--lib/handlebars/compiler/whitespace-control.js2
-rw-r--r--lib/handlebars/decorators.js6
-rw-r--r--lib/handlebars/decorators/inline.js22
-rw-r--r--lib/handlebars/runtime.js32
11 files changed, 176 insertions, 11 deletions
diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 41bb98d..e59f5e7 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -1,6 +1,7 @@
import {createFrame, extend, toString} from './utils';
import Exception from './exception';
import {registerDefaultHelpers} from './helpers';
+import {registerDefaultDecorators} from './decorators';
import logger from './logger';
export const VERSION = '3.0.1';
@@ -17,11 +18,13 @@ export const REVISION_CHANGES = {
const objectType = '[object Object]';
-export function HandlebarsEnvironment(helpers, partials) {
+export function HandlebarsEnvironment(helpers, partials, decorators) {
this.helpers = helpers || {};
this.partials = partials || {};
+ this.decorators = decorators || {};
registerDefaultHelpers(this);
+ registerDefaultDecorators(this);
}
HandlebarsEnvironment.prototype = {
@@ -54,6 +57,18 @@ HandlebarsEnvironment.prototype = {
},
unregisterPartial: function(name) {
delete this.partials[name];
+ },
+
+ registerDecorator: function(name, fn) {
+ if (toString.call(name) === objectType) {
+ if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
+ extend(this.decorators, name);
+ } else {
+ this.decorators[name] = fn;
+ }
+ },
+ unregisterDecorator: function(name) {
+ delete this.decorators[name];
}
};
diff --git a/lib/handlebars/compiler/code-gen.js b/lib/handlebars/compiler/code-gen.js
index 3af4f8c..6541fe8 100644
--- a/lib/handlebars/compiler/code-gen.js
+++ b/lib/handlebars/compiler/code-gen.js
@@ -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));
},
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index a689e7d..64af5da 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -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) {
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
index 9c40f0d..e09a08d 100644
--- a/lib/handlebars/compiler/helpers.js
+++ b/lib/handlebars/compiler/helpers.js
@@ -84,8 +84,9 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) {
let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';
+ let decorator = (/\*/.test(open));
return {
- type: 'MustacheStatement',
+ type: decorator ? 'Decorator' : 'MustacheStatement',
path,
params,
hash,
@@ -124,12 +125,18 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
validateClose(openBlock, close);
}
+ let decorator = (/\*/.test(openBlock.open));
+
program.blockParams = openBlock.blockParams;
let inverse,
inverseStrip;
if (inverseAndProgram) {
+ if (decorator) {
+ throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
+ }
+
if (inverseAndProgram.chain) {
inverseAndProgram.program.body[0].closeStrip = close.strip;
}
@@ -145,7 +152,7 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
}
return {
- type: 'BlockStatement',
+ type: decorator ? 'DecoratorBlock' : 'BlockStatement',
path: openBlock.path,
params: openBlock.params,
hash: openBlock.hash,
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index b8fc976..ede0b5e 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -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;
}
}
};
diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js
index cf7aa48..66e7c7d 100644
--- a/lib/handlebars/compiler/printer.js
+++ b/lib/handlebars/compiler/printer.js
@@ -48,11 +48,15 @@ PrintVisitor.prototype.Program = function(program) {
PrintVisitor.prototype.MustacheStatement = function(mustache) {
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
};
+PrintVisitor.prototype.Decorator = function(mustache) {
+ return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
+};
-PrintVisitor.prototype.BlockStatement = function(block) {
+PrintVisitor.prototype.BlockStatement =
+PrintVisitor.prototype.DecoratorBlock = function(block) {
let out = '';
- out += this.pad('BLOCK:');
+ out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
this.padding++;
out += this.pad(this.SubExpression(block));
if (block.program) {
diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js
index cc54c53..2c504d1 100644
--- a/lib/handlebars/compiler/visitor.js
+++ b/lib/handlebars/compiler/visitor.js
@@ -76,8 +76,10 @@ Visitor.prototype = {
},
MustacheStatement: visitSubExpression,
+ Decorator: visitSubExpression,
BlockStatement: visitBlock,
+ DecoratorBlock: visitBlock,
PartialStatement: visitPartial,
PartialBlockStatement: function(partial) {
diff --git a/lib/handlebars/compiler/whitespace-control.js b/lib/handlebars/compiler/whitespace-control.js
index 6c8a986..e11483c 100644
--- a/lib/handlebars/compiler/whitespace-control.js
+++ b/lib/handlebars/compiler/whitespace-control.js
@@ -63,6 +63,7 @@ WhitespaceControl.prototype.Program = function(program) {
};
WhitespaceControl.prototype.BlockStatement =
+WhitespaceControl.prototype.DecoratorBlock =
WhitespaceControl.prototype.PartialBlockStatement = function(block) {
this.accept(block.program);
this.accept(block.inverse);
@@ -124,6 +125,7 @@ WhitespaceControl.prototype.PartialBlockStatement = function(block) {
return strip;
};
+WhitespaceControl.prototype.Decorator =
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
return mustache.strip;
};
diff --git a/lib/handlebars/decorators.js b/lib/handlebars/decorators.js
new file mode 100644
index 0000000..6f5a615
--- /dev/null
+++ b/lib/handlebars/decorators.js
@@ -0,0 +1,6 @@
+import registerInline from './decorators/inline';
+
+export function registerDefaultDecorators(instance) {
+ registerInline(instance);
+}
+
diff --git a/lib/handlebars/decorators/inline.js b/lib/handlebars/decorators/inline.js
new file mode 100644
index 0000000..2142466
--- /dev/null
+++ b/lib/handlebars/decorators/inline.js
@@ -0,0 +1,22 @@
+import {extend} from '../utils';
+
+export default function(instance) {
+ instance.registerDecorator('inline', function(fn, props, container, options) {
+ let ret = fn;
+ if (!props.partials) {
+ props.partials = {};
+ ret = function(context, options) {
+ // Create a new partials stack frame prior to exec.
+ let original = container.partials;
+ container.partials = extend({}, original, props.partials);
+ let ret = fn(context, options);
+ container.partials = original;
+ return ret;
+ };
+ }
+
+ props.partials[options.args[0]] = options.fn;
+
+ return ret;
+ });
+}
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index de42752..6b31a7b 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -29,6 +29,8 @@ export function template(templateSpec, env) {
throw new Exception('Unknown template object: ' + typeof templateSpec);
}
+ templateSpec.main.decorator = templateSpec.main_d;
+
// 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.
env.VM.checkRevision(templateSpec.compiler);
@@ -90,7 +92,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 +146,11 @@ 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);
+ }
+ main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
+ return main(context, options);
}
ret.isTop = true;
@@ -153,9 +161,13 @@ export function template(templateSpec, env) {
if (templateSpec.usePartial) {
container.partials = container.merge(options.partials, env.partials);
}
+ if (templateSpec.useDecorators) {
+ container.decorators = container.merge(options.decorators, env.decorators);
+ }
} else {
container.helpers = options.helpers;
container.partials = options.partials;
+ container.decorators = options.decorators;
}
};
@@ -186,6 +198,9 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
blockParams && [options.blockParams].concat(blockParams),
currentDepths);
}
+
+ prog = executeDecorators(fn, prog, container, depths, data, blockParams);
+
prog.program = i;
prog.depth = depths ? depths.length : 0;
prog.blockParams = declaredBlockParams || 0;
@@ -216,6 +231,10 @@ export function invokePartial(partial, context, options) {
let partialBlock;
if (options.fn && options.fn !== noop) {
partialBlock = options.data['partial-block'] = options.fn;
+
+ if (partialBlock.partials) {
+ options.partials = Utils.extend({}, options.partials, partialBlock.partials);
+ }
}
if (partial === undefined && partialBlock) {
@@ -238,3 +257,12 @@ function initData(context, data) {
}
return data;
}
+
+function executeDecorators(fn, prog, container, depths, data, blockParams) {
+ if (fn.decorator) {
+ let props = {};
+ prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
+ Utils.extend(prog, props);
+ }
+ return prog;
+}