6650957f79
Causes templates to throw when lookup fields are not defined within the context. Strict mode will throw when any field is omitted. assumeObjects mode maintains the existing behavior of outputting an empty response when fields are not defined but allows for performance/size optimizations by not doing safety checks for intermediate objects in the lookup chain. Strict mode effectively disables the helperMissing and inverse handling for blockHelperMissing as templates will throw rather than call those features for missing data fields. Fixes #651 Fixes #805
209 lines
6.4 KiB
JavaScript
209 lines
6.4 KiB
JavaScript
module Utils from "./utils";
|
|
import Exception from "./exception";
|
|
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from "./base";
|
|
|
|
export function checkRevision(compilerInfo) {
|
|
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
|
currentRevision = COMPILER_REVISION;
|
|
|
|
if (compilerRevision !== currentRevision) {
|
|
if (compilerRevision < currentRevision) {
|
|
var runtimeVersions = REVISION_CHANGES[currentRevision],
|
|
compilerVersions = REVISION_CHANGES[compilerRevision];
|
|
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
|
|
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
|
|
} else {
|
|
// Use the embedded version info since the runtime doesn't know about this revision yet
|
|
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
|
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Remove this line and break up compilePartial
|
|
|
|
export function template(templateSpec, env) {
|
|
/* istanbul ignore next */
|
|
if (!env) {
|
|
throw new Exception("No environment passed to template");
|
|
}
|
|
if (!templateSpec || !templateSpec.main) {
|
|
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
|
}
|
|
|
|
// 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);
|
|
|
|
var invokePartialWrapper = function(partial, context, options) {
|
|
if (options.hash) {
|
|
context = Utils.extend({}, context, options.hash);
|
|
}
|
|
if (!partial) {
|
|
partial = options.partials[options.name];
|
|
}
|
|
|
|
var result = env.VM.invokePartial.call(this, partial, context, options);
|
|
|
|
if (result == null && env.compile) {
|
|
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
|
|
result = options.partials[options.name](context, options);
|
|
}
|
|
if (result != null) {
|
|
if (options.indent) {
|
|
var lines = result.split('\n');
|
|
for (var i = 0, l = lines.length; i < l; i++) {
|
|
if (!lines[i] && i + 1 === l) {
|
|
break;
|
|
}
|
|
|
|
lines[i] = options.indent + lines[i];
|
|
}
|
|
result = lines.join('\n');
|
|
}
|
|
return result;
|
|
} else {
|
|
throw new Exception("The partial " + options.name + " could not be compiled when running in runtime-only mode");
|
|
}
|
|
};
|
|
|
|
// Just add water
|
|
var container = {
|
|
strict: function(obj, name) {
|
|
if (!(name in obj)) {
|
|
throw new Exception('"' + name + '" not defined in ' + obj);
|
|
}
|
|
return obj[name];
|
|
},
|
|
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;
|
|
},
|
|
|
|
escapeExpression: Utils.escapeExpression,
|
|
invokePartial: invokePartialWrapper,
|
|
|
|
fn: function(i) {
|
|
return templateSpec[i];
|
|
},
|
|
|
|
programs: [],
|
|
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
|
var programWrapper = this.programs[i],
|
|
fn = this.fn(i);
|
|
if (data || depths || blockParams || declaredBlockParams) {
|
|
programWrapper = program(this, i, fn, data, declaredBlockParams, blockParams, depths);
|
|
} else if (!programWrapper) {
|
|
programWrapper = this.programs[i] = program(this, i, fn);
|
|
}
|
|
return programWrapper;
|
|
},
|
|
|
|
data: function(data, depth) {
|
|
while (data && depth--) {
|
|
data = data._parent;
|
|
}
|
|
return data;
|
|
},
|
|
merge: function(param, common) {
|
|
var ret = param || common;
|
|
|
|
if (param && common && (param !== common)) {
|
|
ret = Utils.extend({}, common, param);
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
noop: env.VM.noop,
|
|
compilerInfo: templateSpec.compiler
|
|
};
|
|
|
|
var ret = function(context, options) {
|
|
options = options || {};
|
|
var data = options.data;
|
|
|
|
ret._setup(options);
|
|
if (!options.partial && templateSpec.useData) {
|
|
data = initData(context, data);
|
|
}
|
|
var depths,
|
|
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
|
if (templateSpec.useDepths) {
|
|
depths = options.depths ? [context].concat(options.depths) : [context];
|
|
}
|
|
|
|
return templateSpec.main.call(container, context, container.helpers, container.partials, data, blockParams, depths);
|
|
};
|
|
ret.isTop = true;
|
|
|
|
ret._setup = function(options) {
|
|
if (!options.partial) {
|
|
container.helpers = container.merge(options.helpers, env.helpers);
|
|
|
|
if (templateSpec.usePartial) {
|
|
container.partials = container.merge(options.partials, env.partials);
|
|
}
|
|
} else {
|
|
container.helpers = options.helpers;
|
|
container.partials = options.partials;
|
|
}
|
|
};
|
|
|
|
ret._child = function(i, data, blockParams, depths) {
|
|
if (templateSpec.useBlockParams && !blockParams) {
|
|
throw new Exception('must pass block params');
|
|
}
|
|
if (templateSpec.useDepths && !depths) {
|
|
throw new Exception('must pass parent depths');
|
|
}
|
|
|
|
return program(container, i, templateSpec[i], data, 0, blockParams, depths);
|
|
};
|
|
return ret;
|
|
}
|
|
|
|
export function program(container, i, fn, data, declaredBlockParams, blockParams, depths) {
|
|
var prog = function(context, options) {
|
|
options = options || {};
|
|
|
|
return fn.call(container,
|
|
context,
|
|
container.helpers, container.partials,
|
|
options.data || data,
|
|
blockParams && [options.blockParams].concat(blockParams),
|
|
depths && [context].concat(depths));
|
|
};
|
|
prog.program = i;
|
|
prog.depth = depths ? depths.length : 0;
|
|
prog.blockParams = declaredBlockParams || 0;
|
|
return prog;
|
|
}
|
|
|
|
export function invokePartial(partial, context, options) {
|
|
options.partial = true;
|
|
|
|
if(partial === undefined) {
|
|
throw new Exception("The partial " + options.name + " could not be found");
|
|
} else if(partial instanceof Function) {
|
|
return partial(context, options);
|
|
}
|
|
}
|
|
|
|
export function noop() { return ""; }
|
|
|
|
function initData(context, data) {
|
|
if (!data || !('root' in data)) {
|
|
data = data ? createFrame(data) : {};
|
|
data.root = context;
|
|
}
|
|
return data;
|
|
}
|