UNPKG

nunjucks

Version:

A powerful templating engine with inheritance, asynchronous control, and more (jinja2 inspired)

1,027 lines (1,006 loc) 37.7 kB
'use strict'; function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } var parser = require('./parser'); var transformer = require('./transformer'); var nodes = require('./nodes'); var _require = require('./lib'), TemplateError = _require.TemplateError; var _require2 = require('./runtime'), Frame = _require2.Frame; var _require3 = require('./object'), Obj = _require3.Obj; // These are all the same for now, but shouldn't be passed straight // through var compareOps = { '==': '==', '===': '===', '!=': '!=', '!==': '!==', '<': '<', '>': '>', '<=': '<=', '>=': '>=' }; var Compiler = /*#__PURE__*/function (_Obj) { _inheritsLoose(Compiler, _Obj); function Compiler() { return _Obj.apply(this, arguments) || this; } var _proto = Compiler.prototype; _proto.init = function init(templateName, throwOnUndefined) { this.templateName = templateName; this.codebuf = []; this.lastId = 0; this.buffer = null; this.bufferStack = []; this._scopeClosers = ''; this.inBlock = false; this.throwOnUndefined = throwOnUndefined; }; _proto.fail = function fail(msg, lineno, colno) { if (lineno !== undefined) { lineno += 1; } if (colno !== undefined) { colno += 1; } throw new TemplateError(msg, lineno, colno); }; _proto._pushBuffer = function _pushBuffer() { var id = this._tmpid(); this.bufferStack.push(this.buffer); this.buffer = id; this._emit("var " + this.buffer + " = \"\";"); return id; }; _proto._popBuffer = function _popBuffer() { this.buffer = this.bufferStack.pop(); }; _proto._emit = function _emit(code) { this.codebuf.push(code); }; _proto._emitLine = function _emitLine(code) { this._emit(code + '\n'); }; _proto._emitLines = function _emitLines() { var _this = this; for (var _len = arguments.length, lines = new Array(_len), _key = 0; _key < _len; _key++) { lines[_key] = arguments[_key]; } lines.forEach(function (line) { return _this._emitLine(line); }); }; _proto._emitFuncBegin = function _emitFuncBegin(node, name) { this.buffer = 'output'; this._scopeClosers = ''; this._emitLine("function " + name + "(env, context, frame, runtime, cb) {"); this._emitLine("var lineno = " + node.lineno + ";"); this._emitLine("var colno = " + node.colno + ";"); this._emitLine("var " + this.buffer + " = \"\";"); this._emitLine('try {'); }; _proto._emitFuncEnd = function _emitFuncEnd(noReturn) { if (!noReturn) { this._emitLine('cb(null, ' + this.buffer + ');'); } this._closeScopeLevels(); this._emitLine('} catch (e) {'); this._emitLine(' cb(runtime.handleError(e, lineno, colno));'); this._emitLine('}'); this._emitLine('}'); this.buffer = null; }; _proto._addScopeLevel = function _addScopeLevel() { this._scopeClosers += '})'; }; _proto._closeScopeLevels = function _closeScopeLevels() { this._emitLine(this._scopeClosers + ';'); this._scopeClosers = ''; }; _proto._withScopedSyntax = function _withScopedSyntax(func) { var _scopeClosers = this._scopeClosers; this._scopeClosers = ''; func.call(this); this._closeScopeLevels(); this._scopeClosers = _scopeClosers; }; _proto._makeCallback = function _makeCallback(res) { var err = this._tmpid(); return 'function(' + err + (res ? ',' + res : '') + ') {\n' + 'if(' + err + ') { cb(' + err + '); return; }'; }; _proto._tmpid = function _tmpid() { this.lastId++; return 't_' + this.lastId; }; _proto._templateName = function _templateName() { return this.templateName == null ? 'undefined' : JSON.stringify(this.templateName); }; _proto._compileChildren = function _compileChildren(node, frame) { var _this2 = this; node.children.forEach(function (child) { _this2.compile(child, frame); }); }; _proto._compileAggregate = function _compileAggregate(node, frame, startChar, endChar) { var _this3 = this; if (startChar) { this._emit(startChar); } node.children.forEach(function (child, i) { if (i > 0) { _this3._emit(','); } _this3.compile(child, frame); }); if (endChar) { this._emit(endChar); } }; _proto._compileExpression = function _compileExpression(node, frame) { // TODO: I'm not really sure if this type check is worth it or // not. this.assertType(node, nodes.Literal, nodes.Symbol, nodes.Group, nodes.Array, nodes.Dict, nodes.FunCall, nodes.Caller, nodes.Filter, nodes.LookupVal, nodes.Compare, nodes.InlineIf, nodes.In, nodes.Is, nodes.And, nodes.Or, nodes.Not, nodes.Add, nodes.Concat, nodes.Sub, nodes.Mul, nodes.Div, nodes.FloorDiv, nodes.Mod, nodes.Pow, nodes.Neg, nodes.Pos, nodes.Compare, nodes.NodeList); this.compile(node, frame); }; _proto.assertType = function assertType(node) { for (var _len2 = arguments.length, types = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { types[_key2 - 1] = arguments[_key2]; } if (!types.some(function (t) { return node instanceof t; })) { this.fail("assertType: invalid type: " + node.typename, node.lineno, node.colno); } }; _proto.compileCallExtension = function compileCallExtension(node, frame, async) { var _this4 = this; var args = node.args; var contentArgs = node.contentArgs; var autoescape = typeof node.autoescape === 'boolean' ? node.autoescape : true; if (!async) { this._emit(this.buffer + " += runtime.suppressValue("); } this._emit("env.getExtension(\"" + node.extName + "\")[\"" + node.prop + "\"]("); this._emit('context'); if (args || contentArgs) { this._emit(','); } if (args) { if (!(args instanceof nodes.NodeList)) { this.fail('compileCallExtension: arguments must be a NodeList, ' + 'use `parser.parseSignature`'); } args.children.forEach(function (arg, i) { // Tag arguments are passed normally to the call. Note // that keyword arguments are turned into a single js // object as the last argument, if they exist. _this4._compileExpression(arg, frame); if (i !== args.children.length - 1 || contentArgs.length) { _this4._emit(','); } }); } if (contentArgs.length) { contentArgs.forEach(function (arg, i) { if (i > 0) { _this4._emit(','); } if (arg) { _this4._emitLine('function(cb) {'); _this4._emitLine('if(!cb) { cb = function(err) { if(err) { throw err; }}}'); var id = _this4._pushBuffer(); _this4._withScopedSyntax(function () { _this4.compile(arg, frame); _this4._emitLine("cb(null, " + id + ");"); }); _this4._popBuffer(); _this4._emitLine("return " + id + ";"); _this4._emitLine('}'); } else { _this4._emit('null'); } }); } if (async) { var res = this._tmpid(); this._emitLine(', ' + this._makeCallback(res)); this._emitLine(this.buffer + " += runtime.suppressValue(" + res + ", " + autoescape + " && env.opts.autoescape);"); this._addScopeLevel(); } else { this._emit(')'); this._emit(", " + autoescape + " && env.opts.autoescape);\n"); } }; _proto.compileCallExtensionAsync = function compileCallExtensionAsync(node, frame) { this.compileCallExtension(node, frame, true); }; _proto.compileNodeList = function compileNodeList(node, frame) { this._compileChildren(node, frame); }; _proto.compileLiteral = function compileLiteral(node) { if (typeof node.value === 'string') { var val = node.value.replace(/\\/g, '\\\\'); val = val.replace(/"/g, '\\"'); val = val.replace(/\n/g, '\\n'); val = val.replace(/\r/g, '\\r'); val = val.replace(/\t/g, '\\t'); val = val.replace(/\u2028/g, "\\u2028"); this._emit("\"" + val + "\""); } else if (node.value === null) { this._emit('null'); } else { this._emit(node.value.toString()); } }; _proto.compileSymbol = function compileSymbol(node, frame) { var name = node.value; var v = frame.lookup(name); if (v) { this._emit(v); } else { this._emit('runtime.contextOrFrameLookup(' + 'context, frame, "' + name + '")'); } }; _proto.compileGroup = function compileGroup(node, frame) { this._compileAggregate(node, frame, '(', ')'); }; _proto.compileArray = function compileArray(node, frame) { this._compileAggregate(node, frame, '[', ']'); }; _proto.compileDict = function compileDict(node, frame) { this._compileAggregate(node, frame, '{', '}'); }; _proto.compilePair = function compilePair(node, frame) { var key = node.key; var val = node.value; if (key instanceof nodes.Symbol) { key = new nodes.Literal(key.lineno, key.colno, key.value); } else if (!(key instanceof nodes.Literal && typeof key.value === 'string')) { this.fail('compilePair: Dict keys must be strings or names', key.lineno, key.colno); } this.compile(key, frame); this._emit(': '); this._compileExpression(val, frame); }; _proto.compileInlineIf = function compileInlineIf(node, frame) { this._emit('('); this.compile(node.cond, frame); this._emit('?'); this.compile(node.body, frame); this._emit(':'); if (node.else_ !== null) { this.compile(node.else_, frame); } else { this._emit('""'); } this._emit(')'); }; _proto.compileIn = function compileIn(node, frame) { this._emit('runtime.inOperator('); this.compile(node.left, frame); this._emit(','); this.compile(node.right, frame); this._emit(')'); }; _proto.compileIs = function compileIs(node, frame) { // first, we need to try to get the name of the test function, if it's a // callable (i.e., has args) and not a symbol. var right = node.right.name ? node.right.name.value // otherwise go with the symbol value : node.right.value; this._emit('env.getTest("' + right + '").call(context, '); this.compile(node.left, frame); // compile the arguments for the callable if they exist if (node.right.args) { this._emit(','); this.compile(node.right.args, frame); } this._emit(') === true'); }; _proto._binOpEmitter = function _binOpEmitter(node, frame, str) { this.compile(node.left, frame); this._emit(str); this.compile(node.right, frame); } // ensure concatenation instead of addition // by adding empty string in between ; _proto.compileOr = function compileOr(node, frame) { return this._binOpEmitter(node, frame, ' || '); }; _proto.compileAnd = function compileAnd(node, frame) { return this._binOpEmitter(node, frame, ' && '); }; _proto.compileAdd = function compileAdd(node, frame) { return this._binOpEmitter(node, frame, ' + '); }; _proto.compileConcat = function compileConcat(node, frame) { return this._binOpEmitter(node, frame, ' + "" + '); }; _proto.compileSub = function compileSub(node, frame) { return this._binOpEmitter(node, frame, ' - '); }; _proto.compileMul = function compileMul(node, frame) { return this._binOpEmitter(node, frame, ' * '); }; _proto.compileDiv = function compileDiv(node, frame) { return this._binOpEmitter(node, frame, ' / '); }; _proto.compileMod = function compileMod(node, frame) { return this._binOpEmitter(node, frame, ' % '); }; _proto.compileNot = function compileNot(node, frame) { this._emit('!'); this.compile(node.target, frame); }; _proto.compileFloorDiv = function compileFloorDiv(node, frame) { this._emit('Math.floor('); this.compile(node.left, frame); this._emit(' / '); this.compile(node.right, frame); this._emit(')'); }; _proto.compilePow = function compilePow(node, frame) { this._emit('Math.pow('); this.compile(node.left, frame); this._emit(', '); this.compile(node.right, frame); this._emit(')'); }; _proto.compileNeg = function compileNeg(node, frame) { this._emit('-'); this.compile(node.target, frame); }; _proto.compilePos = function compilePos(node, frame) { this._emit('+'); this.compile(node.target, frame); }; _proto.compileCompare = function compileCompare(node, frame) { var _this5 = this; this.compile(node.expr, frame); node.ops.forEach(function (op) { _this5._emit(" " + compareOps[op.type] + " "); _this5.compile(op.expr, frame); }); }; _proto.compileLookupVal = function compileLookupVal(node, frame) { this._emit('runtime.memberLookup(('); this._compileExpression(node.target, frame); this._emit('),'); this._compileExpression(node.val, frame); this._emit(')'); }; _proto._getNodeName = function _getNodeName(node) { switch (node.typename) { case 'Symbol': return node.value; case 'FunCall': return 'the return value of (' + this._getNodeName(node.name) + ')'; case 'LookupVal': return this._getNodeName(node.target) + '["' + this._getNodeName(node.val) + '"]'; case 'Literal': return node.value.toString(); default: return '--expression--'; } }; _proto.compileFunCall = function compileFunCall(node, frame) { // Keep track of line/col info at runtime by settings // variables within an expression. An expression in javascript // like (x, y, z) returns the last value, and x and y can be // anything this._emit('(lineno = ' + node.lineno + ', colno = ' + node.colno + ', '); this._emit('runtime.callWrap('); // Compile it as normal. this._compileExpression(node.name, frame); // Output the name of what we're calling so we can get friendly errors // if the lookup fails. this._emit(', "' + this._getNodeName(node.name).replace(/"/g, '\\"') + '", context, '); this._compileAggregate(node.args, frame, '[', '])'); this._emit(')'); }; _proto.compileFilter = function compileFilter(node, frame) { var name = node.name; this.assertType(name, nodes.Symbol); this._emit('env.getFilter("' + name.value + '").call(context, '); this._compileAggregate(node.args, frame); this._emit(')'); }; _proto.compileFilterAsync = function compileFilterAsync(node, frame) { var name = node.name; var symbol = node.symbol.value; this.assertType(name, nodes.Symbol); frame.set(symbol, symbol); this._emit('env.getFilter("' + name.value + '").call(context, '); this._compileAggregate(node.args, frame); this._emitLine(', ' + this._makeCallback(symbol)); this._addScopeLevel(); }; _proto.compileKeywordArgs = function compileKeywordArgs(node, frame) { this._emit('runtime.makeKeywordArgs('); this.compileDict(node, frame); this._emit(')'); }; _proto.compileSet = function compileSet(node, frame) { var _this6 = this; var ids = []; // Lookup the variable names for each identifier and create // new ones if necessary node.targets.forEach(function (target) { var name = target.value; var id = frame.lookup(name); if (id === null || id === undefined) { id = _this6._tmpid(); // Note: This relies on js allowing scope across // blocks, in case this is created inside an `if` _this6._emitLine('var ' + id + ';'); } ids.push(id); }); if (node.value) { this._emit(ids.join(' = ') + ' = '); this._compileExpression(node.value, frame); this._emitLine(';'); } else { this._emit(ids.join(' = ') + ' = '); this.compile(node.body, frame); this._emitLine(';'); } node.targets.forEach(function (target, i) { var id = ids[i]; var name = target.value; // We are running this for every var, but it's very // uncommon to assign to multiple vars anyway _this6._emitLine("frame.set(\"" + name + "\", " + id + ", true);"); _this6._emitLine('if(frame.topLevel) {'); _this6._emitLine("context.setVariable(\"" + name + "\", " + id + ");"); _this6._emitLine('}'); if (name.charAt(0) !== '_') { _this6._emitLine('if(frame.topLevel) {'); _this6._emitLine("context.addExport(\"" + name + "\", " + id + ");"); _this6._emitLine('}'); } }); }; _proto.compileSwitch = function compileSwitch(node, frame) { var _this7 = this; this._emit('switch ('); this.compile(node.expr, frame); this._emit(') {'); node.cases.forEach(function (c, i) { _this7._emit('case '); _this7.compile(c.cond, frame); _this7._emit(': '); _this7.compile(c.body, frame); // preserve fall-throughs if (c.body.children.length) { _this7._emitLine('break;'); } }); if (node.default) { this._emit('default:'); this.compile(node.default, frame); } this._emit('}'); }; _proto.compileIf = function compileIf(node, frame, async) { var _this8 = this; this._emit('if('); this._compileExpression(node.cond, frame); this._emitLine(') {'); this._withScopedSyntax(function () { _this8.compile(node.body, frame); if (async) { _this8._emit('cb()'); } }); if (node.else_) { this._emitLine('}\nelse {'); this._withScopedSyntax(function () { _this8.compile(node.else_, frame); if (async) { _this8._emit('cb()'); } }); } else if (async) { this._emitLine('}\nelse {'); this._emit('cb()'); } this._emitLine('}'); }; _proto.compileIfAsync = function compileIfAsync(node, frame) { this._emit('(function(cb) {'); this.compileIf(node, frame, true); this._emit('})(' + this._makeCallback()); this._addScopeLevel(); }; _proto._emitLoopBindings = function _emitLoopBindings(node, arr, i, len) { var _this9 = this; var bindings = [{ name: 'index', val: i + " + 1" }, { name: 'index0', val: i }, { name: 'revindex', val: len + " - " + i }, { name: 'revindex0', val: len + " - " + i + " - 1" }, { name: 'first', val: i + " === 0" }, { name: 'last', val: i + " === " + len + " - 1" }, { name: 'length', val: len }]; bindings.forEach(function (b) { _this9._emitLine("frame.set(\"loop." + b.name + "\", " + b.val + ");"); }); }; _proto.compileFor = function compileFor(node, frame) { var _this10 = this; // Some of this code is ugly, but it keeps the generated code // as fast as possible. ForAsync also shares some of this, but // not much. var i = this._tmpid(); var len = this._tmpid(); var arr = this._tmpid(); frame = frame.push(); this._emitLine('frame = frame.push();'); this._emit("var " + arr + " = "); this._compileExpression(node.arr, frame); this._emitLine(';'); this._emit("if(" + arr + ") {"); this._emitLine(arr + ' = runtime.fromIterator(' + arr + ');'); // If multiple names are passed, we need to bind them // appropriately if (node.name instanceof nodes.Array) { this._emitLine("var " + i + ";"); // The object could be an arroy or object. Note that the // body of the loop is duplicated for each condition, but // we are optimizing for speed over size. this._emitLine("if(runtime.isArray(" + arr + ")) {"); this._emitLine("var " + len + " = " + arr + ".length;"); this._emitLine("for(" + i + "=0; " + i + " < " + arr + ".length; " + i + "++) {"); // Bind each declared var node.name.children.forEach(function (child, u) { var tid = _this10._tmpid(); _this10._emitLine("var " + tid + " = " + arr + "[" + i + "][" + u + "];"); _this10._emitLine("frame.set(\"" + child + "\", " + arr + "[" + i + "][" + u + "]);"); frame.set(node.name.children[u].value, tid); }); this._emitLoopBindings(node, arr, i, len); this._withScopedSyntax(function () { _this10.compile(node.body, frame); }); this._emitLine('}'); this._emitLine('} else {'); // Iterate over the key/values of an object var _node$name$children = node.name.children, key = _node$name$children[0], val = _node$name$children[1]; var k = this._tmpid(); var v = this._tmpid(); frame.set(key.value, k); frame.set(val.value, v); this._emitLine(i + " = -1;"); this._emitLine("var " + len + " = runtime.keys(" + arr + ").length;"); this._emitLine("for(var " + k + " in " + arr + ") {"); this._emitLine(i + "++;"); this._emitLine("var " + v + " = " + arr + "[" + k + "];"); this._emitLine("frame.set(\"" + key.value + "\", " + k + ");"); this._emitLine("frame.set(\"" + val.value + "\", " + v + ");"); this._emitLoopBindings(node, arr, i, len); this._withScopedSyntax(function () { _this10.compile(node.body, frame); }); this._emitLine('}'); this._emitLine('}'); } else { // Generate a typical array iteration var _v = this._tmpid(); frame.set(node.name.value, _v); this._emitLine("var " + len + " = " + arr + ".length;"); this._emitLine("for(var " + i + "=0; " + i + " < " + arr + ".length; " + i + "++) {"); this._emitLine("var " + _v + " = " + arr + "[" + i + "];"); this._emitLine("frame.set(\"" + node.name.value + "\", " + _v + ");"); this._emitLoopBindings(node, arr, i, len); this._withScopedSyntax(function () { _this10.compile(node.body, frame); }); this._emitLine('}'); } this._emitLine('}'); if (node.else_) { this._emitLine('if (!' + len + ') {'); this.compile(node.else_, frame); this._emitLine('}'); } this._emitLine('frame = frame.pop();'); }; _proto._compileAsyncLoop = function _compileAsyncLoop(node, frame, parallel) { var _this11 = this; // This shares some code with the For tag, but not enough to // worry about. This iterates across an object asynchronously, // but not in parallel. var i = this._tmpid(); var len = this._tmpid(); var arr = this._tmpid(); var asyncMethod = parallel ? 'asyncAll' : 'asyncEach'; frame = frame.push(); this._emitLine('frame = frame.push();'); this._emit('var ' + arr + ' = runtime.fromIterator('); this._compileExpression(node.arr, frame); this._emitLine(');'); if (node.name instanceof nodes.Array) { var arrayLen = node.name.children.length; this._emit("runtime." + asyncMethod + "(" + arr + ", " + arrayLen + ", function("); node.name.children.forEach(function (name) { _this11._emit(name.value + ","); }); this._emit(i + ',' + len + ',next) {'); node.name.children.forEach(function (name) { var id = name.value; frame.set(id, id); _this11._emitLine("frame.set(\"" + id + "\", " + id + ");"); }); } else { var id = node.name.value; this._emitLine("runtime." + asyncMethod + "(" + arr + ", 1, function(" + id + ", " + i + ", " + len + ",next) {"); this._emitLine('frame.set("' + id + '", ' + id + ');'); frame.set(id, id); } this._emitLoopBindings(node, arr, i, len); this._withScopedSyntax(function () { var buf; if (parallel) { buf = _this11._pushBuffer(); } _this11.compile(node.body, frame); _this11._emitLine('next(' + i + (buf ? ',' + buf : '') + ');'); if (parallel) { _this11._popBuffer(); } }); var output = this._tmpid(); this._emitLine('}, ' + this._makeCallback(output)); this._addScopeLevel(); if (parallel) { this._emitLine(this.buffer + ' += ' + output + ';'); } if (node.else_) { this._emitLine('if (!' + arr + '.length) {'); this.compile(node.else_, frame); this._emitLine('}'); } this._emitLine('frame = frame.pop();'); }; _proto.compileAsyncEach = function compileAsyncEach(node, frame) { this._compileAsyncLoop(node, frame); }; _proto.compileAsyncAll = function compileAsyncAll(node, frame) { this._compileAsyncLoop(node, frame, true); }; _proto._compileMacro = function _compileMacro(node, frame) { var _this12 = this; var args = []; var kwargs = null; var funcId = 'macro_' + this._tmpid(); var keepFrame = frame !== undefined; // Type check the definition of the args node.args.children.forEach(function (arg, i) { if (i === node.args.children.length - 1 && arg instanceof nodes.Dict) { kwargs = arg; } else { _this12.assertType(arg, nodes.Symbol); args.push(arg); } }); var realNames = [].concat(args.map(function (n) { return "l_" + n.value; }), ['kwargs']); // Quoted argument names var argNames = args.map(function (n) { return "\"" + n.value + "\""; }); var kwargNames = (kwargs && kwargs.children || []).map(function (n) { return "\"" + n.key.value + "\""; }); // We pass a function to makeMacro which destructures the // arguments so support setting positional args with keywords // args and passing keyword args as positional args // (essentially default values). See runtime.js. var currFrame; if (keepFrame) { currFrame = frame.push(true); } else { currFrame = new Frame(); } this._emitLines("var " + funcId + " = runtime.makeMacro(", "[" + argNames.join(', ') + "], ", "[" + kwargNames.join(', ') + "], ", "function (" + realNames.join(', ') + ") {", 'var callerFrame = frame;', 'frame = ' + (keepFrame ? 'frame.push(true);' : 'new runtime.Frame();'), 'kwargs = kwargs || {};', 'if (Object.prototype.hasOwnProperty.call(kwargs, "caller")) {', 'frame.set("caller", kwargs.caller); }'); // Expose the arguments to the template. Don't need to use // random names because the function // will create a new run-time scope for us args.forEach(function (arg) { _this12._emitLine("frame.set(\"" + arg.value + "\", l_" + arg.value + ");"); currFrame.set(arg.value, "l_" + arg.value); }); // Expose the keyword arguments if (kwargs) { kwargs.children.forEach(function (pair) { var name = pair.key.value; _this12._emit("frame.set(\"" + name + "\", "); _this12._emit("Object.prototype.hasOwnProperty.call(kwargs, \"" + name + "\")"); _this12._emit(" ? kwargs[\"" + name + "\"] : "); _this12._compileExpression(pair.value, currFrame); _this12._emit(');'); }); } var bufferId = this._pushBuffer(); this._withScopedSyntax(function () { _this12.compile(node.body, currFrame); }); this._emitLine('frame = ' + (keepFrame ? 'frame.pop();' : 'callerFrame;')); this._emitLine("return new runtime.SafeString(" + bufferId + ");"); this._emitLine('});'); this._popBuffer(); return funcId; }; _proto.compileMacro = function compileMacro(node, frame) { var funcId = this._compileMacro(node); // Expose the macro to the templates var name = node.name.value; frame.set(name, funcId); if (frame.parent) { this._emitLine("frame.set(\"" + name + "\", " + funcId + ");"); } else { if (node.name.value.charAt(0) !== '_') { this._emitLine("context.addExport(\"" + name + "\");"); } this._emitLine("context.setVariable(\"" + name + "\", " + funcId + ");"); } }; _proto.compileCaller = function compileCaller(node, frame) { // basically an anonymous "macro expression" this._emit('(function (){'); var funcId = this._compileMacro(node, frame); this._emit("return " + funcId + ";})()"); }; _proto._compileGetTemplate = function _compileGetTemplate(node, frame, eagerCompile, ignoreMissing) { var parentTemplateId = this._tmpid(); var parentName = this._templateName(); var cb = this._makeCallback(parentTemplateId); var eagerCompileArg = eagerCompile ? 'true' : 'false'; var ignoreMissingArg = ignoreMissing ? 'true' : 'false'; this._emit('env.getTemplate('); this._compileExpression(node.template, frame); this._emitLine(", " + eagerCompileArg + ", " + parentName + ", " + ignoreMissingArg + ", " + cb); return parentTemplateId; }; _proto.compileImport = function compileImport(node, frame) { var target = node.target.value; var id = this._compileGetTemplate(node, frame, false, false); this._addScopeLevel(); this._emitLine(id + '.getExported(' + (node.withContext ? 'context.getVariables(), frame, ' : '') + this._makeCallback(id)); this._addScopeLevel(); frame.set(target, id); if (frame.parent) { this._emitLine("frame.set(\"" + target + "\", " + id + ");"); } else { this._emitLine("context.setVariable(\"" + target + "\", " + id + ");"); } }; _proto.compileFromImport = function compileFromImport(node, frame) { var _this13 = this; var importedId = this._compileGetTemplate(node, frame, false, false); this._addScopeLevel(); this._emitLine(importedId + '.getExported(' + (node.withContext ? 'context.getVariables(), frame, ' : '') + this._makeCallback(importedId)); this._addScopeLevel(); node.names.children.forEach(function (nameNode) { var name; var alias; var id = _this13._tmpid(); if (nameNode instanceof nodes.Pair) { name = nameNode.key.value; alias = nameNode.value.value; } else { name = nameNode.value; alias = name; } _this13._emitLine("if(Object.prototype.hasOwnProperty.call(" + importedId + ", \"" + name + "\")) {"); _this13._emitLine("var " + id + " = " + importedId + "." + name + ";"); _this13._emitLine('} else {'); _this13._emitLine("cb(new Error(\"cannot import '" + name + "'\")); return;"); _this13._emitLine('}'); frame.set(alias, id); if (frame.parent) { _this13._emitLine("frame.set(\"" + alias + "\", " + id + ");"); } else { _this13._emitLine("context.setVariable(\"" + alias + "\", " + id + ");"); } }); }; _proto.compileBlock = function compileBlock(node) { var id = this._tmpid(); // If we are executing outside a block (creating a top-level // block), we really don't want to execute its code because it // will execute twice: once when the child template runs and // again when the parent template runs. Note that blocks // within blocks will *always* execute immediately *and* // wherever else they are invoked (like used in a parent // template). This may have behavioral differences from jinja // because blocks can have side effects, but it seems like a // waste of performance to always execute huge top-level // blocks twice if (!this.inBlock) { this._emit('(parentTemplate ? function(e, c, f, r, cb) { cb(""); } : '); } this._emit("context.getBlock(\"" + node.name.value + "\")"); if (!this.inBlock) { this._emit(')'); } this._emitLine('(env, context, frame, runtime, ' + this._makeCallback(id)); this._emitLine(this.buffer + " += " + id + ";"); this._addScopeLevel(); }; _proto.compileSuper = function compileSuper(node, frame) { var name = node.blockName.value; var id = node.symbol.value; var cb = this._makeCallback(id); this._emitLine("context.getSuper(env, \"" + name + "\", b_" + name + ", frame, runtime, " + cb); this._emitLine(id + " = runtime.markSafe(" + id + ");"); this._addScopeLevel(); frame.set(id, id); }; _proto.compileExtends = function compileExtends(node, frame) { var k = this._tmpid(); var parentTemplateId = this._compileGetTemplate(node, frame, true, false); // extends is a dynamic tag and can occur within a block like // `if`, so if this happens we need to capture the parent // template in the top-level scope this._emitLine("parentTemplate = " + parentTemplateId); this._emitLine("for(var " + k + " in parentTemplate.blocks) {"); this._emitLine("context.addBlock(" + k + ", parentTemplate.blocks[" + k + "]);"); this._emitLine('}'); this._addScopeLevel(); }; _proto.compileInclude = function compileInclude(node, frame) { this._emitLine('var tasks = [];'); this._emitLine('tasks.push('); this._emitLine('function(callback) {'); var id = this._compileGetTemplate(node, frame, false, node.ignoreMissing); this._emitLine("callback(null," + id + ");});"); this._emitLine('});'); var id2 = this._tmpid(); this._emitLine('tasks.push('); this._emitLine('function(template, callback){'); this._emitLine('template.render(context.getVariables(), frame, ' + this._makeCallback(id2)); this._emitLine('callback(null,' + id2 + ');});'); this._emitLine('});'); this._emitLine('tasks.push('); this._emitLine('function(result, callback){'); this._emitLine(this.buffer + " += result;"); this._emitLine('callback(null);'); this._emitLine('});'); this._emitLine('env.waterfall(tasks, function(){'); this._addScopeLevel(); }; _proto.compileTemplateData = function compileTemplateData(node, frame) { this.compileLiteral(node, frame); }; _proto.compileCapture = function compileCapture(node, frame) { var _this14 = this; // we need to temporarily override the current buffer id as 'output' // so the set block writes to the capture output instead of the buffer var buffer = this.buffer; this.buffer = 'output'; this._emitLine('(function() {'); this._emitLine('var output = "";'); this._withScopedSyntax(function () { _this14.compile(node.body, frame); }); this._emitLine('return output;'); this._emitLine('})()'); // and of course, revert back to the old buffer id this.buffer = buffer; }; _proto.compileOutput = function compileOutput(node, frame) { var _this15 = this; var children = node.children; children.forEach(function (child) { // TemplateData is a special case because it is never // autoescaped, so simply output it for optimization if (child instanceof nodes.TemplateData) { if (child.value) { _this15._emit(_this15.buffer + " += "); _this15.compileLiteral(child, frame); _this15._emitLine(';'); } } else { _this15._emit(_this15.buffer + " += runtime.suppressValue("); if (_this15.throwOnUndefined) { _this15._emit('runtime.ensureDefined('); } _this15.compile(child, frame); if (_this15.throwOnUndefined) { _this15._emit("," + node.lineno + "," + node.colno + ")"); } _this15._emit(', env.opts.autoescape);\n'); } }); }; _proto.compileRoot = function compileRoot(node, frame) { var _this16 = this; if (frame) { this.fail('compileRoot: root node can\'t have frame'); } frame = new Frame(); this._emitFuncBegin(node, 'root'); this._emitLine('var parentTemplate = null;'); this._compileChildren(node, frame); this._emitLine('if(parentTemplate) {'); this._emitLine('parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);'); this._emitLine('} else {'); this._emitLine("cb(null, " + this.buffer + ");"); this._emitLine('}'); this._emitFuncEnd(true); this.inBlock = true; var blockNames = []; var blocks = node.findAll(nodes.Block); blocks.forEach(function (block, i) { var name = block.name.value; if (blockNames.indexOf(name) !== -1) { throw new Error("Block \"" + name + "\" defined more than once."); } blockNames.push(name); _this16._emitFuncBegin(block, "b_" + name); var tmpFrame = new Frame(); _this16._emitLine('var frame = frame.push(true);'); _this16.compile(block.body, tmpFrame); _this16._emitFuncEnd(); }); this._emitLine('return {'); blocks.forEach(function (block, i) { var blockName = "b_" + block.name.value; _this16._emitLine(blockName + ": " + blockName + ","); }); this._emitLine('root: root\n};'); }; _proto.compile = function compile(node, frame) { var _compile = this['compile' + node.typename]; if (_compile) { _compile.call(this, node, frame); } else { this.fail("compile: Cannot compile node: " + node.typename, node.lineno, node.colno); } }; _proto.getCode = function getCode() { return this.codebuf.join(''); }; return Compiler; }(Obj); module.exports = { compile: function compile(src, asyncFilters, extensions, name, opts) { if (opts === void 0) { opts = {}; } var c = new Compiler(name, opts.throwOnUndefined); // Run the extension preprocessors against the source. var preprocessors = (extensions || []).map(function (ext) { return ext.preprocess; }).filter(function (f) { return !!f; }); var processedSrc = preprocessors.reduce(function (s, processor) { return processor(s); }, src); c.compile(transformer.transform(parser.parse(processedSrc, extensions, opts), asyncFilters, name)); return c.getCode(); }, Compiler: Compiler };