/files/nunjucks/1.0.0/nunjucks.js
JavaScript | 1858 lines | 1638 code | 162 blank | 58 comment | 202 complexity | b6cfa29709eb5e174a487e0a7690f826 MD5 | raw file
- // 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 = {
- '&': '&',
- '"': '"',
- "'": ''',
- "<": '<',
- ">": '>'
- };
- 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) {
-