import * as Utils from './utils'; import Exception from './exception'; import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base'; export function checkRevision(compilerInfo) { const compilerRevision = compilerInfo && compilerInfo[0] || 1, currentRevision = COMPILER_REVISION; if (compilerRevision !== currentRevision) { if (compilerRevision < currentRevision) { const 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] + ').'); } } } 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); } 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); function invokePartialWrapper(partial, context, options) { if (options.hash) { context = Utils.extend({}, context, options.hash); if (options.ids) { options.ids[0] = true; } } partial = env.VM.resolvePartial.call(this, partial, context, options); let 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) { let lines = result.split('\n'); for (let 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 let container = { strict: function(obj, name) { if (!(name in obj)) { throw new Exception('"' + name + '" not defined in ' + obj); } return obj[name]; }, lookup: function(depths, name) { const len = depths.length; for (let 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) { let ret = templateSpec[i]; ret.decorator = templateSpec[i + '_d']; return ret; }, programs: [], program: function(i, data, declaredBlockParams, blockParams, depths) { let programWrapper = this.programs[i], fn = this.fn(i); if (data || depths || blockParams || declaredBlockParams) { programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); } else if (!programWrapper) { programWrapper = this.programs[i] = wrapProgram(this, i, fn); } return programWrapper; }, data: function(value, depth) { while (value && depth--) { value = value._parent; } return value; }, merge: function(param, common) { let obj = param || common; if (param && common && (param !== common)) { obj = Utils.extend({}, common, param); } return obj; }, noop: env.VM.noop, compilerInfo: templateSpec.compiler }; function ret(context, options = {}) { let data = options.data; ret._setup(options); if (!options.partial && templateSpec.useData) { data = initData(context, data); } let depths, blockParams = templateSpec.useBlockParams ? [] : undefined; if (templateSpec.useDepths) { if (options.depths) { depths = context !== options.depths[0] ? [context].concat(options.depths) : options.depths; } else { depths = [context]; } } 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; 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); } if (templateSpec.usePartial || templateSpec.useDecorators) { container.decorators = container.merge(options.decorators, env.decorators); } } else { container.helpers = options.helpers; container.partials = options.partials; container.decorators = options.decorators; } }; 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 wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); }; return ret; } export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { function prog(context, options = {}) { let currentDepths = depths; if (depths && context !== depths[0]) { currentDepths = [context].concat(depths); } return fn(container, context, container.helpers, container.partials, options.data || data, 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; return prog; } export function resolvePartial(partial, context, options) { if (!partial) { if (options.name === '@partial-block') { partial = options.data['partial-block']; } else { partial = options.partials[options.name]; } } else if (!partial.call && !options.name) { // This is a dynamic partial that returned a string options.name = partial; partial = options.partials[partial]; } return partial; } export function invokePartial(partial, context, options) { options.partial = true; if (options.ids) { options.data.contextPath = options.ids[0] || options.data.contextPath; } let partialBlock; if (options.fn && options.fn !== noop) { options.data = createFrame(options.data); partialBlock = options.data['partial-block'] = options.fn; if (partialBlock.partials) { options.partials = Utils.extend({}, options.partials, partialBlock.partials); } } if (partial === undefined && partialBlock) { partial = partialBlock; } 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; } 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; }