UNPKG

todomvc

Version:

> Helping you select an MV\* framework

1,858 lines (1,570 loc) 139 kB
// Browser bundle of nunjucks 1.0.0 (function() { var modules = {}; (function() { // A simple class system, more documentation to come function extend(cls, name, props) { // This does that same thing as Object.create, but with support for IE8 var F = function() {}; F.prototype = cls.prototype; var prototype = new F(); var fnTest = /xyz/.test(function(){ xyz; }) ? /\bparent\b/ : /.*/; props = props || {}; for(var k in props) { var src = props[k]; var parent = prototype[k]; if(typeof parent == "function" && typeof src == "function" && fnTest.test(src)) { prototype[k] = (function (src, parent) { return function() { // Save the current parent method var tmp = this.parent; // Set parent to the previous method, call, and restore this.parent = parent; var res = src.apply(this, arguments); this.parent = tmp; return res; }; })(src, parent); } else { prototype[k] = src; } } prototype.typename = name; var new_cls = function() { if(prototype.init) { prototype.init.apply(this, arguments); } }; new_cls.prototype = prototype; new_cls.prototype.constructor = new_cls; new_cls.extend = function(name, props) { if(typeof name == "object") { props = name; name = "anonymous"; } return extend(new_cls, name, props); }; return new_cls; } modules['object'] = extend(Object, "Object", {}); })(); (function() { var ArrayProto = Array.prototype; var ObjProto = Object.prototype; var escapeMap = { '&': '&amp;', '"': '&quot;', "'": '&#39;', "<": '&lt;', ">": '&gt;' }; var lookupEscape = function(ch) { return escapeMap[ch]; }; var exports = modules['lib'] = {}; exports.withPrettyErrors = function(path, withInternals, func) { try { return func(); } catch (e) { if (!e.Update) { // not one of ours, cast it e = new exports.TemplateError(e); } e.Update(path); // Unless they marked the dev flag, show them a trace from here if (!withInternals) { var old = e; e = new Error(old.message); e.name = old.name; } throw e; } }; exports.TemplateError = function(message, lineno, colno) { var err = this; if (message instanceof Error) { // for casting regular js errors err = message; message = message.name + ": " + message.message; } else { if(Error.captureStackTrace) { Error.captureStackTrace(err); } } err.name = 'Template render error'; err.message = message; err.lineno = lineno; err.colno = colno; err.firstUpdate = true; err.Update = function(path) { var message = "(" + (path || "unknown path") + ")"; // only show lineno + colno next to path of template // where error occurred if (this.firstUpdate) { if(this.lineno && this.colno) { message += ' [Line ' + this.lineno + ', Column ' + this.colno + ']'; } else if(this.lineno) { message += ' [Line ' + this.lineno + ']'; } } message += '\n '; if (this.firstUpdate) { message += ' '; } this.message = message + (this.message || ''); this.firstUpdate = false; return this; }; return err; }; exports.TemplateError.prototype = Error.prototype; exports.escape = function(val) { return val.replace(/[&"'<>]/g, lookupEscape); }; exports.isFunction = function(obj) { return ObjProto.toString.call(obj) == '[object Function]'; }; exports.isArray = Array.isArray || function(obj) { return ObjProto.toString.call(obj) == '[object Array]'; }; exports.isString = function(obj) { return ObjProto.toString.call(obj) == '[object String]'; }; exports.isObject = function(obj) { return obj === Object(obj); }; exports.groupBy = function(obj, val) { var result = {}; var iterator = exports.isFunction(val) ? val : function(obj) { return obj[val]; }; for(var i=0; i<obj.length; i++) { var value = obj[i]; var key = iterator(value, i); (result[key] || (result[key] = [])).push(value); } return result; }; exports.toArray = function(obj) { return Array.prototype.slice.call(obj); }; exports.without = function(array) { var result = []; if (!array) { return result; } var index = -1, length = array.length, contains = exports.toArray(arguments).slice(1); while(++index < length) { if(contains.indexOf(array[index]) === -1) { result.push(array[index]); } } return result; }; exports.extend = function(obj, obj2) { for(var k in obj2) { obj[k] = obj2[k]; } return obj; }; exports.repeat = function(char_, n) { var str = ''; for(var i=0; i<n; i++) { str += char_; } return str; }; exports.each = function(obj, func, context) { if(obj == null) { return; } if(ArrayProto.each && obj.each == ArrayProto.each) { obj.forEach(func, context); } else if(obj.length === +obj.length) { for(var i=0, l=obj.length; i<l; i++) { func.call(context, obj[i], i, obj); } } }; exports.map = function(obj, func) { var results = []; if(obj == null) { return results; } if(ArrayProto.map && obj.map === ArrayProto.map) { return obj.map(func); } for(var i=0; i<obj.length; i++) { results[results.length] = func(obj[i], i); } if(obj.length === +obj.length) { results.length = obj.length; } return results; }; exports.asyncParallel = function(funcs, done) { var count = funcs.length, result = new Array(count), current = 0; var makeNext = function(i) { return function(res) { result[i] = res; current += 1; if (current === count) { done(result); } }; }; for (var i = 0; i < count; i++) { funcs[i](makeNext(i)); } }; exports.asyncIter = function(arr, iter, cb) { var i = -1; function next() { i++; if(i < arr.length) { iter(arr[i], i, next, cb); } else { cb(); } } next(); }; exports.asyncFor = function(obj, iter, cb) { var keys = exports.keys(obj); var len = keys.length; var i = -1; function next() { i++; var k = keys[i]; if(i < len) { iter(k, obj[k], i, len, next); } else { cb(); } } next(); }; if(!Array.prototype.indexOf) { Array.prototype.indexOf = function(array, searchElement /*, fromIndex */) { if (array == null) { throw new TypeError(); } var t = Object(array); var len = t.length >>> 0; if (len === 0) { return -1; } var n = 0; if (arguments.length > 2) { n = Number(arguments[2]); if (n != n) { // shortcut for verifying if it's NaN n = 0; } else if (n != 0 && n != Infinity && n != -Infinity) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } if (n >= len) { return -1; } var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); for (; k < len; k++) { if (k in t && t[k] === searchElement) { return k; } } return -1; }; } if(!Array.prototype.map) { Array.prototype.map = function() { throw new Error("map is unimplemented for this js engine"); }; } exports.keys = function(obj) { if(Object.prototype.keys) { return obj.keys(); } else { var keys = []; for(var k in obj) { if(obj.hasOwnProperty(k)) { keys.push(k); } } return keys; } } })(); (function() { var util = modules["util"]; var lib = modules["lib"]; var Object = modules["object"]; function traverseAndCheck(obj, type, results) { if(obj instanceof type) { results.push(obj); } if(obj instanceof Node) { obj.findAll(type, results); } } var Node = Object.extend("Node", { init: function(lineno, colno) { this.lineno = lineno; this.colno = colno; var fields = this.fields; for(var i=0, l=fields.length; i<l; i++) { var field = fields[i]; // The first two args are line/col numbers, so offset by 2 var val = arguments[i + 2]; // Fields should never be undefined, but null. It makes // testing easier to normalize values. if(val === undefined) { val = null; } this[field] = val; } }, findAll: function(type, results) { results = results || []; if(this instanceof NodeList) { var children = this.children; for(var i=0, l=children.length; i<l; i++) { traverseAndCheck(children[i], type, results); } } else { var fields = this.fields; for(var i=0, l=fields.length; i<l; i++) { traverseAndCheck(this[fields[i]], type, results); } } return results; }, iterFields: function(func) { lib.each(this.fields, function(field) { func(this[field], field); }, this); } }); // Abstract nodes var Value = Node.extend("Value", { fields: ['value'] }); // Concrete nodes var NodeList = Node.extend("NodeList", { fields: ['children'], init: function(lineno, colno, nodes) { this.parent(lineno, colno, nodes || []); }, addChild: function(node) { this.children.push(node); } }); var Root = NodeList.extend("Root"); var Literal = Value.extend("Literal"); var Symbol = Value.extend("Symbol"); var Group = NodeList.extend("Group"); var Array = NodeList.extend("Array"); var Pair = Node.extend("Pair", { fields: ['key', 'value'] }); var Dict = NodeList.extend("Dict"); var LookupVal = Node.extend("LookupVal", { fields: ['target', 'val'] }); var If = Node.extend("If", { fields: ['cond', 'body', 'else_'] }); var IfAsync = If.extend("IfAsync"); var InlineIf = Node.extend("InlineIf", { fields: ['cond', 'body', 'else_'] }); var For = Node.extend("For", { fields: ['arr', 'name', 'body'] }); var AsyncEach = For.extend("AsyncEach"); var AsyncAll = For.extend("AsyncAll"); var Macro = Node.extend("Macro", { fields: ['name', 'args', 'body'] }); var Import = Node.extend("Import", { fields: ['template', 'target'] }); var FromImport = Node.extend("FromImport", { fields: ['template', 'names'], init: function(lineno, colno, template, names) { this.parent(lineno, colno, template, names || new NodeList()); } }); var FunCall = Node.extend("FunCall", { fields: ['name', 'args'] }); var Filter = FunCall.extend("Filter"); var FilterAsync = Filter.extend("FilterAsync", { fields: ['name', 'args', 'symbol'] }); var KeywordArgs = Dict.extend("KeywordArgs"); var Block = Node.extend("Block", { fields: ['name', 'body'] }); var Super = Node.extend("Super", { fields: ['blockName', 'symbol'] }); var TemplateRef = Node.extend("TemplateRef", { fields: ['template'] }); var Extends = TemplateRef.extend("Extends"); var Include = TemplateRef.extend("Include"); var Set = Node.extend("Set", { fields: ['targets', 'value'] }); var Output = NodeList.extend("Output"); var TemplateData = Literal.extend("TemplateData"); var UnaryOp = Node.extend("UnaryOp", { fields: ['target'] }); var BinOp = Node.extend("BinOp", { fields: ['left', 'right'] }); var Or = BinOp.extend("Or"); var And = BinOp.extend("And"); var Not = UnaryOp.extend("Not"); var Add = BinOp.extend("Add"); var Sub = BinOp.extend("Sub"); var Mul = BinOp.extend("Mul"); var Div = BinOp.extend("Div"); var FloorDiv = BinOp.extend("FloorDiv"); var Mod = BinOp.extend("Mod"); var Pow = BinOp.extend("Pow"); var Neg = UnaryOp.extend("Neg"); var Pos = UnaryOp.extend("Pos"); var Compare = Node.extend("Compare", { fields: ['expr', 'ops'] }); var CompareOperand = Node.extend("CompareOperand", { fields: ['expr', 'type'] }); var CustomTag = Node.extend("CustomTag", { init: function(lineno, colno, name) { this.lineno = lineno; this.colno = colno; this.name = name; } }); var CallExtension = Node.extend("CallExtension", { fields: ['extName', 'prop', 'args', 'contentArgs'], init: function(ext, prop, args, contentArgs) { this.extName = ext._name || ext; this.prop = prop; this.args = args || new NodeList(); this.contentArgs = contentArgs || []; } }); var CallExtensionAsync = CallExtension.extend("CallExtensionAsync"); // Print the AST in a nicely formatted tree format for debuggin function printNodes(node, indent) { indent = indent || 0; // This is hacky, but this is just a debugging function anyway function print(str, indent, inline) { var lines = str.split("\n"); for(var i=0; i<lines.length; i++) { if(lines[i]) { if((inline && i > 0) || !inline) { for(var j=0; j<indent; j++) { util.print(" "); } } } if(i === lines.length-1) { util.print(lines[i]); } else { util.puts(lines[i]); } } } print(node.typename + ": ", indent); if(node instanceof NodeList) { print('\n'); lib.each(node.children, function(n) { printNodes(n, indent + 2); }); } else if(node instanceof CallExtension) { print(node.extName + '.' + node.prop); print('\n'); if(node.args) { printNodes(node.args, indent + 2); } if(node.contentArgs) { lib.each(node.contentArgs, function(n) { printNodes(n, indent + 2); }); } } else { var nodes = null; var props = null; node.iterFields(function(val, field) { if(val instanceof Node) { nodes = nodes || {}; nodes[field] = val; } else { props = props || {}; props[field] = val; } }); if(props) { print(util.inspect(props, true, null) + '\n', null, true); } else { print('\n'); } if(nodes) { for(var k in nodes) { printNodes(nodes[k], indent + 2); } } } } // var t = new NodeList(0, 0, // [new Value(0, 0, 3), // new Value(0, 0, 10), // new Pair(0, 0, // new Value(0, 0, 'key'), // new Value(0, 0, 'value'))]); // printNodes(t); modules['nodes'] = { Node: Node, Root: Root, NodeList: NodeList, Value: Value, Literal: Literal, Symbol: Symbol, Group: Group, Array: Array, Pair: Pair, Dict: Dict, Output: Output, TemplateData: TemplateData, If: If, IfAsync: IfAsync, InlineIf: InlineIf, For: For, AsyncEach: AsyncEach, AsyncAll: AsyncAll, Macro: Macro, Import: Import, FromImport: FromImport, FunCall: FunCall, Filter: Filter, FilterAsync: FilterAsync, KeywordArgs: KeywordArgs, Block: Block, Super: Super, Extends: Extends, Include: Include, Set: Set, LookupVal: LookupVal, BinOp: BinOp, Or: Or, And: And, Not: Not, Add: Add, Sub: Sub, Mul: Mul, Div: Div, FloorDiv: FloorDiv, Mod: Mod, Pow: Pow, Neg: Neg, Pos: Pos, Compare: Compare, CompareOperand: CompareOperand, CallExtension: CallExtension, CallExtensionAsync: CallExtensionAsync, printNodes: printNodes }; })(); (function() { var lib = modules["lib"]; var Object = modules["object"]; // Frames keep track of scoping both at compile-time and run-time so // we know how to access variables. Block tags can introduce special // variables, for example. var Frame = Object.extend({ init: function(parent) { this.variables = {}; this.parent = parent; }, set: function(name, val) { // Allow variables with dots by automatically creating the // nested structure var parts = name.split('.'); var obj = this.variables; for(var i=0; i<parts.length - 1; i++) { var id = parts[i]; if(!obj[id]) { obj[id] = {}; } obj = obj[id]; } obj[parts[parts.length - 1]] = val; }, get: function(name) { var val = this.variables[name]; if(val !== undefined && val !== null) { return val; } return null; }, lookup: function(name) { var p = this.parent; var val = this.variables[name]; if(val !== undefined && val !== null) { return val; } return p && p.lookup(name); }, push: function() { return new Frame(this); }, pop: function() { return this.parent; } }); function makeMacro(argNames, kwargNames, func) { return function() { var argCount = numArgs(arguments); var args; var kwargs = getKeywordArgs(arguments); if(argCount > argNames.length) { args = Array.prototype.slice.call(arguments, 0, argNames.length); // Positional arguments that should be passed in as // keyword arguments (essentially default values) var vals = Array.prototype.slice.call(arguments, args.length, argCount); for(var i=0; i<vals.length; i++) { if(i < kwargNames.length) { kwargs[kwargNames[i]] = vals[i]; } } args.push(kwargs); } else if(argCount < argNames.length) { args = Array.prototype.slice.call(arguments, 0, argCount); for(var i=argCount; i<argNames.length; i++) { var arg = argNames[i]; // Keyword arguments that should be passed as // positional arguments, i.e. the caller explicitly // used the name of a positional arg args.push(kwargs[arg]); delete kwargs[arg]; } args.push(kwargs); } else { args = arguments; } return func.apply(this, args); }; } function makeKeywordArgs(obj) { obj.__keywords = true; return obj; } function getKeywordArgs(args) { var len = args.length; if(len) { var lastArg = args[len - 1]; if(lastArg && lastArg.hasOwnProperty('__keywords')) { return lastArg; } } return {}; } function numArgs(args) { var len = args.length; if(len === 0) { return 0; } var lastArg = args[len - 1]; if(lastArg && lastArg.hasOwnProperty('__keywords')) { return len - 1; } else { return len; } } // A SafeString object indicates that the string should not be // autoescaped. This happens magically because autoescaping only // occurs on primitive string objects. function SafeString(val) { if(typeof val != 'string') { return val; } this.toString = function() { return val; }; this.length = val.length; var methods = [ 'charAt', 'charCodeAt', 'concat', 'contains', 'endsWith', 'fromCharCode', 'indexOf', 'lastIndexOf', 'length', 'localeCompare', 'match', 'quote', 'replace', 'search', 'slice', 'split', 'startsWith', 'substr', 'substring', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight' ]; for(var i=0; i<methods.length; i++) { this[methods[i]] = markSafe(val[methods[i]]); } } function copySafeness(dest, target) { if(dest instanceof SafeString) { return new SafeString(target); } return target.toString(); } function markSafe(val) { var type = typeof val; if(type === 'string') { return new SafeString(val); } else if(type !== 'function') { return val; } else { return function() { var ret = val.apply(this, arguments); if(typeof ret === 'string') { return new SafeString(ret); } return ret; }; } } function suppressValue(val, autoescape) { val = (val !== undefined && val !== null) ? val : ""; if(autoescape && typeof val === "string") { val = lib.escape(val); } return val; } function memberLookup(obj, val) { obj = obj || {}; if(typeof obj[val] === 'function') { return function() { return obj[val].apply(obj, arguments); }; } return obj[val]; } function callWrap(obj, name, args) { if(!obj) { throw new Error('Unable to call `' + name + '`, which is undefined or falsey'); } else if(typeof obj !== 'function') { throw new Error('Unable to call `' + name + '`, which is not a function'); } return obj.apply(this, args); } function contextOrFrameLookup(context, frame, name) { var val = frame.lookup(name); return (val !== undefined && val !== null) ? val : context.lookup(name); } function handleError(error, lineno, colno) { if(error.lineno) { return error; } else { return new lib.TemplateError(error, lineno, colno); } } function asyncEach(arr, dimen, iter, cb) { if(lib.isArray(arr)) { var len = arr.length; lib.asyncIter(arr, function(item, i, next) { switch(dimen) { case 1: iter(item, i, len, next); break; case 2: iter(item[0], item[1], i, len, next); break; case 3: iter(item[0], item[1], item[2], i, len, next); break; default: item.push(i, next); iter.apply(this, item); } }, cb); } else { lib.asyncFor(arr, function(key, val, i, len, next) { iter(key, val, i, len, next); }, cb); } } function asyncAll(arr, dimen, func, cb) { var finished = 0; var len; var outputArr; function done(i, output) { finished++; outputArr[i] = output; if(finished == len) { cb(null, outputArr.join('')); } } if(lib.isArray(arr)) { len = arr.length; outputArr = new Array(len); if(len == 0) { cb(null, ''); } else { for(var i=0; i<arr.length; i++) { var item = arr[i]; switch(dimen) { case 1: func(item, i, len, done); break; case 2: func(item[0], item[1], i, len, done); break; case 3: func(item[0], item[1], item[2], i, len, done); break; default: item.push(i, done); func.apply(this, item); } } } } else { var keys = lib.keys(arr); len = keys.length; outputArr = new Array(len); if(len == 0) { cb(null, ''); } else { for(var i=0; i<keys.length; i++) { var k = keys[i]; func(k, arr[k], i, len, done); } } } } modules['runtime'] = { Frame: Frame, makeMacro: makeMacro, makeKeywordArgs: makeKeywordArgs, numArgs: numArgs, suppressValue: suppressValue, memberLookup: memberLookup, contextOrFrameLookup: contextOrFrameLookup, callWrap: callWrap, handleError: handleError, isArray: lib.isArray, asyncEach: lib.asyncEach, keys: lib.keys, SafeString: SafeString, copySafeness: copySafeness, markSafe: markSafe, asyncEach: asyncEach, asyncAll: asyncAll }; })(); (function() { var lib = modules["lib"]; var whitespaceChars = " \n\t\r"; var delimChars = "()[]{}%*-+/#,:|.<>=!"; var intChars = "0123456789"; var BLOCK_START = "{%"; var BLOCK_END = "%}"; var VARIABLE_START = "{{"; var VARIABLE_END = "}}"; var COMMENT_START = "{#"; var COMMENT_END = "#}"; var TOKEN_STRING = "string"; var TOKEN_WHITESPACE = "whitespace"; var TOKEN_DATA = "data"; var TOKEN_BLOCK_START = "block-start"; var TOKEN_BLOCK_END = "block-end"; var TOKEN_VARIABLE_START = "variable-start"; var TOKEN_VARIABLE_END = "variable-end"; var TOKEN_COMMENT = "comment"; var TOKEN_LEFT_PAREN = "left-paren"; var TOKEN_RIGHT_PAREN = "right-paren"; var TOKEN_LEFT_BRACKET = "left-bracket"; var TOKEN_RIGHT_BRACKET = "right-bracket"; var TOKEN_LEFT_CURLY = "left-curly"; var TOKEN_RIGHT_CURLY = "right-curly"; var TOKEN_OPERATOR = "operator"; var TOKEN_COMMA = "comma"; var TOKEN_COLON = "colon"; var TOKEN_PIPE = "pipe"; var TOKEN_INT = "int"; var TOKEN_FLOAT = "float"; var TOKEN_BOOLEAN = "boolean"; var TOKEN_SYMBOL = "symbol"; var TOKEN_SPECIAL = "special"; function token(type, value, lineno, colno) { return { type: type, value: value, lineno: lineno, colno: colno }; } function Tokenizer(str) { this.str = str; this.index = 0; this.len = str.length; this.lineno = 0; this.colno = 0; this.in_code = false; } Tokenizer.prototype.nextToken = function() { var lineno = this.lineno; var colno = this.colno; if(this.in_code) { // Otherwise, if we are in a block parse it as code var cur = this.current(); var tok; if(this.is_finished()) { // We have nothing else to parse return null; } else if(cur == "\"" || cur == "'") { // We've hit a string return token(TOKEN_STRING, this.parseString(cur), lineno, colno); } else if((tok = this._extract(whitespaceChars))) { // We hit some whitespace return token(TOKEN_WHITESPACE, tok, lineno, colno); } else if((tok = this._extractString(BLOCK_END)) || (tok = this._extractString('-' + BLOCK_END))) { // Special check for the block end tag // // It is a requirement that start and end tags are composed of // delimiter characters (%{}[] etc), and our code always // breaks on delimiters so we can assume the token parsing // doesn't consume these elsewhere this.in_code = false; return token(TOKEN_BLOCK_END, tok, lineno, colno); } else if((tok = this._extractString(VARIABLE_END))) { // Special check for variable end tag (see above) this.in_code = false; return token(TOKEN_VARIABLE_END, tok, lineno, colno); } else if(delimChars.indexOf(cur) != -1) { // We've hit a delimiter (a special char like a bracket) this.forward(); var complexOps = ['==', '!=', '<=', '>=', '//', '**']; var curComplex = cur + this.current(); var type; if(complexOps.indexOf(curComplex) !== -1) { this.forward(); cur = curComplex; } switch(cur) { case "(": type = TOKEN_LEFT_PAREN; break; case ")": type = TOKEN_RIGHT_PAREN; break; case "[": type = TOKEN_LEFT_BRACKET; break; case "]": type = TOKEN_RIGHT_BRACKET; break; case "{": type = TOKEN_LEFT_CURLY; break; case "}": type = TOKEN_RIGHT_CURLY; break; case ",": type = TOKEN_COMMA; break; case ":": type = TOKEN_COLON; break; case "|": type = TOKEN_PIPE; break; default: type = TOKEN_OPERATOR; } return token(type, cur, lineno, colno); } else { // We are not at whitespace or a delimiter, so extract the // text and parse it tok = this._extractUntil(whitespaceChars + delimChars); if(tok.match(/^[-+]?[0-9]+$/)) { if(this.current() == '.') { this.forward(); var dec = this._extract(intChars); return token(TOKEN_FLOAT, tok + '.' + dec, lineno, colno); } else { return token(TOKEN_INT, tok, lineno, colno); } } else if(tok.match(/^(true|false)$/)) { return token(TOKEN_BOOLEAN, tok, lineno, colno); } else if(tok) { return token(TOKEN_SYMBOL, tok, lineno, colno); } else { throw new Error("Unexpected value while parsing: " + tok); } } } else { // Parse out the template text, breaking on tag // delimiters because we need to look for block/variable start // tags (don't use the full delimChars for optimization) var beginChars = (BLOCK_START.charAt(0) + VARIABLE_START.charAt(0) + COMMENT_START.charAt(0) + COMMENT_END.charAt(0)); var tok; if(this.is_finished()) { return null; } else if((tok = this._extractString(BLOCK_START + '-')) || (tok = this._extractString(BLOCK_START))) { this.in_code = true; return token(TOKEN_BLOCK_START, tok, lineno, colno); } else if((tok = this._extractString(VARIABLE_START))) { this.in_code = true; return token(TOKEN_VARIABLE_START, tok, lineno, colno); } else { tok = ''; var data; var in_comment = false; if(this._matches(COMMENT_START)) { in_comment = true; tok = this._extractString(COMMENT_START); } // Continually consume text, breaking on the tag delimiter // characters and checking to see if it's a start tag. // // We could hit the end of the template in the middle of // our looping, so check for the null return value from // _extractUntil while((data = this._extractUntil(beginChars)) !== null) { tok += data; if((this._matches(BLOCK_START) || this._matches(VARIABLE_START) || this._matches(COMMENT_START)) && !in_comment) { // If it is a start tag, stop looping break; } else if(this._matches(COMMENT_END)) { if(!in_comment) { throw new Error("unexpected end of comment"); } tok += this._extractString(COMMENT_END); break; } else { // It does not match any tag, so add the character and // carry on tok += this.current(); this.forward(); } } if(data === null && in_comment) { throw new Error("expected end of comment, got end of file"); } return token(in_comment ? TOKEN_COMMENT : TOKEN_DATA, tok, lineno, colno); } } throw new Error("Could not parse text"); }; Tokenizer.prototype.parseString = function(delimiter) { this.forward(); var lineno = this.lineno; var colno = this.colno; var str = ""; while(!this.is_finished() && this.current() != delimiter) { var cur = this.current(); if(cur == "\\") { this.forward(); switch(this.current()) { case "n": str += "\n"; break; case "t": str += "\t"; break; case "r": str += "\r"; break; default: str += this.current(); } this.forward(); } else { str += cur; this.forward(); } } this.forward(); return str; }; Tokenizer.prototype._matches = function(str) { if(this.index + str.length > this.length) { return null; } var m = this.str.slice(this.index, this.index + str.length); return m == str; }; Tokenizer.prototype._extractString = function(str) { if(this._matches(str)) { this.index += str.length; return str; } return null; }; Tokenizer.prototype._extractUntil = function(charString) { // Extract all non-matching chars, with the default matching set // to everything return this._extractMatching(true, charString || ""); }; Tokenizer.prototype._extract = function(charString) { // Extract all matching chars (no default, so charString must be // explicit) return this._extractMatching(false, charString); }; Tokenizer.prototype._extractMatching = function (breakOnMatch, charString) { // Pull out characters until a breaking char is hit. // If breakOnMatch is false, a non-matching char stops it. // If breakOnMatch is true, a matching char stops it. if(this.is_finished()) { return null; } var first = charString.indexOf(this.current()); // Only proceed if the first character doesn't meet our condition if((breakOnMatch && first == -1) || (!breakOnMatch && first != -1)) { var t = this.current(); this.forward(); // And pull out all the chars one at a time until we hit a // breaking char var idx = charString.indexOf(this.current()); while(((breakOnMatch && idx == -1) || (!breakOnMatch && idx != -1)) && !this.is_finished()) { t += this.current(); this.forward(); idx = charString.indexOf(this.current()); } return t; } return ""; }; Tokenizer.prototype.is_finished = function() { return this.index >= this.len; }; Tokenizer.prototype.forwardN = function(n) { for(var i=0; i<n; i++) { this.forward(); } }; Tokenizer.prototype.forward = function() { this.index++; if(this.previous() == "\n") { this.lineno++; this.colno = 0; } else { this.colno++; } }; Tokenizer.prototype.backN = function(n) { for(var i=0; i<n; i++) { self.back(); } }; Tokenizer.prototype.back = function() { this.index--; if(this.current() == "\n") { this.lineno--; var idx = this.src.lastIndexOf("\n", this.index-1); if(idx == -1) { this.colno = this.index; } else { this.colno = this.index - idx; } } else { this.colno--; } }; Tokenizer.prototype.current = function() { if(!this.is_finished()) { return this.str.charAt(this.index); } return ""; }; Tokenizer.prototype.previous = function() { return this.str.charAt(this.index-1); }; modules['lexer'] = { lex: function(src) { return new Tokenizer(src); }, setTags: function(tags) { BLOCK_START = tags.blockStart || BLOCK_START; BLOCK_END = tags.blockEnd || BLOCK_END; VARIABLE_START = tags.variableStart || VARIABLE_START; VARIABLE_END = tags.variableEnd || VARIABLE_END; COMMENT_START = tags.commentStart || COMMENT_START; COMMENT_END = tags.commentEnd || COMMENT_END; }, TOKEN_STRING: TOKEN_STRING, TOKEN_WHITESPACE: TOKEN_WHITESPACE, TOKEN_DATA: TOKEN_DATA, TOKEN_BLOCK_START: TOKEN_BLOCK_START, TOKEN_BLOCK_END: TOKEN_BLOCK_END, TOKEN_VARIABLE_START: TOKEN_VARIABLE_START, TOKEN_VARIABLE_END: TOKEN_VARIABLE_END, TOKEN_COMMENT: TOKEN_COMMENT, TOKEN_LEFT_PAREN: TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN: TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACKET: TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET: TOKEN_RIGHT_BRACKET, TOKEN_LEFT_CURLY: TOKEN_LEFT_CURLY, TOKEN_RIGHT_CURLY: TOKEN_RIGHT_CURLY, TOKEN_OPERATOR: TOKEN_OPERATOR, TOKEN_COMMA: TOKEN_COMMA, TOKEN_COLON: TOKEN_COLON, TOKEN_PIPE: TOKEN_PIPE, TOKEN_INT: TOKEN_INT, TOKEN_FLOAT: TOKEN_FLOAT, TOKEN_BOOLEAN: TOKEN_BOOLEAN, TOKEN_SYMBOL: TOKEN_SYMBOL, TOKEN_SPECIAL: TOKEN_SPECIAL }; })(); (function() { var lexer = modules["lexer"]; var nodes = modules["nodes"]; var Object = modules["object"]; var lib = modules["lib"]; var Parser = Object.extend({ init: function (tokens) { this.tokens = tokens; this.peeked = null; this.breakOnBlocks = null; this.dropLeadingWhitespace = false; this.extensions = []; }, nextToken: function (withWhitespace) { var tok; if(this.peeked) { if(!withWhitespace && this.peeked.type == lexer.TOKEN_WHITESPACE) { this.peeked = null; } else { tok = this.peeked; this.peeked = null; return tok; } } tok = this.tokens.nextToken(); if(!withWhitespace) { while(tok && tok.type == lexer.TOKEN_WHITESPACE) { tok = this.tokens.nextToken(); } } return tok; }, peekToken: function () { this.peeked = this.peeked || this.nextToken(); return this.peeked; }, pushToken: function(tok) { if(this.peeked) { throw new Error("pushToken: can only push one token on between reads"); } this.peeked = tok; }, fail: function (msg, lineno, colno) { if((lineno === undefined || colno === undefined) && this.peekToken()) { var tok = this.peekToken(); lineno = tok.lineno; colno = tok.colno; } if (lineno !== undefined) lineno += 1; if (colno !== undefined) colno += 1; throw new lib.TemplateError(msg, lineno, colno); }, skip: function(type) { var tok = this.nextToken(); if(!tok || tok.type != type) { this.pushToken(tok); return false; } return true; }, expect: function(type) { var tok = this.nextToken(); if(!tok.type == type) { this.fail('expected ' + type + ', got ' + tok.type, tok.lineno, tok.colno); } return tok; }, skipValue: function(type, val) { var tok = this.nextToken(); if(!tok || tok.type != type || tok.value != val) { this.pushToken(tok); return false; } return true; }, skipWhitespace: function () { return this.skip(lexer.TOKEN_WHITESPACE); }, skipSymbol: function(val) { return this.skipValue(lexer.TOKEN_SYMBOL, val); }, advanceAfterBlockEnd: function(name) { if(!name) { var tok = this.peekToken(); if(!tok) { this.fail('unexpected end of file'); } if(tok.type != lexer.TOKEN_SYMBOL) { this.fail("advanceAfterBlockEnd: expected symbol token or " + "explicit name to be passed"); } name = this.nextToken().value; } var tok = this.nextToken(); if(tok && tok.type == lexer.TOKEN_BLOCK_END) { if(tok.value.charAt(0) === '-') { this.dropLeadingWhitespace = true; } } else { this.fail("expected block end in " + name + " statement"); } }, advanceAfterVariableEnd: function() { if(!this.skip(lexer.TOKEN_VARIABLE_END)) { this.fail("expected variable end"); } }, parseFor: function() { var forTok = this.peekToken(); var node; var endBlock; if(this.skipSymbol('for')) { node = new nodes.For(forTok.lineno, forTok.colno); endBlock = 'endfor'; } else if(this.skipSymbol('asyncEach')) { node = new nodes.AsyncEach(forTok.lineno, forTok.colno); endBlock = 'endeach'; } else if(this.skipSymbol('asyncAll')) { node = new nodes.AsyncAll(forTok.lineno, forTok.colno); endBlock = 'endall'; } else { this.fail("parseFor: expected for{Async}", forTok.lineno, forTok.colno); } node.name = this.parsePrimary(); if(!(node.name instanceof nodes.Symbol)) { this.fail('parseFor: variable name expected for loop'); } var type = this.peekToken().type; if(type == lexer.TOKEN_COMMA) { // key/value iteration var key = node.name; node.name = new nodes.Array(key.lineno, key.colno); node.name.addChild(key); while(this.skip(lexer.TOKEN_COMMA)) { var prim = this.parsePrimary(); node.name.addChild(prim); } } if(!this.skipSymbol('in')) { this.fail('parseFor: expected "in" keyword for loop', forTok.lineno, forTok.colno); } node.arr = this.parseExpression(); this.advanceAfterBlockEnd(forTok.value); node.body = this.parseUntilBlocks(endBlock); this.advanceAfterBlockEnd(); return node; }, parseMacro: function() { var macroTok = this.peekToken(); if(!this.skipSymbol('macro')) { this.fail("expected macro"); } var name = this.parsePrimary(true); var args = this.parseSignature(); var node = new nodes.Macro(macroTok.lineno, macroTok.colno, name, args); this.advanceAfterBlockEnd(macroTok.value); node.body = this.parseUntilBlocks('endmacro'); this.advanceAfterBlockEnd(); return node; }, parseImport: function() { var importTok = this.peekToken(); if(!this.skipSymbol('import')) { this.fail("parseImport: expected import", importTok.lineno, importTok.colno); } var template = this.parsePrimary(); if(!this.skipSymbol('as')) { this.fail('parseImport: expected "as" keyword', importTok.lineno, importTok.colno); } var target = this.parsePrimary(); var node = new nodes.Import(importTok.lineno, importTok.colno, template, target); this.advanceAfterBlockEnd(importTok.value); return node; }, parseFrom: function() { var fromTok = this.peekToken(); if(!this.skipSymbol('from')) { this.fail("parseFrom: expected from"); } var template = this.parsePrimary(); var node = new nodes.FromImport(fromTok.lineno, fromTok.colno, template, new nodes.NodeList()); if(!this.skipSymbol('import')) { this.fail("parseFrom: expected import", fromTok.lineno, fromTok.colno); } var names = node.names; while(1) { var nextTok = this.peekToken(); if(nextTok.type == lexer.TOKEN_BLOCK_END) { if(!names.children.length) { this.fail('parseFrom: Expected at least one import name', fromTok.lineno, fromTok.colno); } // Since we are manually advancing past the block end, // need to keep track of whitespace control (normally // this is done in `advanceAfterBlockEnd` if(nextTok.value.charAt(0) == '-') { this.dropLeadingWhitespace = true; } this.nextToken(); break; } if(names.children.length > 0 && !this.skip(lexer.TOKEN_COMMA)) { this.fail('parseFrom: expected comma', fromTok.lineno, fromTok.colno); } var name = this.parsePrimary(); if(name.value.charAt(0) == '_') { this.fail('parseFrom: names starting with an underscore ' + 'cannot be imported', name.lineno, name.colno); } if(this.skipSymbol('as')) { var alias = this.parsePrimary(); names.addChild(new nodes.Pair(name.lineno, name.colno, name, alias)); } else { names.addChild(name); } } return node; }, parseBlock: function() { var tag = this.peekToken(); if(!this.skipSymbol('block')) { this.fail('parseBlock: expected block', tag.lineno, tag.colno); } var node = new nodes.Block(tag.lineno, tag.colno); node.name = this.parsePrimary(); if(!(node.name instanceof nodes.Symbol)) { this.fail('parseBlock: variable name expected', tag.lineno, tag.colno); } this.advanceAfterBlockEnd(tag.value); node.body = this.parseUntilBlocks('endblock'); if(!this.peekToken()) { this.fail('parseBlock: expected endblock, got end of file'); } this.advanceAfterBlockEnd(); return node; }, parseTemplateRef: function(tagName, nodeType) { var tag = this.peekToken(); if(!this.skipSymbol(tagName)) { this.fail('parseTemplateRef: expected '+ tagName); } var node = new nodeType(tag.lineno, tag.colno); node.template = this.parsePrimary(); this.advanceAfterBlockEnd(tag.value); return node; }, parseExtends: function() { return this.parseTemplateRef('extends', nodes.Extends); }, parseInclude: function() { return this.parseTemplateRef('include', nodes.Include); }, parseIf: function() { var tag = this.peekToken(); var node; if(this.skipSymbol('if') || this.skipSymbol('elif')) { node = new nodes.If(tag.lineno, tag.colno); } else if(this.skipSymbol('ifAsync')) { node = new nodes.IfAsync(tag.lineno, tag.colno); } else { this.fail("parseIf: expected if or elif", tag.lineno, tag.colno); } node.cond = this.parseExpression(); this.advanceAfterBlockEnd(tag.value); node.body = this.parseUntilBlocks('elif', 'else', 'endif'); var tok = this.peekToken(); switch(tok && tok.value) { case "elif": node.else_ = this.parseIf(); break; case "else": this.advanceAfterBlockEnd(); node.else_ = this.parseUntilBlocks("endif"); this.advanceAfterBlockEnd(); break; case "endif": node.else_ = null; this.advanceAfterBlockEnd(); break; default: this.fail('parseIf: expected endif, else, or endif, ' + 'got end of file'); } return node; }, parseSet: function() { var tag = this.peekToken(); if(!this.skipSymbol('set')) { this.fail('parseSet: expected set', tag.lineno, tag.colno); } var node = new nodes.Set(tag.lineno, tag.colno, []); var target; while((target = this.parsePrimary())) { node.targets.push(target); if(!this.skip(lexer.TOKEN_COMMA)) { break; } } if(!this.skipValue(lexer.TOKEN_OPERATOR, '=')) { this.fail('parseSet: expected = in set tag', tag.lineno, tag.colno); } node.value = this.parseExpression(); this.advanceAfterBlockEnd(tag.value); return node; }, parseStatement: function () { var tok = this.peekToken(); var node; if(tok.type != lexer.TOKEN_SYMBOL) { this.fail('tag name expected', tok.lineno, tok.colno); } if(this.breakOnBlocks && this.breakOnBlocks.indexOf(tok.value) !== -1) { return null; } switch(tok.value) { case