UNPKG

141 kBJavaScriptView Raw
1// Browser bundle of nunjucks 1.0.7
2
3(function() {
4var modules = {};
5(function() {
6
7// A simple class system, more documentation to come
8
9function extend(cls, name, props) {
10 // This does that same thing as Object.create, but with support for IE8
11 var F = function() {};
12 F.prototype = cls.prototype;
13 var prototype = new F();
14
15 var fnTest = /xyz/.test(function(){ xyz; }) ? /\bparent\b/ : /.*/;
16 props = props || {};
17
18 for(var k in props) {
19 var src = props[k];
20 var parent = prototype[k];
21
22 if(typeof parent == "function" &&
23 typeof src == "function" &&
24 fnTest.test(src)) {
25 prototype[k] = (function (src, parent) {
26 return function() {
27 // Save the current parent method
28 var tmp = this.parent;
29
30 // Set parent to the previous method, call, and restore
31 this.parent = parent;
32 var res = src.apply(this, arguments);
33 this.parent = tmp;
34
35 return res;
36 };
37 })(src, parent);
38 }
39 else {
40 prototype[k] = src;
41 }
42 }
43
44 prototype.typename = name;
45
46 var new_cls = function() {
47 if(prototype.init) {
48 prototype.init.apply(this, arguments);
49 }
50 };
51
52 new_cls.prototype = prototype;
53 new_cls.prototype.constructor = new_cls;
54
55 new_cls.extend = function(name, props) {
56 if(typeof name == "object") {
57 props = name;
58 name = "anonymous";
59 }
60 return extend(new_cls, name, props);
61 };
62
63 return new_cls;
64}
65
66modules['object'] = extend(Object, "Object", {});
67})();
68(function() {
69var ArrayProto = Array.prototype;
70var ObjProto = Object.prototype;
71
72var escapeMap = {
73 '&': '&',
74 '"': '"',
75 "'": ''',
76 "<": '&lt;',
77 ">": '&gt;'
78};
79
80var escapeRegex = /[&"'<>]/g;
81
82var lookupEscape = function(ch) {
83 return escapeMap[ch];
84};
85
86var exports = modules['lib'] = {};
87
88exports.withPrettyErrors = function(path, withInternals, func) {
89 try {
90 return func();
91 } catch (e) {
92 if (!e.Update) {
93 // not one of ours, cast it
94 e = new exports.TemplateError(e);
95 }
96 e.Update(path);
97
98 // Unless they marked the dev flag, show them a trace from here
99 if (!withInternals) {
100 var old = e;
101 e = new Error(old.message);
102 e.name = old.name;
103 }
104
105 throw e;
106 }
107};
108
109exports.TemplateError = function(message, lineno, colno) {
110 var err = this;
111
112 if (message instanceof Error) { // for casting regular js errors
113 err = message;
114 message = message.name + ": " + message.message;
115 } else {
116 if(Error.captureStackTrace) {
117 Error.captureStackTrace(err);
118 }
119 }
120
121 err.name = 'Template render error';
122 err.message = message;
123 err.lineno = lineno;
124 err.colno = colno;
125 err.firstUpdate = true;
126
127 err.Update = function(path) {
128 var message = "(" + (path || "unknown path") + ")";
129
130 // only show lineno + colno next to path of template
131 // where error occurred
132 if (this.firstUpdate) {
133 if(this.lineno && this.colno) {
134 message += ' [Line ' + this.lineno + ', Column ' + this.colno + ']';
135 }
136 else if(this.lineno) {
137 message += ' [Line ' + this.lineno + ']';
138 }
139 }
140
141 message += '\n ';
142 if (this.firstUpdate) {
143 message += ' ';
144 }
145
146 this.message = message + (this.message || '');
147 this.firstUpdate = false;
148 return this;
149 };
150
151 return err;
152};
153
154exports.TemplateError.prototype = Error.prototype;
155
156exports.escape = function(val) {
157 return val.replace(escapeRegex, lookupEscape);
158};
159
160exports.isFunction = function(obj) {
161 return ObjProto.toString.call(obj) == '[object Function]';
162};
163
164exports.isArray = Array.isArray || function(obj) {
165 return ObjProto.toString.call(obj) == '[object Array]';
166};
167
168exports.isString = function(obj) {
169 return ObjProto.toString.call(obj) == '[object String]';
170};
171
172exports.isObject = function(obj) {
173 return ObjProto.toString.call(obj) == '[object Object]';
174};
175
176exports.groupBy = function(obj, val) {
177 var result = {};
178 var iterator = exports.isFunction(val) ? val : function(obj) { return obj[val]; };
179 for(var i=0; i<obj.length; i++) {
180 var value = obj[i];
181 var key = iterator(value, i);
182 (result[key] || (result[key] = [])).push(value);
183 }
184 return result;
185};
186
187exports.toArray = function(obj) {
188 return Array.prototype.slice.call(obj);
189};
190
191exports.without = function(array) {
192 var result = [];
193 if (!array) {
194 return result;
195 }
196 var index = -1,
197 length = array.length,
198 contains = exports.toArray(arguments).slice(1);
199
200 while(++index < length) {
201 if(contains.indexOf(array[index]) === -1) {
202 result.push(array[index]);
203 }
204 }
205 return result;
206};
207
208exports.extend = function(obj, obj2) {
209 for(var k in obj2) {
210 obj[k] = obj2[k];
211 }
212 return obj;
213};
214
215exports.repeat = function(char_, n) {
216 var str = '';
217 for(var i=0; i<n; i++) {
218 str += char_;
219 }
220 return str;
221};
222
223exports.each = function(obj, func, context) {
224 if(obj == null) {
225 return;
226 }
227
228 if(ArrayProto.each && obj.each == ArrayProto.each) {
229 obj.forEach(func, context);
230 }
231 else if(obj.length === +obj.length) {
232 for(var i=0, l=obj.length; i<l; i++) {
233 func.call(context, obj[i], i, obj);
234 }
235 }
236};
237
238exports.map = function(obj, func) {
239 var results = [];
240 if(obj == null) {
241 return results;
242 }
243
244 if(ArrayProto.map && obj.map === ArrayProto.map) {
245 return obj.map(func);
246 }
247
248 for(var i=0; i<obj.length; i++) {
249 results[results.length] = func(obj[i], i);
250 }
251
252 if(obj.length === +obj.length) {
253 results.length = obj.length;
254 }
255
256 return results;
257};
258
259exports.asyncIter = function(arr, iter, cb) {
260 var i = -1;
261
262 function next() {
263 i++;
264
265 if(i < arr.length) {
266 iter(arr[i], i, next, cb);
267 }
268 else {
269 cb();
270 }
271 }
272
273 next();
274};
275
276exports.asyncFor = function(obj, iter, cb) {
277 var keys = exports.keys(obj);
278 var len = keys.length;
279 var i = -1;
280
281 function next() {
282 i++;
283 var k = keys[i];
284
285 if(i < len) {
286 iter(k, obj[k], i, len, next);
287 }
288 else {
289 cb();
290 }
291 }
292
293 next();
294};
295
296if(!Array.prototype.indexOf) {
297 Array.prototype.indexOf = function(array, searchElement /*, fromIndex */) {
298 if (array == null) {
299 throw new TypeError();
300 }
301 var t = Object(array);
302 var len = t.length >>> 0;
303 if (len === 0) {
304 return -1;
305 }
306 var n = 0;
307 if (arguments.length > 2) {
308 n = Number(arguments[2]);
309 if (n != n) { // shortcut for verifying if it's NaN
310 n = 0;
311 } else if (n != 0 && n != Infinity && n != -Infinity) {
312 n = (n > 0 || -1) * Math.floor(Math.abs(n));
313 }
314 }
315 if (n >= len) {
316 return -1;
317 }
318 var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
319 for (; k < len; k++) {
320 if (k in t && t[k] === searchElement) {
321 return k;
322 }
323 }
324 return -1;
325 };
326}
327
328if(!Array.prototype.map) {
329 Array.prototype.map = function() {
330 throw new Error("map is unimplemented for this js engine");
331 };
332}
333
334exports.keys = function(obj) {
335 if(Object.prototype.keys) {
336 return obj.keys();
337 }
338 else {
339 var keys = [];
340 for(var k in obj) {
341 if(obj.hasOwnProperty(k)) {
342 keys.push(k);
343 }
344 }
345 return keys;
346 }
347}
348})();
349(function() {
350var util = modules["util"];
351var lib = modules["lib"];
352var Object = modules["object"];
353
354function traverseAndCheck(obj, type, results) {
355 if(obj instanceof type) {
356 results.push(obj);
357 }
358
359 if(obj instanceof Node) {
360 obj.findAll(type, results);
361 }
362}
363
364var Node = Object.extend("Node", {
365 init: function(lineno, colno) {
366 this.lineno = lineno;
367 this.colno = colno;
368
369 var fields = this.fields;
370 for(var i=0, l=fields.length; i<l; i++) {
371 var field = fields[i];
372
373 // The first two args are line/col numbers, so offset by 2
374 var val = arguments[i + 2];
375
376 // Fields should never be undefined, but null. It makes
377 // testing easier to normalize values.
378 if(val === undefined) {
379 val = null;
380 }
381
382 this[field] = val;
383 }
384 },
385
386 findAll: function(type, results) {
387 results = results || [];
388
389 if(this instanceof NodeList) {
390 var children = this.children;
391
392 for(var i=0, l=children.length; i<l; i++) {
393 traverseAndCheck(children[i], type, results);
394 }
395 }
396 else {
397 var fields = this.fields;
398
399 for(var i=0, l=fields.length; i<l; i++) {
400 traverseAndCheck(this[fields[i]], type, results);
401 }
402 }
403
404 return results;
405 },
406
407 iterFields: function(func) {
408 lib.each(this.fields, function(field) {
409 func(this[field], field);
410 }, this);
411 }
412});
413
414// Abstract nodes
415var Value = Node.extend("Value", { fields: ['value'] });
416
417// Concrete nodes
418var NodeList = Node.extend("NodeList", {
419 fields: ['children'],
420
421 init: function(lineno, colno, nodes) {
422 this.parent(lineno, colno, nodes || []);
423 },
424
425 addChild: function(node) {
426 this.children.push(node);
427 }
428});
429
430var Root = NodeList.extend("Root");
431var Literal = Value.extend("Literal");
432var Symbol = Value.extend("Symbol");
433var Group = NodeList.extend("Group");
434var Array = NodeList.extend("Array");
435var Pair = Node.extend("Pair", { fields: ['key', 'value'] });
436var Dict = NodeList.extend("Dict");
437var LookupVal = Node.extend("LookupVal", { fields: ['target', 'val'] });
438var If = Node.extend("If", { fields: ['cond', 'body', 'else_'] });
439var IfAsync = If.extend("IfAsync");
440var InlineIf = Node.extend("InlineIf", { fields: ['cond', 'body', 'else_'] });
441var For = Node.extend("For", { fields: ['arr', 'name', 'body'] });
442var AsyncEach = For.extend("AsyncEach");
443var AsyncAll = For.extend("AsyncAll");
444var Macro = Node.extend("Macro", { fields: ['name', 'args', 'body'] });
445var Import = Node.extend("Import", { fields: ['template', 'target'] });
446var FromImport = Node.extend("FromImport", {
447 fields: ['template', 'names'],
448
449 init: function(lineno, colno, template, names) {
450 this.parent(lineno, colno,
451 template,
452 names || new NodeList());
453 }
454});
455var FunCall = Node.extend("FunCall", { fields: ['name', 'args'] });
456var Filter = FunCall.extend("Filter");
457var FilterAsync = Filter.extend("FilterAsync", {
458 fields: ['name', 'args', 'symbol']
459});
460var KeywordArgs = Dict.extend("KeywordArgs");
461var Block = Node.extend("Block", { fields: ['name', 'body'] });
462var Super = Node.extend("Super", { fields: ['blockName', 'symbol'] });
463var TemplateRef = Node.extend("TemplateRef", { fields: ['template'] });
464var Extends = TemplateRef.extend("Extends");
465var Include = TemplateRef.extend("Include");
466var Set = Node.extend("Set", { fields: ['targets', 'value'] });
467var Output = NodeList.extend("Output");
468var TemplateData = Literal.extend("TemplateData");
469var UnaryOp = Node.extend("UnaryOp", { fields: ['target'] });
470var BinOp = Node.extend("BinOp", { fields: ['left', 'right'] });
471var Or = BinOp.extend("Or");
472var And = BinOp.extend("And");
473var Not = UnaryOp.extend("Not");
474var Add = BinOp.extend("Add");
475var Sub = BinOp.extend("Sub");
476var Mul = BinOp.extend("Mul");
477var Div = BinOp.extend("Div");
478var FloorDiv = BinOp.extend("FloorDiv");
479var Mod = BinOp.extend("Mod");
480var Pow = BinOp.extend("Pow");
481var Neg = UnaryOp.extend("Neg");
482var Pos = UnaryOp.extend("Pos");
483var Compare = Node.extend("Compare", { fields: ['expr', 'ops'] });
484var CompareOperand = Node.extend("CompareOperand", {
485 fields: ['expr', 'type']
486});
487
488var CustomTag = Node.extend("CustomTag", {
489 init: function(lineno, colno, name) {
490 this.lineno = lineno;
491 this.colno = colno;
492 this.name = name;
493 }
494});
495
496var CallExtension = Node.extend("CallExtension", {
497 fields: ['extName', 'prop', 'args', 'contentArgs'],
498
499 init: function(ext, prop, args, contentArgs) {
500 this.extName = ext._name || ext;
501 this.prop = prop;
502 this.args = args || new NodeList();
503 this.contentArgs = contentArgs || [];
504 this.autoescape = ext.autoescape;
505 }
506});
507
508var CallExtensionAsync = CallExtension.extend("CallExtensionAsync");
509
510// Print the AST in a nicely formatted tree format for debuggin
511function printNodes(node, indent) {
512 indent = indent || 0;
513
514 // This is hacky, but this is just a debugging function anyway
515 function print(str, indent, inline) {
516 var lines = str.split("\n");
517
518 for(var i=0; i<lines.length; i++) {
519 if(lines[i]) {
520 if((inline && i > 0) || !inline) {
521 for(var j=0; j<indent; j++) {
522 util.print(" ");
523 }
524 }
525 }
526
527 if(i === lines.length-1) {
528 util.print(lines[i]);
529 }
530 else {
531 util.puts(lines[i]);
532 }
533 }
534 }
535
536 print(node.typename + ": ", indent);
537
538 if(node instanceof NodeList) {
539 print('\n');
540 lib.each(node.children, function(n) {
541 printNodes(n, indent + 2);
542 });
543 }
544 else if(node instanceof CallExtension) {
545 print(node.extName + '.' + node.prop);
546 print('\n');
547
548 if(node.args) {
549 printNodes(node.args, indent + 2);
550 }
551
552 if(node.contentArgs) {
553 lib.each(node.contentArgs, function(n) {
554 printNodes(n, indent + 2);
555 });
556 }
557 }
558 else {
559 var nodes = null;
560 var props = null;
561
562 node.iterFields(function(val, field) {
563 if(val instanceof Node) {
564 nodes = nodes || {};
565 nodes[field] = val;
566 }
567 else {
568 props = props || {};
569 props[field] = val;
570 }
571 });
572
573 if(props) {
574 print(util.inspect(props, true, null) + '\n', null, true);
575 }
576 else {
577 print('\n');
578 }
579
580 if(nodes) {
581 for(var k in nodes) {
582 printNodes(nodes[k], indent + 2);
583 }
584 }
585
586 }
587}
588
589// var t = new NodeList(0, 0,
590// [new Value(0, 0, 3),
591// new Value(0, 0, 10),
592// new Pair(0, 0,
593// new Value(0, 0, 'key'),
594// new Value(0, 0, 'value'))]);
595// printNodes(t);
596
597modules['nodes'] = {
598 Node: Node,
599 Root: Root,
600 NodeList: NodeList,
601 Value: Value,
602 Literal: Literal,
603 Symbol: Symbol,
604 Group: Group,
605 Array: Array,
606 Pair: Pair,
607 Dict: Dict,
608 Output: Output,
609 TemplateData: TemplateData,
610 If: If,
611 IfAsync: IfAsync,
612 InlineIf: InlineIf,
613 For: For,
614 AsyncEach: AsyncEach,
615 AsyncAll: AsyncAll,
616 Macro: Macro,
617 Import: Import,
618 FromImport: FromImport,
619 FunCall: FunCall,
620 Filter: Filter,
621 FilterAsync: FilterAsync,
622 KeywordArgs: KeywordArgs,
623 Block: Block,
624 Super: Super,
625 Extends: Extends,
626 Include: Include,
627 Set: Set,
628 LookupVal: LookupVal,
629 BinOp: BinOp,
630 Or: Or,
631 And: And,
632 Not: Not,
633 Add: Add,
634 Sub: Sub,
635 Mul: Mul,
636 Div: Div,
637 FloorDiv: FloorDiv,
638 Mod: Mod,
639 Pow: Pow,
640 Neg: Neg,
641 Pos: Pos,
642 Compare: Compare,
643 CompareOperand: CompareOperand,
644
645 CallExtension: CallExtension,
646 CallExtensionAsync: CallExtensionAsync,
647
648 printNodes: printNodes
649};
650})();
651(function() {
652var lib = modules["lib"];
653var Obj = modules["object"];
654
655// Frames keep track of scoping both at compile-time and run-time so
656// we know how to access variables. Block tags can introduce special
657// variables, for example.
658var Frame = Obj.extend({
659 init: function(parent) {
660 this.variables = {};
661 this.parent = parent;
662 },
663
664 set: function(name, val, resolveUp) {
665 // Allow variables with dots by automatically creating the
666 // nested structure
667 var parts = name.split('.');
668 var obj = this.variables;
669 var frame = this;
670
671 if(resolveUp) {
672 if((frame = this.resolve(parts[0]))) {
673 frame.set(name, val);
674 return;
675 }
676 frame = this;
677 }
678
679 for(var i=0; i<parts.length - 1; i++) {
680 var id = parts[i];
681
682 if(!obj[id]) {
683 obj[id] = {};
684 }
685 obj = obj[id];
686 }
687
688 obj[parts[parts.length - 1]] = val;
689 },
690
691 get: function(name) {
692 var val = this.variables[name];
693 if(val !== undefined && val !== null) {
694 return val;
695 }
696 return null;
697 },
698
699 lookup: function(name) {
700 var p = this.parent;
701 var val = this.variables[name];
702 if(val !== undefined && val !== null) {
703 return val;
704 }
705 return p && p.lookup(name);
706 },
707
708 resolve: function(name) {
709 var p = this.parent;
710 var val = this.variables[name];
711 if(val != null) {
712 return this;
713 }
714 return p && p.resolve(name);
715 },
716
717 push: function() {
718 return new Frame(this);
719 },
720
721 pop: function() {
722 return this.parent;
723 }
724});
725
726function makeMacro(argNames, kwargNames, func) {
727 return function() {
728 var argCount = numArgs(arguments);
729 var args;
730 var kwargs = getKeywordArgs(arguments);
731
732 if(argCount > argNames.length) {
733 args = Array.prototype.slice.call(arguments, 0, argNames.length);
734
735 // Positional arguments that should be passed in as
736 // keyword arguments (essentially default values)
737 var vals = Array.prototype.slice.call(arguments, args.length, argCount);
738 for(var i=0; i<vals.length; i++) {
739 if(i < kwargNames.length) {
740 kwargs[kwargNames[i]] = vals[i];
741 }
742 }
743
744 args.push(kwargs);
745 }
746 else if(argCount < argNames.length) {
747 args = Array.prototype.slice.call(arguments, 0, argCount);
748
749 for(var i=argCount; i<argNames.length; i++) {
750 var arg = argNames[i];
751
752 // Keyword arguments that should be passed as
753 // positional arguments, i.e. the caller explicitly
754 // used the name of a positional arg
755 args.push(kwargs[arg]);
756 delete kwargs[arg];
757 }
758
759 args.push(kwargs);
760 }
761 else {
762 args = arguments;
763 }
764
765 return func.apply(this, args);
766 };
767}
768
769function makeKeywordArgs(obj) {
770 obj.__keywords = true;
771 return obj;
772}
773
774function getKeywordArgs(args) {
775 var len = args.length;
776 if(len) {
777 var lastArg = args[len - 1];
778 if(lastArg && lastArg.hasOwnProperty('__keywords')) {
779 return lastArg;
780 }
781 }
782 return {};
783}
784
785function numArgs(args) {
786 var len = args.length;
787 if(len === 0) {
788 return 0;
789 }
790
791 var lastArg = args[len - 1];
792 if(lastArg && lastArg.hasOwnProperty('__keywords')) {
793 return len - 1;
794 }
795 else {
796 return len;
797 }
798}
799
800// A SafeString object indicates that the string should not be
801// autoescaped. This happens magically because autoescaping only
802// occurs on primitive string objects.
803function SafeString(val) {
804 if(typeof val != 'string') {
805 return val;
806 }
807
808 this.val = val;
809}
810
811SafeString.prototype = Object.create(String.prototype);
812SafeString.prototype.valueOf = function() {
813 return this.val;
814};
815SafeString.prototype.toString = function() {
816 return this.val;
817};
818
819function copySafeness(dest, target) {
820 if(dest instanceof SafeString) {
821 return new SafeString(target);
822 }
823 return target.toString();
824}
825
826function markSafe(val) {
827 var type = typeof val;
828
829 if(type === 'string') {
830 return new SafeString(val);
831 }
832 else if(type !== 'function') {
833 return val;
834 }
835 else {
836 return function() {
837 var ret = val.apply(this, arguments);
838
839 if(typeof ret === 'string') {
840 return new SafeString(ret);
841 }
842
843 return ret;
844 };
845 }
846}
847
848function suppressValue(val, autoescape) {
849 val = (val !== undefined && val !== null) ? val : "";
850
851 if(autoescape && typeof val === "string") {
852 val = lib.escape(val);
853 }
854
855 return val;
856}
857
858function memberLookup(obj, val) {
859 obj = obj || {};
860
861 if(typeof obj[val] === 'function') {
862 return function() {
863 return obj[val].apply(obj, arguments);
864 };
865 }
866
867 return obj[val];
868}
869
870function callWrap(obj, name, args) {
871 if(!obj) {
872 throw new Error('Unable to call `' + name + '`, which is undefined or falsey');
873 }
874 else if(typeof obj !== 'function') {
875 throw new Error('Unable to call `' + name + '`, which is not a function');
876 }
877
878 return obj.apply(this, args);
879}
880
881function contextOrFrameLookup(context, frame, name) {
882 var val = frame.lookup(name);
883 return (val !== undefined && val !== null) ?
884 val :
885 context.lookup(name);
886}
887
888function handleError(error, lineno, colno) {
889 if(error.lineno) {
890 return error;
891 }
892 else {
893 return new lib.TemplateError(error, lineno, colno);
894 }
895}
896
897function asyncEach(arr, dimen, iter, cb) {
898 if(lib.isArray(arr)) {
899 var len = arr.length;
900
901 lib.asyncIter(arr, function(item, i, next) {
902 switch(dimen) {
903 case 1: iter(item, i, len, next); break;
904 case 2: iter(item[0], item[1], i, len, next); break;
905 case 3: iter(item[0], item[1], item[2], i, len, next); break;
906 default:
907 item.push(i, next);
908 iter.apply(this, item);
909 }
910 }, cb);
911 }
912 else {
913 lib.asyncFor(arr, function(key, val, i, len, next) {
914 iter(key, val, i, len, next);
915 }, cb);
916 }
917}
918
919function asyncAll(arr, dimen, func, cb) {
920 var finished = 0;
921 var len;
922 var outputArr;
923
924 function done(i, output) {
925 finished++;
926 outputArr[i] = output;
927
928 if(finished == len) {
929 cb(null, outputArr.join(''));
930 }
931 }
932
933 if(lib.isArray(arr)) {
934 len = arr.length;
935 outputArr = new Array(len);
936
937 if(len == 0) {
938 cb(null, '');
939 }
940 else {
941 for(var i=0; i<arr.length; i++) {
942 var item = arr[i];
943
944 switch(dimen) {
945 case 1: func(item, i, len, done); break;
946 case 2: func(item[0], item[1], i, len, done); break;
947 case 3: func(item[0], item[1], item[2], i, len, done); break;
948 default:
949 item.push(i, done);
950 func.apply(this, item);
951 }
952 }
953 }
954 }
955 else {
956 var keys = lib.keys(arr);
957 len = keys.length;
958 outputArr = new Array(len);
959
960 if(len == 0) {
961 cb(null, '');
962 }
963 else {
964 for(var i=0; i<keys.length; i++) {
965 var k = keys[i];
966 func(k, arr[k], i, len, done);
967 }
968 }
969 }
970}
971
972modules['runtime'] = {
973 Frame: Frame,
974 makeMacro: makeMacro,
975 makeKeywordArgs: makeKeywordArgs,
976 numArgs: numArgs,
977 suppressValue: suppressValue,
978 memberLookup: memberLookup,
979 contextOrFrameLookup: contextOrFrameLookup,
980 callWrap: callWrap,
981 handleError: handleError,
982 isArray: lib.isArray,
983 keys: lib.keys,
984 SafeString: SafeString,
985 copySafeness: copySafeness,
986 markSafe: markSafe,
987 asyncEach: asyncEach,
988 asyncAll: asyncAll
989};
990})();
991(function() {
992var lib = modules["lib"];
993
994var whitespaceChars = " \n\t\r";
995var delimChars = "()[]{}%*-+/#,:|.<>=!";
996var intChars = "0123456789";
997
998var BLOCK_START = "{%";
999var BLOCK_END = "%}";
1000var VARIABLE_START = "{{";
1001var VARIABLE_END = "}}";
1002var COMMENT_START = "{#";
1003var COMMENT_END = "#}";
1004
1005var TOKEN_STRING = "string";
1006var TOKEN_WHITESPACE = "whitespace";
1007var TOKEN_DATA = "data";
1008var TOKEN_BLOCK_START = "block-start";
1009var TOKEN_BLOCK_END = "block-end";
1010var TOKEN_VARIABLE_START = "variable-start";
1011var TOKEN_VARIABLE_END = "variable-end";
1012var TOKEN_COMMENT = "comment";
1013var TOKEN_LEFT_PAREN = "left-paren";
1014var TOKEN_RIGHT_PAREN = "right-paren";
1015var TOKEN_LEFT_BRACKET = "left-bracket";
1016var TOKEN_RIGHT_BRACKET = "right-bracket";
1017var TOKEN_LEFT_CURLY = "left-curly";
1018var TOKEN_RIGHT_CURLY = "right-curly";
1019var TOKEN_OPERATOR = "operator";
1020var TOKEN_COMMA = "comma";
1021var TOKEN_COLON = "colon";
1022var TOKEN_PIPE = "pipe";
1023var TOKEN_INT = "int";
1024var TOKEN_FLOAT = "float";
1025var TOKEN_BOOLEAN = "boolean";
1026var TOKEN_SYMBOL = "symbol";
1027var TOKEN_SPECIAL = "special";
1028
1029function token(type, value, lineno, colno) {
1030 return {
1031 type: type,
1032 value: value,
1033 lineno: lineno,
1034 colno: colno
1035 };
1036}
1037
1038function Tokenizer(str) {
1039 this.str = str;
1040 this.index = 0;
1041 this.len = str.length;
1042 this.lineno = 0;
1043 this.colno = 0;
1044
1045 this.in_code = false;
1046}
1047
1048Tokenizer.prototype.nextToken = function() {
1049 var lineno = this.lineno;
1050 var colno = this.colno;
1051
1052 if(this.in_code) {
1053 // Otherwise, if we are in a block parse it as code
1054 var cur = this.current();
1055 var tok;
1056
1057 if(this.is_finished()) {
1058 // We have nothing else to parse
1059 return null;
1060 }
1061 else if(cur == "\"" || cur == "'") {
1062 // We've hit a string
1063 return token(TOKEN_STRING, this.parseString(cur), lineno, colno);
1064 }
1065 else if((tok = this._extract(whitespaceChars))) {
1066 // We hit some whitespace
1067 return token(TOKEN_WHITESPACE, tok, lineno, colno);
1068 }
1069 else if((tok = this._extractString(BLOCK_END)) ||
1070 (tok = this._extractString('-' + BLOCK_END))) {
1071 // Special check for the block end tag
1072 //
1073 // It is a requirement that start and end tags are composed of
1074 // delimiter characters (%{}[] etc), and our code always
1075 // breaks on delimiters so we can assume the token parsing
1076 // doesn't consume these elsewhere
1077 this.in_code = false;
1078 return token(TOKEN_BLOCK_END, tok, lineno, colno);
1079 }
1080 else if((tok = this._extractString(VARIABLE_END))) {
1081 // Special check for variable end tag (see above)
1082 this.in_code = false;
1083 return token(TOKEN_VARIABLE_END, tok, lineno, colno);
1084 }
1085 else if(delimChars.indexOf(cur) != -1) {
1086 // We've hit a delimiter (a special char like a bracket)
1087 this.forward();
1088 var complexOps = ['==', '!=', '<=', '>=', '//', '**'];
1089 var curComplex = cur + this.current();
1090 var type;
1091
1092 if(complexOps.indexOf(curComplex) !== -1) {
1093 this.forward();
1094 cur = curComplex;
1095 }
1096
1097 switch(cur) {
1098 case "(": type = TOKEN_LEFT_PAREN; break;
1099 case ")": type = TOKEN_RIGHT_PAREN; break;
1100 case "[": type = TOKEN_LEFT_BRACKET; break;
1101 case "]": type = TOKEN_RIGHT_BRACKET; break;
1102 case "{": type = TOKEN_LEFT_CURLY; break;
1103 case "}": type = TOKEN_RIGHT_CURLY; break;
1104 case ",": type = TOKEN_COMMA; break;
1105 case ":": type = TOKEN_COLON; break;
1106 case "|": type = TOKEN_PIPE; break;
1107 default: type = TOKEN_OPERATOR;
1108 }
1109
1110 return token(type, cur, lineno, colno);
1111 }
1112 else {
1113 // We are not at whitespace or a delimiter, so extract the
1114 // text and parse it
1115 tok = this._extractUntil(whitespaceChars + delimChars);
1116
1117 if(tok.match(/^[-+]?[0-9]+$/)) {
1118 if(this.current() == '.') {
1119 this.forward();
1120 var dec = this._extract(intChars);
1121 return token(TOKEN_FLOAT, tok + '.' + dec, lineno, colno);
1122 }
1123 else {
1124 return token(TOKEN_INT, tok, lineno, colno);
1125 }
1126 }
1127 else if(tok.match(/^(true|false)$/)) {
1128 return token(TOKEN_BOOLEAN, tok, lineno, colno);
1129 }
1130 else if(tok) {
1131 return token(TOKEN_SYMBOL, tok, lineno, colno);
1132 }
1133 else {
1134 throw new Error("Unexpected value while parsing: " + tok);
1135 }
1136 }
1137 }
1138 else {
1139 // Parse out the template text, breaking on tag
1140 // delimiters because we need to look for block/variable start
1141 // tags (don't use the full delimChars for optimization)
1142 var beginChars = (BLOCK_START.charAt(0) +
1143 VARIABLE_START.charAt(0) +
1144 COMMENT_START.charAt(0) +
1145 COMMENT_END.charAt(0));
1146 var tok;
1147
1148 if(this.is_finished()) {
1149 return null;
1150 }
1151 else if((tok = this._extractString(BLOCK_START + '-')) ||
1152 (tok = this._extractString(BLOCK_START))) {
1153 this.in_code = true;
1154 return token(TOKEN_BLOCK_START, tok, lineno, colno);
1155 }
1156 else if((tok = this._extractString(VARIABLE_START))) {
1157 this.in_code = true;
1158 return token(TOKEN_VARIABLE_START, tok, lineno, colno);
1159 }
1160 else {
1161 tok = '';
1162 var data;
1163 var in_comment = false;
1164
1165 if(this._matches(COMMENT_START)) {
1166 in_comment = true;
1167 tok = this._extractString(COMMENT_START);
1168 }
1169
1170 // Continually consume text, breaking on the tag delimiter
1171 // characters and checking to see if it's a start tag.
1172 //
1173 // We could hit the end of the template in the middle of
1174 // our looping, so check for the null return value from
1175 // _extractUntil
1176 while((data = this._extractUntil(beginChars)) !== null) {
1177 tok += data;
1178
1179 if((this._matches(BLOCK_START) ||
1180 this._matches(VARIABLE_START) ||
1181 this._matches(COMMENT_START)) &&
1182 !in_comment) {
1183 // If it is a start tag, stop looping
1184 break;
1185 }
1186 else if(this._matches(COMMENT_END)) {
1187 if(!in_comment) {
1188 throw new Error("unexpected end of comment");
1189 }
1190 tok += this._extractString(COMMENT_END);
1191 break;
1192 }
1193 else {
1194 // It does not match any tag, so add the character and
1195 // carry on
1196 tok += this.current();
1197 this.forward();
1198 }
1199 }
1200
1201 if(data === null && in_comment) {
1202 throw new Error("expected end of comment, got end of file");
1203 }
1204
1205 return token(in_comment ? TOKEN_COMMENT : TOKEN_DATA,
1206 tok,
1207 lineno,
1208 colno);
1209 }
1210 }
1211
1212 throw new Error("Could not parse text");
1213};
1214
1215Tokenizer.prototype.parseString = function(delimiter) {
1216 this.forward();
1217
1218 var lineno = this.lineno;
1219 var colno = this.colno;
1220 var str = "";
1221
1222 while(!this.is_finished() && this.current() != delimiter) {
1223 var cur = this.current();
1224
1225 if(cur == "\\") {
1226 this.forward();
1227 switch(this.current()) {
1228 case "n": str += "\n"; break;
1229 case "t": str += "\t"; break;
1230 case "r": str += "\r"; break;
1231 default:
1232 str += this.current();
1233 }
1234 this.forward();
1235 }
1236 else {
1237 str += cur;
1238 this.forward();
1239 }
1240 }
1241
1242 this.forward();
1243 return str;
1244};
1245
1246Tokenizer.prototype._matches = function(str) {
1247 if(this.index + str.length > this.length) {
1248 return null;
1249 }
1250
1251 var m = this.str.slice(this.index, this.index + str.length);
1252 return m == str;
1253};
1254
1255Tokenizer.prototype._extractString = function(str) {
1256 if(this._matches(str)) {
1257 this.index += str.length;
1258 return str;
1259 }
1260 return null;
1261};
1262
1263Tokenizer.prototype._extractUntil = function(charString) {
1264 // Extract all non-matching chars, with the default matching set
1265 // to everything
1266 return this._extractMatching(true, charString || "");
1267};
1268
1269Tokenizer.prototype._extract = function(charString) {
1270 // Extract all matching chars (no default, so charString must be
1271 // explicit)
1272 return this._extractMatching(false, charString);
1273};
1274
1275Tokenizer.prototype._extractMatching = function (breakOnMatch, charString) {
1276 // Pull out characters until a breaking char is hit.
1277 // If breakOnMatch is false, a non-matching char stops it.
1278 // If breakOnMatch is true, a matching char stops it.
1279
1280 if(this.is_finished()) {
1281 return null;
1282 }
1283
1284 var first = charString.indexOf(this.current());
1285
1286 // Only proceed if the first character doesn't meet our condition
1287 if((breakOnMatch && first == -1) ||
1288 (!breakOnMatch && first != -1)) {
1289 var t = this.current();
1290 this.forward();
1291
1292 // And pull out all the chars one at a time until we hit a
1293 // breaking char
1294 var idx = charString.indexOf(this.current());
1295
1296 while(((breakOnMatch && idx == -1) ||
1297 (!breakOnMatch && idx != -1)) && !this.is_finished()) {
1298 t += this.current();
1299 this.forward();
1300
1301 idx = charString.indexOf(this.current());
1302 }
1303
1304 return t;
1305 }
1306
1307 return "";
1308};
1309
1310Tokenizer.prototype.is_finished = function() {
1311 return this.index >= this.len;
1312};
1313
1314Tokenizer.prototype.forwardN = function(n) {
1315 for(var i=0; i<n; i++) {
1316 this.forward();
1317 }
1318};
1319
1320Tokenizer.prototype.forward = function() {
1321 this.index++;
1322
1323 if(this.previous() == "\n") {
1324 this.lineno++;
1325 this.colno = 0;
1326 }
1327 else {
1328 this.colno++;
1329 }
1330};
1331
1332Tokenizer.prototype.back = function() {
1333 this.index--;
1334
1335 if(this.current() == "\n") {
1336 this.lineno--;
1337
1338 var idx = this.src.lastIndexOf("\n", this.index-1);
1339 if(idx == -1) {
1340 this.colno = this.index;
1341 }
1342 else {
1343 this.colno = this.index - idx;
1344 }
1345 }
1346 else {
1347 this.colno--;
1348 }
1349};
1350
1351Tokenizer.prototype.current = function() {
1352 if(!this.is_finished()) {
1353 return this.str.charAt(this.index);
1354 }
1355 return "";
1356};
1357
1358Tokenizer.prototype.previous = function() {
1359 return this.str.charAt(this.index-1);
1360};
1361
1362modules['lexer'] = {
1363 lex: function(src) {
1364 return new Tokenizer(src);
1365 },
1366
1367 setTags: function(tags) {
1368 BLOCK_START = tags.blockStart || BLOCK_START;
1369 BLOCK_END = tags.blockEnd || BLOCK_END;
1370 VARIABLE_START = tags.variableStart || VARIABLE_START;
1371 VARIABLE_END = tags.variableEnd || VARIABLE_END;
1372 COMMENT_START = tags.commentStart || COMMENT_START;
1373 COMMENT_END = tags.commentEnd || COMMENT_END;
1374 },
1375
1376 TOKEN_STRING: TOKEN_STRING,
1377 TOKEN_WHITESPACE: TOKEN_WHITESPACE,
1378 TOKEN_DATA: TOKEN_DATA,
1379 TOKEN_BLOCK_START: TOKEN_BLOCK_START,
1380 TOKEN_BLOCK_END: TOKEN_BLOCK_END,
1381 TOKEN_VARIABLE_START: TOKEN_VARIABLE_START,
1382 TOKEN_VARIABLE_END: TOKEN_VARIABLE_END,
1383 TOKEN_COMMENT: TOKEN_COMMENT,
1384 TOKEN_LEFT_PAREN: TOKEN_LEFT_PAREN,
1385 TOKEN_RIGHT_PAREN: TOKEN_RIGHT_PAREN,
1386 TOKEN_LEFT_BRACKET: TOKEN_LEFT_BRACKET,
1387 TOKEN_RIGHT_BRACKET: TOKEN_RIGHT_BRACKET,
1388 TOKEN_LEFT_CURLY: TOKEN_LEFT_CURLY,
1389 TOKEN_RIGHT_CURLY: TOKEN_RIGHT_CURLY,
1390 TOKEN_OPERATOR: TOKEN_OPERATOR,
1391 TOKEN_COMMA: TOKEN_COMMA,
1392 TOKEN_COLON: TOKEN_COLON,
1393 TOKEN_PIPE: TOKEN_PIPE,
1394 TOKEN_INT: TOKEN_INT,
1395 TOKEN_FLOAT: TOKEN_FLOAT,
1396 TOKEN_BOOLEAN: TOKEN_BOOLEAN,
1397 TOKEN_SYMBOL: TOKEN_SYMBOL,
1398 TOKEN_SPECIAL: TOKEN_SPECIAL
1399};
1400})();
1401(function() {
1402var lexer = modules["lexer"];
1403var nodes = modules["nodes"];
1404var Object = modules["object"];
1405var lib = modules["lib"];
1406
1407var Parser = Object.extend({
1408 init: function (tokens) {
1409 this.tokens = tokens;
1410 this.peeked = null;
1411 this.breakOnBlocks = null;
1412 this.dropLeadingWhitespace = false;
1413
1414 this.extensions = [];
1415 },
1416
1417 nextToken: function (withWhitespace) {
1418 var tok;
1419
1420 if(this.peeked) {
1421 if(!withWhitespace && this.peeked.type == lexer.TOKEN_WHITESPACE) {
1422 this.peeked = null;
1423 }
1424 else {
1425 tok = this.peeked;
1426 this.peeked = null;
1427 return tok;
1428 }
1429 }
1430
1431 tok = this.tokens.nextToken();
1432
1433 if(!withWhitespace) {
1434 while(tok && tok.type == lexer.TOKEN_WHITESPACE) {
1435 tok = this.tokens.nextToken();
1436 }
1437 }
1438
1439 return tok;
1440 },
1441
1442 peekToken: function () {
1443 this.peeked = this.peeked || this.nextToken();
1444 return this.peeked;
1445 },
1446
1447 pushToken: function(tok) {
1448 if(this.peeked) {
1449 throw new Error("pushToken: can only push one token on between reads");
1450 }
1451 this.peeked = tok;
1452 },
1453
1454 fail: function (msg, lineno, colno) {
1455 if((lineno === undefined || colno === undefined) && this.peekToken()) {
1456 var tok = this.peekToken();
1457 lineno = tok.lineno;
1458 colno = tok.colno;
1459 }
1460 if (lineno !== undefined) lineno += 1;
1461 if (colno !== undefined) colno += 1;
1462
1463 throw new lib.TemplateError(msg, lineno, colno);
1464 },
1465
1466 skip: function(type) {
1467 var tok = this.nextToken();
1468 if(!tok || tok.type != type) {
1469 this.pushToken(tok);
1470 return false;
1471 }
1472 return true;
1473 },
1474
1475 expect: function(type) {
1476 var tok = this.nextToken();
1477 if(tok.type !== type) {
1478 this.fail('expected ' + type + ', got ' + tok.type,
1479 tok.lineno,
1480 tok.colno);
1481 }
1482 return tok;
1483 },
1484
1485 skipValue: function(type, val) {
1486 var tok = this.nextToken();
1487 if(!tok || tok.type != type || tok.value != val) {
1488 this.pushToken(tok);
1489 return false;
1490 }
1491 return true;
1492 },
1493
1494 skipWhitespace: function () {
1495 return this.skip(lexer.TOKEN_WHITESPACE);
1496 },
1497
1498 skipSymbol: function(val) {
1499 return this.skipValue(lexer.TOKEN_SYMBOL, val);
1500 },
1501
1502 advanceAfterBlockEnd: function(name) {
1503 if(!name) {
1504 var tok = this.peekToken();
1505
1506 if(!tok) {
1507 this.fail('unexpected end of file');
1508 }
1509
1510 if(tok.type != lexer.TOKEN_SYMBOL) {
1511 this.fail("advanceAfterBlockEnd: expected symbol token or " +
1512 "explicit name to be passed");
1513 }
1514
1515 name = this.nextToken().value;
1516 }
1517
1518 var tok = this.nextToken();
1519
1520 if(tok && tok.type == lexer.TOKEN_BLOCK_END) {
1521 if(tok.value.charAt(0) === '-') {
1522 this.dropLeadingWhitespace = true;
1523 }
1524 }
1525 else {
1526 this.fail("expected block end in " + name + " statement");
1527 }
1528 },
1529
1530 advanceAfterVariableEnd: function() {
1531 if(!this.skip(lexer.TOKEN_VARIABLE_END)) {
1532 this.fail("expected variable end");
1533 }
1534 },
1535
1536 parseFor: function() {
1537 var forTok = this.peekToken();
1538 var node;
1539 var endBlock;
1540
1541 if(this.skipSymbol('for')) {
1542 node = new nodes.For(forTok.lineno, forTok.colno);
1543 endBlock = 'endfor';
1544 }
1545 else if(this.skipSymbol('asyncEach')) {
1546 node = new nodes.AsyncEach(forTok.lineno, forTok.colno);
1547 endBlock = 'endeach';
1548 }
1549 else if(this.skipSymbol('asyncAll')) {
1550 node = new nodes.AsyncAll(forTok.lineno, forTok.colno);
1551 endBlock = 'endall';
1552 }
1553 else {
1554 this.fail("parseFor: expected for{Async}", forTok.lineno, forTok.colno);
1555 }
1556
1557 node.name = this.parsePrimary();
1558
1559 if(!(node.name instanceof nodes.Symbol)) {
1560 this.fail('parseFor: variable name expected for loop');
1561 }
1562
1563 var type = this.peekToken().type;
1564 if(type == lexer.TOKEN_COMMA) {
1565 // key/value iteration
1566 var key = node.name;
1567 node.name = new nodes.Array(key.lineno, key.colno);
1568 node.name.addChild(key);
1569
1570 while(this.skip(lexer.TOKEN_COMMA)) {
1571 var prim = this.parsePrimary();
1572 node.name.addChild(prim);
1573 }
1574 }
1575
1576 if(!this.skipSymbol('in')) {
1577 this.fail('parseFor: expected "in" keyword for loop',
1578 forTok.lineno,
1579 forTok.colno);
1580 }
1581
1582 node.arr = this.parseExpression();
1583 this.advanceAfterBlockEnd(forTok.value);
1584
1585 node.body = this.parseUntilBlocks(endBlock);
1586 this.advanceAfterBlockEnd();
1587
1588 return node;
1589 },
1590
1591 parseMacro: function() {
1592 var macroTok = this.peekToken();
1593 if(!this.skipSymbol('macro')) {
1594 this.fail("expected macro");
1595 }
1596
1597 var name = this.parsePrimary(true);
1598 var args = this.parseSignature();
1599 var node = new nodes.Macro(macroTok.lineno,
1600 macroTok.colno,
1601 name,
1602 args);
1603
1604 this.advanceAfterBlockEnd(macroTok.value);
1605 node.body = this.parseUntilBlocks('endmacro');
1606 this.advanceAfterBlockEnd();
1607
1608 return node;
1609 },
1610
1611 parseImport: function() {
1612 var importTok = this.peekToken();
1613 if(!this.skipSymbol('import')) {
1614 this.fail("parseImport: expected import",
1615 importTok.lineno,
1616 importTok.colno);
1617 }
1618
1619 var template = this.parseExpression();
1620
1621 if(!this.skipSymbol('as')) {
1622 this.fail('parseImport: expected "as" keyword',
1623 importTok.lineno,
1624 importTok.colno);
1625 }
1626
1627 var target = this.parsePrimary();
1628 var node = new nodes.Import(importTok.lineno,
1629 importTok.colno,
1630 template,
1631 target);
1632 this.advanceAfterBlockEnd(importTok.value);
1633
1634 return node;
1635 },
1636
1637 parseFrom: function() {
1638 var fromTok = this.peekToken();
1639 if(!this.skipSymbol('from')) {
1640 this.fail("parseFrom: expected from");
1641 }
1642
1643 var template = this.parsePrimary();
1644 var node = new nodes.FromImport(fromTok.lineno,
1645 fromTok.colno,
1646 template,
1647 new nodes.NodeList());
1648
1649 if(!this.skipSymbol('import')) {
1650 this.fail("parseFrom: expected import",
1651 fromTok.lineno,
1652 fromTok.colno);
1653 }
1654
1655 var names = node.names;
1656
1657 while(1) {
1658 var nextTok = this.peekToken();
1659 if(nextTok.type == lexer.TOKEN_BLOCK_END) {
1660 if(!names.children.length) {
1661 this.fail('parseFrom: Expected at least one import name',
1662 fromTok.lineno,
1663 fromTok.colno);
1664 }
1665
1666 // Since we are manually advancing past the block end,
1667 // need to keep track of whitespace control (normally
1668 // this is done in `advanceAfterBlockEnd`
1669 if(nextTok.value.charAt(0) == '-') {
1670 this.dropLeadingWhitespace = true;
1671 }
1672
1673 this.nextToken();
1674 break;
1675 }
1676
1677 if(names.children.length > 0 && !this.skip(lexer.TOKEN_COMMA)) {
1678 this.fail('parseFrom: expected comma',
1679 fromTok.lineno,
1680 fromTok.colno);
1681 }
1682
1683 var name = this.parsePrimary();
1684 if(name.value.charAt(0) == '_') {
1685 this.fail('parseFrom: names starting with an underscore ' +
1686 'cannot be imported',
1687 name.lineno,
1688 name.colno);
1689 }
1690
1691 if(this.skipSymbol('as')) {
1692 var alias = this.parsePrimary();
1693 names.addChild(new nodes.Pair(name.lineno,
1694 name.colno,
1695 name,
1696 alias));
1697 }
1698 else {
1699 names.addChild(name);
1700 }
1701 }
1702
1703 return node;
1704 },
1705
1706 parseBlock: function() {
1707 var tag = this.peekToken();
1708 if(!this.skipSymbol('block')) {
1709 this.fail('parseBlock: expected block', tag.lineno, tag.colno);
1710 }
1711
1712 var node = new nodes.Block(tag.lineno, tag.colno);
1713
1714 node.name = this.parsePrimary();
1715 if(!(node.name instanceof nodes.Symbol)) {
1716 this.fail('parseBlock: variable name expected',
1717 tag.lineno,
1718 tag.colno);
1719 }
1720
1721 this.advanceAfterBlockEnd(tag.value);
1722
1723 node.body = this.parseUntilBlocks('endblock');
1724
1725 if(!this.peekToken()) {
1726 this.fail('parseBlock: expected endblock, got end of file');
1727 }
1728
1729 this.advanceAfterBlockEnd();
1730
1731 return node;
1732 },
1733
1734 parseTemplateRef: function(tagName, nodeType) {
1735 var tag = this.peekToken();
1736 if(!this.skipSymbol(tagName)) {
1737 this.fail('parseTemplateRef: expected '+ tagName);
1738 }
1739
1740 var node = new nodeType(tag.lineno, tag.colno);
1741 node.template = this.parseExpression();
1742
1743 this.advanceAfterBlockEnd(tag.value);
1744 return node;
1745 },
1746
1747 parseExtends: function() {
1748 return this.parseTemplateRef('extends', nodes.Extends);
1749 },
1750
1751 parseInclude: function() {
1752 return this.parseTemplateRef('include', nodes.Include);
1753 },
1754
1755 parseIf: function() {
1756 var tag = this.peekToken();
1757 var node;
1758
1759 if(this.skipSymbol('if') || this.skipSymbol('elif')) {
1760 node = new nodes.If(tag.lineno, tag.colno);
1761 }
1762 else if(this.skipSymbol('ifAsync')) {
1763 node = new nodes.IfAsync(tag.lineno, tag.colno);
1764 }
1765 else {
1766 this.fail("parseIf: expected if or elif",
1767 tag.lineno,
1768 tag.colno);
1769 }
1770
1771 node.cond = this.parseExpression();
1772 this.advanceAfterBlockEnd(tag.value);
1773
1774 node.body = this.parseUntilBlocks('elif', 'else', 'endif');
1775 var tok = this.peekToken();
1776
1777 switch(tok && tok.value) {
1778 case "elif":
1779 node.else_ = this.parseIf();
1780 break;
1781 case "else":
1782 this.advanceAfterBlockEnd();
1783 node.else_ = this.parseUntilBlocks("endif");
1784 this.advanceAfterBlockEnd();
1785 break;
1786 case "endif":
1787 node.else_ = null;
1788 this.advanceAfterBlockEnd();
1789 break;
1790 default:
1791 this.fail('parseIf: expected endif, else, or endif, ' +
1792 'got end of file');
1793 }
1794
1795 return node;
1796 },
1797
1798 parseSet: function() {
1799 var tag = this.peekToken();
1800 if(!this.skipSymbol('set')) {
1801 this.fail('parseSet: expected set', tag.lineno, tag.colno);
1802 }
1803
1804 var node = new nodes.Set(tag.lineno, tag.colno, []);
1805
1806 var target;
1807 while((target = this.parsePrimary())) {
1808 node.targets.push(target);
1809
1810 if(!this.skip(lexer.TOKEN_COMMA)) {
1811 break;
1812 }
1813 }
1814
1815 if(!this.skipValue(lexer.TOKEN_OPERATOR, '=')) {
1816 this.fail('parseSet: expected = in set tag',
1817 tag.lineno,
1818 tag.colno);
1819 }
1820
1821 node.value = this.parseExpression();
1822 this.advanceAfterBlockEnd(tag.value);
1823
1824 return node;
1825 },
1826
1827 parseStatement: function () {
1828 var tok = this.peekToken();
1829 var node;
1830
1831 if(tok.type != lexer.TOKEN_SYMBOL) {
1832 this.fail('tag name expected', tok.lineno, tok.colno);
1833 }
1834
1835 if(this.breakOnBlocks &&
1836 this.breakOnBlocks.indexOf(tok.value) !== -1) {
1837 return null;
1838 }
1839
1840 switch(tok.value) {
1841 case 'raw': return this.parseRaw();
1842 case 'if':
1843 case 'ifAsync':
1844 return this.parseIf();
1845 case 'for':
1846 case 'asyncEach':
1847 case 'asyncAll':
1848 return this.parseFor();
1849 case 'block': return this.parseBlock();
1850 case 'extends': return this.parseExtends();
1851 case 'include': return this.parseInclude();
1852 case 'set': return this.parseSet();
1853 case 'macro': return this.parseMacro();
1854 case 'import': return this.parseImport();
1855 case 'from': return this.parseFrom();
1856 default:
1857 if (this.extensions.length) {
1858 for (var i = 0; i < this.extensions.length; i++) {
1859 var ext = this.extensions[i];
1860 if ((ext.tags || []).indexOf(tok.value) !== -1) {
1861 return ext.parse(this, nodes, lexer);
1862 }
1863 }
1864 }
1865 this.fail('unknown block tag: ' + tok.value, tok.lineno, tok.colno);
1866 }
1867
1868 return node;
1869 },
1870
1871 parseRaw: function() {
1872 this.advanceAfterBlockEnd();
1873 var str = '';
1874 var begun = this.peekToken();
1875
1876 while(1) {
1877 // Passing true gives us all the whitespace tokens as
1878 // well, which are usually ignored.
1879 var tok = this.nextToken(true);
1880
1881 if(!tok) {
1882 this.fail("expected endraw, got end of file");
1883 }
1884
1885 if(tok.type == lexer.TOKEN_BLOCK_START) {
1886 // We need to look for the `endraw` block statement,
1887 // which involves a lookahead so carefully keep track
1888 // of whitespace
1889 var ws = null;
1890 var name = this.nextToken(true);
1891
1892 if(name.type == lexer.TOKEN_WHITESPACE) {
1893 ws = name;
1894 name = this.nextToken();
1895 }
1896
1897 if(name.type == lexer.TOKEN_SYMBOL &&
1898 name.value == 'endraw') {
1899 this.advanceAfterBlockEnd(name.value);
1900 break;
1901 }
1902 else {
1903 str += tok.value;
1904 if(ws) {
1905 str += ws.value;
1906 }
1907 str += name.value;
1908 }
1909 }
1910 else if(tok.type === lexer.TOKEN_STRING) {
1911 str += '"' + tok.value + '"';
1912 }
1913 else {
1914 str += tok.value;
1915 }
1916 }
1917
1918
1919 var output = new nodes.Output(
1920 begun.lineno,
1921 begun.colno,
1922 [new nodes.TemplateData(begun.lineno, begun.colno, str)]
1923 );
1924
1925 return output;
1926 },
1927
1928 parsePostfix: function(node) {
1929 var tok = this.peekToken();
1930
1931 while(tok) {
1932 if(tok.type == lexer.TOKEN_LEFT_PAREN) {
1933 // Function call
1934 node = new nodes.FunCall(tok.lineno,
1935 tok.colno,
1936 node,
1937 this.parseSignature());
1938 }
1939 else if(tok.type == lexer.TOKEN_LEFT_BRACKET) {
1940 // Reference
1941 var lookup = this.parseAggregate();
1942 if(lookup.children.length > 1) {
1943 this.fail('invalid index');
1944 }
1945
1946 node = new nodes.LookupVal(tok.lineno,
1947 tok.colno,
1948 node,
1949 lookup.children[0]);
1950 }
1951 else if(tok.type == lexer.TOKEN_OPERATOR && tok.value == '.') {
1952 // Reference
1953 this.nextToken();
1954 var val = this.nextToken();
1955
1956 if(val.type != lexer.TOKEN_SYMBOL) {
1957 this.fail('expected name as lookup value, got ' + val.value,
1958 val.lineno,
1959 val.colno);
1960 }
1961
1962 // Make a literal string because it's not a variable
1963 // reference
1964 var lookup = new nodes.Literal(val.lineno,
1965 val.colno,
1966 val.value);
1967
1968 node = new nodes.LookupVal(tok.lineno,
1969 tok.colno,
1970 node,
1971 lookup);
1972 }
1973 else {
1974 break;
1975 }
1976
1977 tok = this.peekToken();
1978 }
1979
1980 return node;
1981 },
1982
1983 parseExpression: function() {
1984 var node = this.parseInlineIf();
1985 return node;
1986 },
1987
1988 parseInlineIf: function() {
1989 var node = this.parseOr();
1990 if(this.skipSymbol('if')) {
1991 var cond_node = this.parseOr();
1992 var body_node = node;
1993 node = new nodes.InlineIf(node.lineno, node.colno);
1994 node.body = body_node;
1995 node.cond = cond_node;
1996 if(this.skipSymbol('else')) {
1997 node.else_ = this.parseOr();
1998 } else {
1999 node.else_ = null;
2000 }
2001 }
2002
2003 return node;
2004 },
2005
2006 parseOr: function() {
2007 var node = this.parseAnd();
2008 while(this.skipSymbol('or')) {
2009 var node2 = this.parseAnd();
2010 node = new nodes.Or(node.lineno,
2011 node.colno,
2012 node,
2013 node2);
2014 }
2015 return node;
2016 },
2017
2018 parseAnd: function() {
2019 var node = this.parseNot();
2020 while(this.skipSymbol('and')) {
2021 var node2 = this.parseNot();
2022 node = new nodes.And(node.lineno,
2023 node.colno,
2024 node,
2025 node2);
2026 }
2027 return node;
2028 },
2029
2030 parseNot: function() {
2031 var tok = this.peekToken();
2032 if(this.skipSymbol('not')) {
2033 return new nodes.Not(tok.lineno,
2034 tok.colno,
2035 this.parseNot());
2036 }
2037 return this.parseCompare();
2038 },
2039
2040 parseCompare: function() {
2041 var compareOps = ['==', '!=', '<', '>', '<=', '>='];
2042 var expr = this.parseAdd();
2043 var ops = [];
2044
2045 while(1) {
2046 var tok = this.nextToken();
2047
2048 if(!tok) {
2049 break;
2050 }
2051 else if(compareOps.indexOf(tok.value) !== -1) {
2052 ops.push(new nodes.CompareOperand(tok.lineno,
2053 tok.colno,
2054 this.parseAdd(),
2055 tok.value));
2056 }
2057 else if(tok.type == lexer.TOKEN_SYMBOL &&
2058 tok.value == 'in') {
2059 ops.push(new nodes.CompareOperand(tok.lineno,
2060 tok.colno,
2061 this.parseAdd(),
2062 'in'));
2063 }
2064 else if(tok.type == lexer.TOKEN_SYMBOL &&
2065 tok.value == 'not' &&
2066 this.skipSymbol('in')) {
2067 ops.push(new nodes.CompareOperand(tok.lineno,
2068 tok.colno,
2069 this.parseAdd(),
2070 'notin'));
2071 }
2072 else {
2073 this.pushToken(tok);
2074 break;
2075 }
2076 }
2077
2078 if(ops.length) {
2079 return new nodes.Compare(ops[0].lineno,
2080 ops[0].colno,
2081 expr,
2082 ops);
2083 }
2084 else {
2085 return expr;
2086 }
2087 },
2088
2089 parseAdd: function() {
2090 var node = this.parseSub();
2091 while(this.skipValue(lexer.TOKEN_OPERATOR, '+')) {
2092 var node2 = this.parseSub();
2093 node = new nodes.Add(node.lineno,
2094 node.colno,
2095 node,
2096 node2);
2097 }
2098 return node;
2099 },
2100
2101 parseSub: function() {
2102 var node = this.parseMul();
2103 while(this.skipValue(lexer.TOKEN_OPERATOR, '-')) {
2104 var node2 = this.parseMul();
2105 node = new nodes.Sub(node.lineno,
2106 node.colno,
2107 node,
2108 node2);
2109 }
2110 return node;
2111 },
2112
2113 parseMul: function() {
2114 var node = this.parseDiv();
2115 while(this.skipValue(lexer.TOKEN_OPERATOR, '*')) {
2116 var node2 = this.parseDiv();
2117 node = new nodes.Mul(node.lineno,
2118 node.colno,
2119 node,
2120 node2);
2121 }
2122 return node;
2123 },
2124
2125 parseDiv: function() {
2126 var node = this.parseFloorDiv();
2127 while(this.skipValue(lexer.TOKEN_OPERATOR, '/')) {
2128 var node2 = this.parseFloorDiv();
2129 node = new nodes.Div(node.lineno,
2130 node.colno,
2131 node,
2132 node2);
2133 }
2134 return node;
2135 },
2136
2137 parseFloorDiv: function() {
2138 var node = this.parseMod();
2139 while(this.skipValue(lexer.TOKEN_OPERATOR, '//')) {
2140 var node2 = this.parseMod();
2141 node = new nodes.FloorDiv(node.lineno,
2142 node.colno,
2143 node,
2144 node2);
2145 }
2146 return node;
2147 },
2148
2149 parseMod: function() {
2150 var node = this.parsePow();
2151 while(this.skipValue(lexer.TOKEN_OPERATOR, '%')) {
2152 var node2 = this.parsePow();
2153 node = new nodes.Mod(node.lineno,
2154 node.colno,
2155 node,
2156 node2);
2157 }
2158 return node;
2159 },
2160
2161 parsePow: function() {
2162 var node = this.parseUnary();
2163 while(this.skipValue(lexer.TOKEN_OPERATOR, '**')) {
2164 var node2 = this.parseUnary();
2165 node = new nodes.Pow(node.lineno,
2166 node.colno,
2167 node,
2168 node2);
2169 }
2170 return node;
2171 },
2172
2173 parseUnary: function(noFilters) {
2174 var tok = this.peekToken();
2175 var node;
2176
2177 if(this.skipValue(lexer.TOKEN_OPERATOR, '-')) {
2178 node = new nodes.Neg(tok.lineno,
2179 tok.colno,
2180 this.parseUnary(true));
2181 }
2182 else if(this.skipValue(lexer.TOKEN_OPERATOR, '+')) {
2183 node = new nodes.Pos(tok.lineno,
2184 tok.colno,
2185 this.parseUnary(true));
2186 }
2187 else {
2188 node = this.parsePrimary();
2189 }
2190
2191 if(!noFilters) {
2192 node = this.parseFilter(node);
2193 }
2194
2195 return node;
2196 },
2197
2198 parsePrimary: function (noPostfix) {
2199 var tok = this.nextToken();
2200 var val = null;
2201 var node = null;
2202
2203 if(!tok) {
2204 this.fail('expected expression, got end of file');
2205 }
2206 else if(tok.type == lexer.TOKEN_STRING) {
2207 val = tok.value;
2208 }
2209 else if(tok.type == lexer.TOKEN_INT) {
2210 val = parseInt(tok.value, 10);
2211 }
2212 else if(tok.type == lexer.TOKEN_FLOAT) {
2213 val = parseFloat(tok.value);
2214 }
2215 else if(tok.type == lexer.TOKEN_BOOLEAN) {
2216 if(tok.value == "true") {
2217 val = true;
2218 }
2219 else if(tok.value == "false") {
2220 val = false;
2221 }
2222 else {
2223 this.fail("invalid boolean: " + tok.val,
2224 tok.lineno,
2225 tok.colno);
2226 }
2227 }
2228
2229 if(val !== null) {
2230 node = new nodes.Literal(tok.lineno, tok.colno, val);
2231 }
2232 else if(tok.type == lexer.TOKEN_SYMBOL) {
2233 node = new nodes.Symbol(tok.lineno, tok.colno, tok.value);
2234
2235 if(!noPostfix) {
2236 node = this.parsePostfix(node);
2237 }
2238 }
2239 else {
2240 // See if it's an aggregate type, we need to push the
2241 // current delimiter token back on
2242 this.pushToken(tok);
2243 node = this.parseAggregate();
2244 }
2245
2246 if(node) {
2247 return node;
2248 }
2249 else {
2250 this.fail('unexpected token: ' + tok.value,
2251 tok.lineno,
2252 tok.colno);
2253 }
2254 },
2255
2256 parseFilter: function(node) {
2257 while(this.skip(lexer.TOKEN_PIPE)) {
2258 var tok = this.expect(lexer.TOKEN_SYMBOL);
2259 var name = tok.value;
2260
2261 while(this.skipValue(lexer.TOKEN_OPERATOR, '.')) {
2262 name += '.' + this.expect(lexer.TOKEN_SYMBOL).value;
2263 }
2264
2265 node = new nodes.Filter(
2266 tok.lineno,
2267 tok.colno,
2268 new nodes.Symbol(tok.lineno,
2269 tok.colno,
2270 name),
2271 new nodes.NodeList(
2272 tok.lineno,
2273 tok.colno,
2274 [node])
2275 );
2276
2277 if(this.peekToken().type == lexer.TOKEN_LEFT_PAREN) {
2278 // Get a FunCall node and add the parameters to the
2279 // filter
2280 var call = this.parsePostfix(node);
2281 node.args.children = node.args.children.concat(call.args.children);
2282 }
2283 }
2284
2285 return node;
2286 },
2287
2288 parseAggregate: function() {
2289 var tok = this.nextToken();
2290 var node;
2291
2292 switch(tok.type) {
2293 case lexer.TOKEN_LEFT_PAREN:
2294 node = new nodes.Group(tok.lineno, tok.colno); break;
2295 case lexer.TOKEN_LEFT_BRACKET:
2296 node = new nodes.Array(tok.lineno, tok.colno); break;
2297 case lexer.TOKEN_LEFT_CURLY:
2298 node = new nodes.Dict(tok.lineno, tok.colno); break;
2299 default:
2300 return null;
2301 }
2302
2303 while(1) {
2304 var type = this.peekToken().type;
2305 if(type == lexer.TOKEN_RIGHT_PAREN ||
2306 type == lexer.TOKEN_RIGHT_BRACKET ||
2307 type == lexer.TOKEN_RIGHT_CURLY) {
2308 this.nextToken();
2309 break;
2310 }
2311
2312 if(node.children.length > 0) {
2313 if(!this.skip(lexer.TOKEN_COMMA)) {
2314 this.fail("parseAggregate: expected comma after expression",
2315 tok.lineno,
2316 tok.colno);
2317 }
2318 }
2319
2320 if(node instanceof nodes.Dict) {
2321 // TODO: check for errors
2322 var key = this.parsePrimary();
2323
2324 // We expect a key/value pair for dicts, separated by a
2325 // colon
2326 if(!this.skip(lexer.TOKEN_COLON)) {
2327 this.fail("parseAggregate: expected colon after dict key",
2328 tok.lineno,
2329 tok.colno);
2330 }
2331
2332 // TODO: check for errors
2333 var value = this.parseExpression();
2334 node.addChild(new nodes.Pair(key.lineno,
2335 key.colno,
2336 key,
2337 value));
2338 }
2339 else {
2340 // TODO: check for errors
2341 var expr = this.parseExpression();
2342 node.addChild(expr);
2343 }
2344 }
2345
2346 return node;
2347 },
2348
2349 parseSignature: function(tolerant, noParens) {
2350 var tok = this.peekToken();
2351 if(!noParens && tok.type != lexer.TOKEN_LEFT_PAREN) {
2352 if(tolerant) {
2353 return null;
2354 }
2355 else {
2356 this.fail('expected arguments', tok.lineno, tok.colno);
2357 }
2358 }
2359
2360 if(tok.type == lexer.TOKEN_LEFT_PAREN) {
2361 tok = this.nextToken();
2362 }
2363
2364 var args = new nodes.NodeList(tok.lineno, tok.colno);
2365 var kwargs = new nodes.KeywordArgs(tok.lineno, tok.colno);
2366 var kwnames = [];
2367 var checkComma = false;
2368
2369 while(1) {
2370 tok = this.peekToken();
2371 if(!noParens && tok.type == lexer.TOKEN_RIGHT_PAREN) {
2372 this.nextToken();
2373 break;
2374 }
2375 else if(noParens && tok.type == lexer.TOKEN_BLOCK_END) {
2376 break;
2377 }
2378
2379 if(checkComma && !this.skip(lexer.TOKEN_COMMA)) {
2380 this.fail("parseSignature: expected comma after expression",
2381 tok.lineno,
2382 tok.colno);
2383 }
2384 else {
2385 var arg = this.parseExpression();
2386
2387 if(this.skipValue(lexer.TOKEN_OPERATOR, '=')) {
2388 kwargs.addChild(
2389 new nodes.Pair(arg.lineno,
2390 arg.colno,
2391 arg,
2392 this.parseExpression())
2393 );
2394 }
2395 else {
2396 args.addChild(arg);
2397 }
2398 }
2399
2400 checkComma = true;
2401 }
2402
2403 if(kwargs.children.length) {
2404 args.addChild(kwargs);
2405 }
2406
2407 return args;
2408 },
2409
2410 parseUntilBlocks: function(/* blockNames */) {
2411 var prev = this.breakOnBlocks;
2412 this.breakOnBlocks = lib.toArray(arguments);
2413
2414 var ret = this.parse();
2415
2416 this.breakOnBlocks = prev;
2417 return ret;
2418 },
2419
2420 parseNodes: function () {
2421 var tok;
2422 var buf = [];
2423
2424 while((tok = this.nextToken())) {
2425 if(tok.type == lexer.TOKEN_DATA) {
2426 var data = tok.value;
2427 var nextToken = this.peekToken();
2428 var nextVal = nextToken && nextToken.value;
2429
2430 // If the last token has "-" we need to trim the
2431 // leading whitespace of the data. This is marked with
2432 // the `dropLeadingWhitespace` variable.
2433 if(this.dropLeadingWhitespace) {
2434 // TODO: this could be optimized (don't use regex)
2435 data = data.replace(/^\s*/, '');
2436 this.dropLeadingWhitespace = false;
2437 }
2438
2439 // Same for the succeding block start token
2440 if(nextToken &&
2441 nextToken.type == lexer.TOKEN_BLOCK_START &&
2442 nextVal.charAt(nextVal.length - 1) == '-') {
2443 // TODO: this could be optimized (don't use regex)
2444 data = data.replace(/\s*$/, '');
2445 }
2446
2447 buf.push(new nodes.Output(tok.lineno,
2448 tok.colno,
2449 [new nodes.TemplateData(tok.lineno,
2450 tok.colno,
2451 data)]));
2452 }
2453 else if(tok.type == lexer.TOKEN_BLOCK_START) {
2454 var n = this.parseStatement();
2455 if(!n) {
2456 break;
2457 }
2458 buf.push(n);
2459 }
2460 else if(tok.type == lexer.TOKEN_VARIABLE_START) {
2461 var e = this.parseExpression();
2462 this.advanceAfterVariableEnd();
2463 buf.push(new nodes.Output(tok.lineno, tok.colno, [e]));
2464 }
2465 else if(tok.type != lexer.TOKEN_COMMENT) {
2466 // Ignore comments, otherwise this should be an error
2467 this.fail("Unexpected token at top-level: " +
2468 tok.type, tok.lineno, tok.colno);
2469 }
2470 }
2471
2472 return buf;
2473 },
2474
2475 parse: function() {
2476 return new nodes.NodeList(0, 0, this.parseNodes());
2477 },
2478
2479 parseAsRoot: function() {
2480 return new nodes.Root(0, 0, this.parseNodes());
2481 }
2482});
2483
2484// var util = modules["util"];
2485
2486// var l = lexer.lex('{%- if x -%}\n hello {% endif %}');
2487// var t;
2488// while((t = l.nextToken())) {
2489// console.log(util.inspect(t));
2490// }
2491
2492// var p = new Parser(lexer.lex('hello {% foo %} {{ "hi" | bar }} {% endfoo %} end'));
2493// p.extensions = [new FooExtension()];
2494// var n = p.parseAsRoot();
2495// nodes.printNodes(n);
2496
2497modules['parser'] = {
2498 parse: function(src, extensions) {
2499 var p = new Parser(lexer.lex(src));
2500 if (extensions !== undefined) {
2501 p.extensions = extensions;
2502 }
2503 return p.parseAsRoot();
2504 }
2505};
2506})();
2507(function() {
2508var nodes = modules["nodes"];
2509
2510var sym = 0;
2511function gensym() {
2512 return 'hole_' + sym++;
2513}
2514
2515// copy-on-write version of map
2516function mapCOW(arr, func) {
2517 var res = null;
2518
2519 for(var i=0; i<arr.length; i++) {
2520 var item = func(arr[i]);
2521
2522 if(item !== arr[i]) {
2523 if(!res) {
2524 res = arr.slice();
2525 }
2526
2527 res[i] = item;
2528 }
2529 }
2530
2531 return res || arr;
2532}
2533
2534function walk(ast, func, depthFirst) {
2535 if(!(ast instanceof nodes.Node)) {
2536 return ast;
2537 }
2538
2539 if(!depthFirst) {
2540 var astT = func(ast);
2541
2542 if(astT && astT !== ast) {
2543 return astT;
2544 }
2545 }
2546
2547 if(ast instanceof nodes.NodeList) {
2548 var children = mapCOW(ast.children, function(node) {
2549 return walk(node, func, depthFirst);
2550 });
2551
2552 if(children !== ast.children) {
2553 ast = new nodes[ast.typename](ast.lineno, ast.colno, children);
2554 }
2555 }
2556 else if(ast instanceof nodes.CallExtension) {
2557 var args = walk(ast.args, func, depthFirst);
2558
2559 var contentArgs = mapCOW(ast.contentArgs, function(node) {
2560 return walk(node, func, depthFirst);
2561 });
2562
2563 if(args !== ast.args || contentArgs !== ast.contentArgs) {
2564 ast = new nodes[ast.typename](ast.extName,
2565 ast.prop,
2566 args,
2567 contentArgs);
2568 }
2569 }
2570 else {
2571 var props = ast.fields.map(function(field) {
2572 return ast[field];
2573 });
2574
2575 var propsT = mapCOW(props, function(prop) {
2576 return walk(prop, func, depthFirst);
2577 });
2578
2579 if(propsT !== props) {
2580 ast = new nodes[ast.typename](ast.lineno, ast.colno);
2581
2582 propsT.forEach(function(prop, i) {
2583 ast[ast.fields[i]] = prop;
2584 });
2585 }
2586 }
2587
2588 return depthFirst ? (func(ast) || ast) : ast;
2589}
2590
2591function depthWalk(ast, func) {
2592 return walk(ast, func, true);
2593}
2594
2595function _liftFilters(node, asyncFilters, prop) {
2596 var children = [];
2597
2598 var walked = depthWalk(prop ? node[prop] : node, function(node) {
2599 if(node instanceof nodes.Block) {
2600 return node;
2601 }
2602 else if((node instanceof nodes.Filter &&
2603 asyncFilters.indexOf(node.name.value) !== -1) ||
2604 node instanceof nodes.CallExtensionAsync) {
2605 var symbol = new nodes.Symbol(node.lineno,
2606 node.colno,
2607 gensym());
2608
2609 children.push(new nodes.FilterAsync(node.lineno,
2610 node.colno,
2611 node.name,
2612 node.args,
2613 symbol));
2614 return symbol;
2615 }
2616 });
2617
2618 if(prop) {
2619 node[prop] = walked;
2620 }
2621 else {
2622 node = walked;
2623 }
2624
2625 if(children.length) {
2626 children.push(node);
2627
2628 return new nodes.NodeList(
2629 node.lineno,
2630 node.colno,
2631 children
2632 );
2633 }
2634 else {
2635 return node;
2636 }
2637}
2638
2639function liftFilters(ast, asyncFilters) {
2640 return depthWalk(ast, function(node) {
2641 if(node instanceof nodes.Output) {
2642 return _liftFilters(node, asyncFilters);
2643 }
2644 else if(node instanceof nodes.For) {
2645 return _liftFilters(node, asyncFilters, 'arr');
2646 }
2647 else if(node instanceof nodes.If) {
2648 return _liftFilters(node, asyncFilters, 'cond');
2649 }
2650 else if(node instanceof nodes.CallExtension) {
2651 return _liftFilters(node, asyncFilters, 'args');
2652 }
2653 });
2654}
2655
2656function liftSuper(ast) {
2657 return walk(ast, function(blockNode) {
2658 if(!(blockNode instanceof nodes.Block)) {
2659 return;
2660 }
2661
2662 var hasSuper = false;
2663 var symbol = gensym();
2664
2665 blockNode.body = walk(blockNode.body, function(node) {
2666 if(node instanceof nodes.FunCall &&
2667 node.name.value == 'super') {
2668 hasSuper = true;
2669 return new nodes.Symbol(node.lineno, node.colno, symbol);
2670 }
2671 });
2672
2673 if(hasSuper) {
2674 blockNode.body.children.unshift(new nodes.Super(
2675 0, 0, blockNode.name, new nodes.Symbol(0, 0, symbol)
2676 ));
2677 }
2678 });
2679}
2680
2681function convertStatements(ast) {
2682 return depthWalk(ast, function(node) {
2683 if(!(node instanceof nodes.If) &&
2684 !(node instanceof nodes.For)) {
2685 return;
2686 }
2687
2688 var async = false;
2689 walk(node, function(node) {
2690 if(node instanceof nodes.FilterAsync ||
2691 node instanceof nodes.IfAsync ||
2692 node instanceof nodes.AsyncEach ||
2693 node instanceof nodes.AsyncAll ||
2694 node instanceof nodes.CallExtensionAsync) {
2695 async = true;
2696 // Stop iterating by returning the node
2697 return node;
2698 }
2699 });
2700
2701 if(async) {
2702 if(node instanceof nodes.If) {
2703 return new nodes.IfAsync(
2704 node.lineno,
2705 node.colno,
2706 node.cond,
2707 node.body,
2708 node.else_
2709 );
2710 }
2711 else if(node instanceof nodes.For) {
2712 return new nodes.AsyncEach(
2713 node.lineno,
2714 node.colno,
2715 node.arr,
2716 node.name,
2717 node.body
2718 );
2719 }
2720 }
2721 });
2722}
2723
2724function cps(ast, asyncFilters) {
2725 return convertStatements(liftSuper(liftFilters(ast, asyncFilters)));
2726}
2727
2728function transform(ast, asyncFilters, name) {
2729 return cps(ast, asyncFilters || []);
2730}
2731
2732// var parser = modules["parser"];
2733// var src = 'hello {% foo %}{% endfoo %} end';
2734// var ast = transform(parser.parse(src, [new FooExtension()]), ['bar']);
2735// nodes.printNodes(ast);
2736
2737modules['transformer'] = {
2738 transform: transform
2739};
2740})();
2741(function() {
2742var lib = modules["lib"];
2743var parser = modules["parser"];
2744var transformer = modules["transformer"];
2745var nodes = modules["nodes"];
2746var Object = modules["object"];
2747var Frame = modules["runtime"].Frame;
2748
2749// These are all the same for now, but shouldn't be passed straight
2750// through
2751var compareOps = {
2752 '==': '==',
2753 '!=': '!=',
2754 '<': '<',
2755 '>': '>',
2756 '<=': '<=',
2757 '>=': '>='
2758};
2759
2760// A common pattern is to emit binary operators
2761function binOpEmitter(str) {
2762 return function(node, frame) {
2763 this.compile(node.left, frame);
2764 this.emit(str);
2765 this.compile(node.right, frame);
2766 };
2767}
2768
2769// Generate an array of strings
2770function quotedArray(arr) {
2771 return '[' +
2772 lib.map(arr, function(x) { return '"' + x + '"'; }) +
2773 ']';
2774}
2775
2776var Compiler = Object.extend({
2777 init: function() {
2778 this.codebuf = [];
2779 this.lastId = 0;
2780 this.buffer = null;
2781 this.bufferStack = [];
2782 this.isChild = false;
2783 this.scopeClosers = '';
2784 },
2785
2786 fail: function (msg, lineno, colno) {
2787 if (lineno !== undefined) lineno += 1;
2788 if (colno !== undefined) colno += 1;
2789
2790 throw new lib.TemplateError(msg, lineno, colno);
2791 },
2792
2793 pushBufferId: function(id) {
2794 this.bufferStack.push(this.buffer);
2795 this.buffer = id;
2796 this.emit('var ' + this.buffer + ' = "";');
2797 },
2798
2799 popBufferId: function() {
2800 this.buffer = this.bufferStack.pop();
2801 },
2802
2803 emit: function(code) {
2804 this.codebuf.push(code);
2805 },
2806
2807 emitLine: function(code) {
2808 this.emit(code + "\n");
2809 },
2810
2811 emitLines: function() {
2812 lib.each(lib.toArray(arguments), function(line) {
2813 this.emitLine(line);
2814 }, this);
2815 },
2816
2817 emitFuncBegin: function(name) {
2818 this.buffer = 'output';
2819 this.scopeClosers = '';
2820 this.emitLine('function ' + name + '(env, context, frame, runtime, cb) {');
2821 this.emitLine('var lineno = null;');
2822 this.emitLine('var colno = null;');
2823 this.emitLine('var ' + this.buffer + ' = "";');
2824 this.emitLine('try {');
2825 },
2826
2827 emitFuncEnd: function(noReturn) {
2828 if(!noReturn) {
2829 this.emitLine('cb(null, ' + this.buffer +');');
2830 }
2831
2832 this.closeScopeLevels();
2833 this.emitLine('} catch (e) {');
2834 this.emitLine(' cb(runtime.handleError(e, lineno, colno));');
2835 this.emitLine('}');
2836 this.emitLine('}');
2837 this.buffer = null;
2838 },
2839
2840 addScopeLevel: function() {
2841 this.scopeClosers += '})';
2842 },
2843
2844 closeScopeLevels: function() {
2845 this.emitLine(this.scopeClosers + ';');
2846 this.scopeClosers = '';
2847 },
2848
2849 withScopedSyntax: function(func) {
2850 var scopeClosers = this.scopeClosers;
2851 this.scopeClosers = '';
2852
2853 func.call(this);
2854
2855 this.closeScopeLevels();
2856 this.scopeClosers = scopeClosers;
2857 },
2858
2859 makeCallback: function(res) {
2860 var err = this.tmpid();
2861
2862 return 'function(' + err + (res ? ',' + res : '') + ') {\n' +
2863 'if(' + err + ') { cb(' + err + '); return; }';
2864 },
2865
2866 tmpid: function() {
2867 this.lastId++;
2868 return 't_' + this.lastId;
2869 },
2870
2871 _bufferAppend: function(func) {
2872 this.emit(this.buffer + ' += runtime.suppressValue(');
2873 func.call(this);
2874 this.emit(', env.autoesc);\n');
2875 },
2876
2877 _compileChildren: function(node, frame) {
2878 var children = node.children;
2879 for(var i=0, l=children.length; i<l; i++) {
2880 this.compile(children[i], frame);
2881 }
2882 },
2883
2884 _compileAggregate: function(node, frame, startChar, endChar) {
2885 if(startChar) {
2886 this.emit(startChar);
2887 }
2888
2889 for(var i=0; i<node.children.length; i++) {
2890 if(i > 0) {
2891 this.emit(',');
2892 }
2893
2894 this.compile(node.children[i], frame);
2895 }
2896
2897 if(endChar) {
2898 this.emit(endChar);
2899 }
2900 },
2901
2902 _compileExpression: function(node, frame) {
2903 // TODO: I'm not really sure if this type check is worth it or
2904 // not.
2905 this.assertType(
2906 node,
2907 nodes.Literal,
2908 nodes.Symbol,
2909 nodes.Group,
2910 nodes.Array,
2911 nodes.Dict,
2912 nodes.FunCall,
2913 nodes.Filter,
2914 nodes.LookupVal,
2915 nodes.Compare,
2916 nodes.InlineIf,
2917 nodes.And,
2918 nodes.Or,
2919 nodes.Not,
2920 nodes.Add,
2921 nodes.Sub,
2922 nodes.Mul,
2923 nodes.Div,
2924 nodes.FloorDiv,
2925 nodes.Mod,
2926 nodes.Pow,
2927 nodes.Neg,
2928 nodes.Pos,
2929 nodes.Compare,
2930 nodes.NodeList
2931 );
2932 this.compile(node, frame);
2933 },
2934
2935 assertType: function(node /*, types */) {
2936 var types = lib.toArray(arguments).slice(1);
2937 var success = false;
2938
2939 for(var i=0; i<types.length; i++) {
2940 if(node instanceof types[i]) {
2941 success = true;
2942 }
2943 }
2944
2945 if(!success) {
2946 this.fail("assertType: invalid type: " + node.typename,
2947 node.lineno,
2948 node.colno);
2949 }
2950 },
2951
2952 compileCallExtension: function(node, frame, async) {
2953 var name = node.extName;
2954 var args = node.args;
2955 var contentArgs = node.contentArgs;
2956 var autoescape = typeof node.autoescape === 'boolean' ? node.autoescape : true;
2957 var transformedArgs = [];
2958
2959 if(!async) {
2960 this.emit(this.buffer + ' += runtime.suppressValue(');
2961 }
2962
2963 this.emit('env.getExtension("' + node.extName + '")["' + node.prop + '"](');
2964 this.emit('context');
2965
2966 if(args || contentArgs) {
2967 this.emit(',');
2968 }
2969
2970 if(args) {
2971 if(!(args instanceof nodes.NodeList)) {
2972 this.fail('compileCallExtension: arguments must be a NodeList, ' +
2973 'use `parser.parseSignature`');
2974 }
2975
2976 lib.each(args.children, function(arg, i) {
2977 // Tag arguments are passed normally to the call. Note
2978 // that keyword arguments are turned into a single js
2979 // object as the last argument, if they exist.
2980 this._compileExpression(arg, frame);
2981
2982 if(i != args.children.length - 1 || contentArgs.length) {
2983 this.emit(',');
2984 }
2985 }, this);
2986 }
2987
2988 if(contentArgs.length) {
2989 lib.each(contentArgs, function(arg, i) {
2990 if(i > 0) {
2991 this.emit(',');
2992 }
2993
2994 if(arg) {
2995 var id = this.tmpid();
2996
2997 this.emitLine('function(cb) {');
2998 this.emitLine('if(!cb) { cb = function(err) { if(err) { throw err; }}}');
2999 this.pushBufferId(id);
3000
3001 this.withScopedSyntax(function() {
3002 this.compile(arg, frame);
3003 this.emitLine('cb(null, ' + id + ');');
3004 });
3005
3006 this.popBufferId();
3007 this.emitLine('return ' + id + ';');
3008 this.emitLine('}');
3009 }
3010 else {
3011 this.emit('null');
3012 }
3013 }, this);
3014 }
3015
3016 if(async) {
3017 var res = this.tmpid();
3018 this.emitLine(', ' + this.makeCallback(res));
3019 this.emitLine(this.buffer + ' += runtime.suppressValue(' + res + ', ' + autoescape + ' && env.autoesc);');
3020 this.addScopeLevel();
3021 }
3022 else {
3023 this.emit(')');
3024 this.emit(', ' + autoescape + ' && env.autoesc);\n');
3025 }
3026 },
3027
3028 compileCallExtensionAsync: function(node, frame) {
3029 this.compileCallExtension(node, frame, true);
3030 },
3031
3032 compileNodeList: function(node, frame) {
3033 this._compileChildren(node, frame);
3034 },
3035
3036 compileLiteral: function(node, frame) {
3037 if(typeof node.value == "string") {
3038 var val = node.value.replace(/\\/g, '\\\\');
3039 val = val.replace(/"/g, '\\"');
3040 val = val.replace(/\n/g, "\\n");
3041 val = val.replace(/\r/g, "\\r");
3042 val = val.replace(/\t/g, "\\t");
3043 this.emit('"' + val + '"');
3044 }
3045 else {
3046 this.emit(node.value.toString());
3047 }
3048 },
3049
3050 compileSymbol: function(node, frame) {
3051 var name = node.value;
3052 var v;
3053
3054 if((v = frame.lookup(name))) {
3055 this.emit(v);
3056 }
3057 else {
3058 this.emit('runtime.contextOrFrameLookup(' +
3059 'context, frame, "' + name + '")');
3060 }
3061 },
3062
3063 compileGroup: function(node, frame) {
3064 this._compileAggregate(node, frame, '(', ')');
3065 },
3066
3067 compileArray: function(node, frame) {
3068 this._compileAggregate(node, frame, '[', ']');
3069 },
3070
3071 compileDict: function(node, frame) {
3072 this._compileAggregate(node, frame, '{', '}');
3073 },
3074
3075 compilePair: function(node, frame) {
3076 var key = node.key;
3077 var val = node.value;
3078
3079 if(key instanceof nodes.Symbol) {
3080 key = new nodes.Literal(key.lineno, key.colno, key.value);
3081 }
3082 else if(!(key instanceof nodes.Literal &&
3083 typeof key.value == "string")) {
3084 this.fail("compilePair: Dict keys must be strings or names",
3085 key.lineno,
3086 key.colno);
3087 }
3088
3089 this.compile(key, frame);
3090 this.emit(': ');
3091 this._compileExpression(val, frame);
3092 },
3093
3094 compileInlineIf: function(node, frame) {
3095 this.emit('(');
3096 this.compile(node.cond, frame);
3097 this.emit('?');
3098 this.compile(node.body, frame);
3099 this.emit(':');
3100 if(node.else_ !== null)
3101 this.compile(node.else_, frame);
3102 else
3103 this.emit('""');
3104 this.emit(')');
3105 },
3106
3107 compileOr: binOpEmitter(' || '),
3108 compileAnd: binOpEmitter(' && '),
3109 compileAdd: binOpEmitter(' + '),
3110 compileSub: binOpEmitter(' - '),
3111 compileMul: binOpEmitter(' * '),
3112 compileDiv: binOpEmitter(' / '),
3113 compileMod: binOpEmitter(' % '),
3114
3115 compileNot: function(node, frame) {
3116 this.emit('!');
3117 this.compile(node.target, frame);
3118 },
3119
3120 compileFloorDiv: function(node, frame) {
3121 this.emit('Math.floor(');
3122 this.compile(node.left, frame);
3123 this.emit(' / ');
3124 this.compile(node.right, frame);
3125 this.emit(')');
3126 },
3127
3128 compilePow: function(node, frame) {
3129 this.emit('Math.pow(');
3130 this.compile(node.left, frame);
3131 this.emit(', ');
3132 this.compile(node.right, frame);
3133 this.emit(')');
3134 },
3135
3136 compileNeg: function(node, frame) {
3137 this.emit('-');
3138 this.compile(node.target, frame);
3139 },
3140
3141 compilePos: function(node, frame) {
3142 this.emit('+');
3143 this.compile(node.target, frame);
3144 },
3145
3146 compileCompare: function(node, frame) {
3147 this.compile(node.expr, frame);
3148
3149 for(var i=0; i<node.ops.length; i++) {
3150 var n = node.ops[i];
3151 this.emit(' ' + compareOps[n.type] + ' ');
3152 this.compile(n.expr, frame);
3153 }
3154 },
3155
3156 compileLookupVal: function(node, frame) {
3157 this.emit('runtime.memberLookup((');
3158 this._compileExpression(node.target, frame);
3159 this.emit('),');
3160 this._compileExpression(node.val, frame);
3161 this.emit(', env.autoesc)');
3162 },
3163
3164 _getNodeName: function(node) {
3165 switch (node.typename) {
3166 case 'Symbol':
3167 return node.value;
3168 case 'FunCall':
3169 return 'the return value of (' + this._getNodeName(node.name) + ')';
3170 case 'LookupVal':
3171 return this._getNodeName(node.target) + '["' +
3172 this._getNodeName(node.val) + '"]';
3173 case 'Literal':
3174 return node.value.toString().substr(0, 10);
3175 default:
3176 return '--expression--';
3177 }
3178 },
3179
3180 compileFunCall: function(node, frame) {
3181 // Keep track of line/col info at runtime by settings
3182 // variables within an expression. An expression in javascript
3183 // like (x, y, z) returns the last value, and x and y can be
3184 // anything
3185 this.emit('(lineno = ' + node.lineno +
3186 ', colno = ' + node.colno + ', ');
3187
3188 this.emit('runtime.callWrap(');
3189 // Compile it as normal.
3190 this._compileExpression(node.name, frame);
3191
3192 // Output the name of what we're calling so we can get friendly errors
3193 // if the lookup fails.
3194 this.emit(', "' + this._getNodeName(node.name).replace(/"/g, '\\"') + '", ');
3195
3196 this._compileAggregate(node.args, frame, '[', '])');
3197
3198 this.emit(')');
3199 },
3200
3201 compileFilter: function(node, frame) {
3202 var name = node.name;
3203 this.assertType(name, nodes.Symbol);
3204
3205 this.emit('env.getFilter("' + name.value + '").call(context, ');
3206 this._compileAggregate(node.args, frame);
3207 this.emit(')');
3208 },
3209
3210 compileFilterAsync: function(node, frame) {
3211 var name = node.name;
3212 this.assertType(name, nodes.Symbol);
3213
3214 var symbol = node.symbol.value;
3215 frame.set(symbol, symbol);
3216
3217 this.emit('env.getFilter("' + name.value + '").call(context, ');
3218 this._compileAggregate(node.args, frame);
3219 this.emitLine(', ' + this.makeCallback(symbol));
3220
3221 this.addScopeLevel();
3222 },
3223
3224 compileKeywordArgs: function(node, frame) {
3225 var names = [];
3226
3227 lib.each(node.children, function(pair) {
3228 names.push(pair.key.value);
3229 });
3230
3231 this.emit('runtime.makeKeywordArgs(');
3232 this.compileDict(node, frame);
3233 this.emit(')');
3234 },
3235
3236 compileSet: function(node, frame) {
3237 var ids = [];
3238
3239 // Lookup the variable names for each identifier and create
3240 // new ones if necessary
3241 lib.each(node.targets, function(target) {
3242 var name = target.value;
3243 var id = frame.lookup(name);
3244
3245 if (id == null) {
3246 id = this.tmpid();
3247
3248 // Note: This relies on js allowing scope across
3249 // blocks, in case this is created inside an `if`
3250 this.emitLine('var ' + id + ';');
3251 }
3252
3253 ids.push(id);
3254 }, this);
3255
3256 this.emit(ids.join(' = ') + ' = ');
3257 this._compileExpression(node.value, frame);
3258 this.emitLine(';');
3259
3260 lib.each(node.targets, function(target, i) {
3261 var id = ids[i];
3262 var name = target.value;
3263
3264 this.emitLine('frame.set("' + name + '", ' + id + ', true);');
3265
3266 // We are running this for every var, but it's very
3267 // uncommon to assign to multiple vars anyway
3268 this.emitLine('if(!frame.parent) {');
3269 this.emitLine('context.setVariable("' + name + '", ' + id + ');');
3270 if(name.charAt(0) != '_') {
3271 this.emitLine('context.addExport("' + name + '");');
3272 }
3273 this.emitLine('}');
3274 }, this);
3275 },
3276
3277 compileIf: function(node, frame, async) {
3278 this.emit('if(');
3279 this._compileExpression(node.cond, frame);
3280 this.emitLine(') {');
3281
3282 this.withScopedSyntax(function() {
3283 this.compile(node.body, frame);
3284
3285 if(async) {
3286 this.emit('cb()');
3287 }
3288 });
3289
3290 if(node.else_) {
3291 this.emitLine('}\nelse {');
3292
3293 this.withScopedSyntax(function() {
3294 this.compile(node.else_, frame);
3295
3296 if(async) {
3297 this.emit('cb()');
3298 }
3299 });
3300 } else if(async) {
3301 this.emitLine('}\nelse {');
3302 this.emit('cb()');
3303 }
3304
3305 this.emitLine('}');
3306 },
3307
3308 compileIfAsync: function(node, frame) {
3309 this.emit('(function(cb) {');
3310 this.compileIf(node, frame, true);
3311 this.emit('})(function() {');
3312 this.addScopeLevel();
3313 },
3314
3315 scanLoop: function(node) {
3316 var loopUses = {};
3317
3318 node.iterFields(function(field) {
3319 var lookups = field.findAll(nodes.LookupVal);
3320
3321 lib.each(lookups, function(lookup) {
3322 if (lookup.target instanceof nodes.Symbol &&
3323 lookup.target.value == 'loop' &&
3324 lookup.val instanceof nodes.Literal) {
3325 loopUses[lookup.val.value] = true;
3326 }
3327 });
3328 });
3329
3330 return loopUses;
3331 },
3332
3333 emitLoopBindings: function(node, loopUses, arr, i, len) {
3334 len = len || arr + '.length';
3335
3336 var bindings = {
3337 index: i + ' + 1',
3338 index0: i,
3339 revindex: len + ' - ' + i,
3340 revindex0: len + ' - ' + i + ' - 1',
3341 first: i + ' === 0',
3342 last: i + ' === ' + len + ' - 1',
3343 length: len
3344 };
3345
3346 for(var name in bindings) {
3347 if(name in loopUses) {
3348 this.emitLine('frame.set("loop.' + name + '", ' + bindings[name] + ');');
3349 }
3350 }
3351 },
3352
3353 compileFor: function(node, frame) {
3354 // Some of this code is ugly, but it keeps the generated code
3355 // as fast as possible. ForAsync also shares some of this, but
3356 // not much.
3357
3358 var i = this.tmpid();
3359 var len = this.tmpid();
3360 var arr = this.tmpid();
3361 var loopUses = this.scanLoop(node);
3362 frame = frame.push();
3363
3364 this.emitLine('frame = frame.push();');
3365
3366 this.emit('var ' + arr + ' = ');
3367 this._compileExpression(node.arr, frame);
3368 this.emitLine(';');
3369
3370 this.emit('if(' + arr + ') {');
3371
3372 // If multiple names are passed, we need to bind them
3373 // appropriately
3374 if(node.name instanceof nodes.Array) {
3375 this.emitLine('var ' + i + ';');
3376
3377 // The object could be an arroy or object. Note that the
3378 // body of the loop is duplicated for each condition, but
3379 // we are optimizing for speed over size.
3380 this.emitLine('if(runtime.isArray(' + arr + ')) {'); {
3381 this.emitLine('for(' + i + '=0; ' + i + ' < ' + arr + '.length; '
3382 + i + '++) {');
3383
3384 // Bind each declared var
3385 for (var u=0; u < node.name.children.length; u++) {
3386 var tid = this.tmpid();
3387 this.emitLine('var ' + tid + ' = ' + arr + '[' + i + '][' + u + ']');
3388 this.emitLine('frame.set("' + node.name.children[u].value
3389 + '", ' + arr + '[' + i + '][' + u + ']' + ');');
3390 frame.set(node.name.children[u].value, tid);
3391 }
3392
3393 this.emitLoopBindings(node, loopUses, arr, i);
3394 this.withScopedSyntax(function() {
3395 this.compile(node.body, frame);
3396 });
3397 this.emitLine('}');
3398 }
3399
3400 this.emitLine('} else {'); {
3401 // Iterate over the key/values of an object
3402 var key = node.name.children[0];
3403 var val = node.name.children[1];
3404 var k = this.tmpid();
3405 var v = this.tmpid();
3406 frame.set(key.value, k);
3407 frame.set(val.value, v);
3408
3409 this.emitLine(i + ' = -1;');
3410
3411 if(loopUses['revindex'] || loopUses['revindex0'] ||
3412 loopUses['last'] || loopUses['length']) {
3413 this.emitLine('var ' + len + ' = runtime.keys(' + arr + ').length;');
3414 }
3415
3416 this.emitLine('for(var ' + k + ' in ' + arr + ') {');
3417 this.emitLine(i + '++;');
3418 this.emitLine('var ' + v + ' = ' + arr + '[' + k + '];');
3419 this.emitLine('frame.set("' + key.value + '", ' + k + ');');
3420 this.emitLine('frame.set("' + val.value + '", ' + v + ');');
3421
3422 this.emitLoopBindings(node, loopUses, arr, i, len);
3423 this.withScopedSyntax(function() {
3424 this.compile(node.body, frame);
3425 });
3426 this.emitLine('}');
3427 }
3428
3429 this.emitLine('}');
3430 }
3431 else {
3432 // Generate a typical array iteration
3433 var v = this.tmpid();
3434 frame.set(node.name.value, v);
3435
3436 this.emitLine('for(var ' + i + '=0; ' + i + ' < ' + arr + '.length; ' +
3437 i + '++) {');
3438 this.emitLine('var ' + v + ' = ' + arr + '[' + i + '];');
3439 this.emitLine('frame.set("' + node.name.value + '", ' + v + ');');
3440
3441 this.emitLoopBindings(node, loopUses, arr, i);
3442
3443 this.withScopedSyntax(function() {
3444 this.compile(node.body, frame);
3445 });
3446
3447 this.emitLine('}');
3448 }
3449
3450 this.emitLine('}');
3451 this.emitLine('frame = frame.pop();');
3452 },
3453
3454 _compileAsyncLoop: function(node, frame, parallel) {
3455 // This shares some code with the For tag, but not enough to
3456 // worry about. This iterates across an object asynchronously,
3457 // but not in parallel.
3458
3459 var i = this.tmpid();
3460 var len = this.tmpid();
3461 var arr = this.tmpid();
3462 var loopUses = this.scanLoop(node);
3463 var asyncMethod = parallel ? 'asyncAll' : 'asyncEach';
3464 frame = frame.push();
3465
3466 this.emitLine('frame = frame.push();');
3467
3468 this.emit('var ' + arr + ' = ');
3469 this._compileExpression(node.arr, frame);
3470 this.emitLine(';');
3471
3472 if(node.name instanceof nodes.Array) {
3473 this.emit('runtime.' + asyncMethod + '(' + arr + ', ' +
3474 node.name.children.length + ', function(');
3475
3476 lib.each(node.name.children, function(name) {
3477 this.emit(name.value + ',');
3478 }, this);
3479
3480 this.emit(i + ',' + len + ',next) {');
3481
3482 lib.each(node.name.children, function(name) {
3483 var id = name.value;
3484 frame.set(id, id);
3485 this.emitLine('frame.set("' + id + '", ' + id + ');');
3486 }, this);
3487 }
3488 else {
3489 var id = node.name.value;
3490 this.emitLine('runtime.' + asyncMethod + '(' + arr + ', 1, function(' + id + ', ' + i + ', ' + len + ',next) {');
3491 this.emitLine('frame.set("' + id + '", ' + id + ');');
3492 frame.set(id, id);
3493 }
3494
3495 this.emitLoopBindings(node, loopUses, arr, i, len);
3496
3497 this.withScopedSyntax(function() {
3498 var buf;
3499 if(parallel) {
3500 buf = this.tmpid();
3501 this.pushBufferId(buf);
3502 }
3503
3504 this.compile(node.body, frame);
3505 this.emitLine('next(' + i + (buf ? ',' + buf : '') + ');');
3506
3507 if(parallel) {
3508 this.popBufferId();
3509 }
3510 });
3511
3512 var output = this.tmpid();
3513 this.emitLine('}, ' + this.makeCallback(output));
3514 this.addScopeLevel();
3515
3516 if(parallel) {
3517 this.emitLine(this.buffer + ' += ' + output + ';');
3518 }
3519
3520 this.emitLine('frame = frame.pop();');
3521 },
3522
3523 compileAsyncEach: function(node, frame) {
3524 this._compileAsyncLoop(node, frame);
3525 },
3526
3527 compileAsyncAll: function(node, frame) {
3528 this._compileAsyncLoop(node, frame, true);
3529 },
3530
3531 _emitMacroBegin: function(node, frame) {
3532 var args = [];
3533 var kwargs = null;
3534 var funcId = 'macro_' + this.tmpid();
3535
3536 // Type check the definition of the args
3537 lib.each(node.args.children, function(arg, i) {
3538 if(i === node.args.children.length - 1 &&
3539 arg instanceof nodes.Dict) {
3540 kwargs = arg;
3541 }
3542 else {
3543 this.assertType(arg, nodes.Symbol);
3544 args.push(arg);
3545 }
3546 }, this);
3547
3548 var realNames = lib.map(args, function(n) { return 'l_' + n.value; });
3549 realNames.push('kwargs');
3550
3551 // Quoted argument names
3552 var argNames = lib.map(args, function(n) { return '"' + n.value + '"'; });
3553 var kwargNames = lib.map((kwargs && kwargs.children) || [],
3554 function(n) { return '"' + n.key.value + '"'; });
3555
3556 // We pass a function to makeMacro which destructures the
3557 // arguments so support setting positional args with keywords
3558 // args and passing keyword args as positional args
3559 // (essentially default values). See runtime.js.
3560 this.emitLines(
3561 'var ' + funcId + ' = runtime.makeMacro(',
3562 '[' + argNames.join(', ') + '], ',
3563 '[' + kwargNames.join(', ') + '], ',
3564 'function (' + realNames.join(', ') + ') {',
3565 'frame = frame.push();',
3566 'kwargs = kwargs || {};'
3567 );
3568
3569 // Expose the arguments to the template. Don't need to use
3570 // random names because the function
3571 // will create a new run-time scope for us
3572 lib.each(args, function(arg) {
3573 this.emitLine('frame.set("' + arg.value + '", ' +
3574 'l_' + arg.value + ');');
3575 frame.set(arg.value, 'l_' + arg.value);
3576 }, this);
3577
3578 // Expose the keyword arguments
3579 if(kwargs) {
3580 lib.each(kwargs.children, function(pair) {
3581 var name = pair.key.value;
3582 this.emit('frame.set("' + name + '", ' +
3583 'kwargs.hasOwnProperty("' + name + '") ? ' +
3584 'kwargs["' + name + '"] : ');
3585 this._compileExpression(pair.value, frame);
3586 this.emitLine(');');
3587 }, this);
3588 }
3589
3590 return funcId;
3591 },
3592
3593 _emitMacroEnd: function(bufferId) {
3594 this.emitLine('frame = frame.pop();');
3595 this.emitLine('return new runtime.SafeString(' + bufferId + ');');
3596 this.emitLine('});');
3597 },
3598
3599 compileMacro: function(node, frame) {
3600 frame = frame.push();
3601 var funcId = this._emitMacroBegin(node, frame);
3602 var id = this.tmpid();
3603 this.pushBufferId(id);
3604
3605 this.withScopedSyntax(function() {
3606 this.compile(node.body, frame);
3607 });
3608
3609 this._emitMacroEnd(id);
3610 this.popBufferId();
3611
3612 // Expose the macro to the templates
3613 var name = node.name.value;
3614 frame = frame.pop();
3615 frame.set(name, funcId);
3616
3617 if(frame.parent) {
3618 this.emitLine('frame.set("' + name + '", ' + funcId + ');');
3619 }
3620 else {
3621 if(node.name.value.charAt(0) != '_') {
3622 this.emitLine('context.addExport("' + name + '");');
3623 }
3624 this.emitLine('context.setVariable("' + name + '", ' + funcId + ');');
3625 }
3626 },
3627
3628 compileImport: function(node, frame) {
3629 var id = this.tmpid();
3630 var target = node.target.value;
3631
3632 this.emit('env.getTemplate(');
3633 this._compileExpression(node.template, frame);
3634 this.emitLine(', ' + this.makeCallback(id));
3635 this.addScopeLevel();
3636
3637 this.emitLine(id + '.getExported(' + this.makeCallback(id));
3638 this.addScopeLevel();
3639
3640 frame.set(target, id);
3641
3642 if(frame.parent) {
3643 this.emitLine('frame.set("' + target + '", ' + id + ');');
3644 }
3645 else {
3646 this.emitLine('context.setVariable("' + target + '", ' + id + ');');
3647 }
3648 },
3649
3650 compileFromImport: function(node, frame) {
3651 var importedId = this.tmpid();
3652
3653 this.emit('env.getTemplate(');
3654 this._compileExpression(node.template, frame);
3655 this.emitLine(', ' + this.makeCallback(importedId));
3656 this.addScopeLevel();
3657
3658 this.emitLine(importedId + '.getExported(' + this.makeCallback(importedId));
3659 this.addScopeLevel();
3660
3661 lib.each(node.names.children, function(nameNode) {
3662 var name;
3663 var alias;
3664 var id = this.tmpid();
3665
3666 if(nameNode instanceof nodes.Pair) {
3667 name = nameNode.key.value;
3668 alias = nameNode.value.value;
3669 }
3670 else {
3671 name = nameNode.value;
3672 alias = name;
3673 }
3674
3675 this.emitLine('if(' + importedId + '.hasOwnProperty("' + name + '")) {');
3676 this.emitLine('var ' + id + ' = ' + importedId + '.' + name + ';');
3677 this.emitLine('} else {');
3678 this.emitLine('cb(new Error("cannot import \'' + name + '\'")); return;');
3679 this.emitLine('}');
3680
3681 frame.set(alias, id);
3682
3683 if(frame.parent) {
3684 this.emitLine('frame.set("' + alias + '", ' + id + ');');
3685 }
3686 else {
3687 this.emitLine('context.setVariable("' + alias + '", ' + id + ');');
3688 }
3689 }, this);
3690 },
3691
3692 compileBlock: function(node, frame) {
3693 if(!this.isChild) {
3694 var id = this.tmpid();
3695
3696 this.emitLine('context.getBlock("' + node.name.value + '")' +
3697 '(env, context, frame, runtime, ' + this.makeCallback(id));
3698 this.emitLine(this.buffer + ' += ' + id + ';');
3699 this.addScopeLevel();
3700 }
3701 },
3702
3703 compileSuper: function(node, frame) {
3704 var name = node.blockName.value;
3705 var id = node.symbol.value;
3706
3707 this.emitLine('context.getSuper(env, ' +
3708 '"' + name + '", ' +
3709 'b_' + name + ', ' +
3710 'frame, runtime, '+
3711 this.makeCallback(id));
3712 this.emitLine(id + ' = runtime.markSafe(' + id + ');');
3713 this.addScopeLevel();
3714 frame.set(id, id);
3715 },
3716
3717 compileExtends: function(node, frame) {
3718 if(this.isChild) {
3719 this.fail('compileExtends: cannot extend multiple times',
3720 node.template.lineno,
3721 node.template.colno);
3722 }
3723
3724 var k = this.tmpid();
3725
3726 this.emit('env.getTemplate(');
3727 this._compileExpression(node.template, frame);
3728 this.emitLine(', true, ' + this.makeCallback('parentTemplate'));
3729
3730 this.emitLine('for(var ' + k + ' in parentTemplate.blocks) {');
3731 this.emitLine('context.addBlock(' + k +
3732 ', parentTemplate.blocks[' + k + ']);');
3733 this.emitLine('}');
3734
3735 this.addScopeLevel();
3736 this.isChild = true;
3737 },
3738
3739 compileInclude: function(node, frame) {
3740 var id = this.tmpid();
3741 var id2 = this.tmpid();
3742
3743 this.emit('env.getTemplate(');
3744 this._compileExpression(node.template, frame);
3745 this.emitLine(', ' + this.makeCallback(id));
3746 this.addScopeLevel();
3747
3748 this.emitLine(id + '.render(' +
3749 'context.getVariables(), frame.push(), ' + this.makeCallback(id2));
3750 this.emitLine(this.buffer + ' += ' + id2);
3751 this.addScopeLevel();
3752 },
3753
3754 compileTemplateData: function(node, frame) {
3755 this.compileLiteral(node, frame);
3756 },
3757
3758 compileOutput: function(node, frame) {
3759 var children = node.children;
3760 for(var i=0, l=children.length; i<l; i++) {
3761 // TemplateData is a special case because it is never
3762 // autoescaped, so simply output it for optimization
3763 if(children[i] instanceof nodes.TemplateData) {
3764 if(children[i].value) {
3765 this.emit(this.buffer + ' += ');
3766 this.compileLiteral(children[i], frame);
3767 this.emitLine(';');
3768 }
3769 }
3770 else {
3771 this.emit(this.buffer + ' += runtime.suppressValue(');
3772 this.compile(children[i], frame);
3773 this.emit(', env.autoesc);\n');
3774 }
3775 }
3776 },
3777
3778 compileRoot: function(node, frame) {
3779 if(frame) {
3780 this.fail("compileRoot: root node can't have frame");
3781 }
3782
3783 frame = new Frame();
3784
3785 this.emitFuncBegin('root');
3786 this._compileChildren(node, frame);
3787 if(this.isChild) {
3788 this.emitLine('parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);');
3789 }
3790 this.emitFuncEnd(this.isChild);
3791
3792 // When compiling the blocks, they should all act as top-level code
3793 this.isChild = false;
3794
3795 var blocks = node.findAll(nodes.Block);
3796 for(var i=0; i<blocks.length; i++) {
3797 var block = blocks[i];
3798 var name = block.name.value;
3799
3800 this.emitFuncBegin('b_' + name);
3801
3802 var tmpFrame = new Frame();
3803 this.compile(block.body, tmpFrame);
3804 this.emitFuncEnd();
3805 }
3806
3807 this.emitLine('return {');
3808 for(var i=0; i<blocks.length; i++) {
3809 var block = blocks[i];
3810 var name = 'b_' + block.name.value;
3811 this.emitLine(name + ': ' + name + ',');
3812 }
3813 this.emitLine('root: root\n};');
3814 },
3815
3816 compile: function (node, frame) {
3817 var _compile = this["compile" + node.typename];
3818 if(_compile) {
3819 _compile.call(this, node, frame);
3820 }
3821 else {
3822 this.fail("compile: Cannot compile node: " + node.typename,
3823 node.lineno,
3824 node.colno);
3825 }
3826 },
3827
3828 getCode: function() {
3829 return this.codebuf.join('');
3830 }
3831});
3832
3833// var c = new Compiler();
3834// var src = '{% macro foo() %}{% include "include.html" %}{% endmacro %} This is my template {{ foo() }}';
3835// var ast = transformer.transform(parser.parse(src));
3836//nodes.printNodes(ast);
3837// c.compile(ast);
3838// var tmpl = c.getCode();
3839// console.log(tmpl);
3840
3841modules['compiler'] = {
3842 compile: function(src, asyncFilters, extensions, name) {
3843 var c = new Compiler();
3844
3845 // Run the extension preprocessors against the source.
3846 if(extensions && extensions.length) {
3847 for(var i=0; i<extensions.length; i++) {
3848 if('preprocess' in extensions[i]) {
3849 src = extensions[i].preprocess(src, name);
3850 }
3851 }
3852 }
3853
3854 c.compile(transformer.transform(parser.parse(src, extensions),
3855 asyncFilters,
3856 name));
3857 return c.getCode();
3858 },
3859
3860 Compiler: Compiler
3861};
3862})();
3863(function() {
3864var lib = modules["lib"];
3865var r = modules["runtime"];
3866
3867var filters = {
3868 abs: function(n) {
3869 return Math.abs(n);
3870 },
3871
3872 batch: function(arr, linecount, fill_with) {
3873 var res = [];
3874 var tmp = [];
3875
3876 for(var i=0; i<arr.length; i++) {
3877 if(i % linecount === 0 && tmp.length) {
3878 res.push(tmp);
3879 tmp = [];
3880 }
3881
3882 tmp.push(arr[i]);
3883 }
3884
3885 if(tmp.length) {
3886 if(fill_with) {
3887 for(var i=tmp.length; i<linecount; i++) {
3888 tmp.push(fill_with);
3889 }
3890 }
3891
3892 res.push(tmp);
3893 }
3894
3895 return res;
3896 },
3897
3898 capitalize: function(str) {
3899 var ret = str.toLowerCase();
3900 return r.copySafeness(str, ret.charAt(0).toUpperCase() + ret.slice(1));
3901 },
3902
3903 center: function(str, width) {
3904 width = width || 80;
3905
3906 if(str.length >= width) {
3907 return str;
3908 }
3909
3910 var spaces = width - str.length;
3911 var pre = lib.repeat(" ", spaces/2 - spaces % 2);
3912 var post = lib.repeat(" ", spaces/2);
3913 return r.copySafeness(str, pre + str + post);
3914 },
3915
3916 'default': function(val, def) {
3917 return val ? val : def;
3918 },
3919
3920 dictsort: function(val, case_sensitive, by) {
3921 if (!lib.isObject(val)) {
3922 throw new lib.TemplateError("dictsort filter: val must be an object");
3923 }
3924
3925 var array = [];
3926 for (var k in val) {
3927 // deliberately include properties from the object's prototype
3928 array.push([k,val[k]]);
3929 }
3930
3931 var si;
3932 if (by === undefined || by === "key") {
3933 si = 0;
3934 } else if (by === "value") {
3935 si = 1;
3936 } else {
3937 throw new lib.TemplateError(
3938 "dictsort filter: You can only sort by either key or value");
3939 }
3940
3941 array.sort(function(t1, t2) {
3942 var a = t1[si];
3943 var b = t2[si];
3944
3945 if (!case_sensitive) {
3946 if (lib.isString(a)) {
3947 a = a.toUpperCase();
3948 }
3949 if (lib.isString(b)) {
3950 b = b.toUpperCase();
3951 }
3952 }
3953
3954 return a > b ? 1 : (a == b ? 0 : -1);
3955 });
3956
3957 return array;
3958 },
3959
3960 escape: function(str) {
3961 if(typeof str == 'string' ||
3962 str instanceof r.SafeString) {
3963 return lib.escape(str);
3964 }
3965 return str;
3966 },
3967
3968 safe: function(str) {
3969 return r.markSafe(str);
3970 },
3971
3972 first: function(arr) {
3973 return arr[0];
3974 },
3975
3976 groupby: function(arr, attr) {
3977 return lib.groupBy(arr, attr);
3978 },
3979
3980 indent: function(str, width, indentfirst) {
3981 width = width || 4;
3982 var res = '';
3983 var lines = str.split('\n');
3984 var sp = lib.repeat(' ', width);
3985
3986 for(var i=0; i<lines.length; i++) {
3987 if(i == 0 && !indentfirst) {
3988 res += lines[i] + '\n';
3989 }
3990 else {
3991 res += sp + lines[i] + '\n';
3992 }
3993 }
3994
3995 return r.copySafeness(str, res);
3996 },
3997
3998 join: function(arr, del, attr) {
3999 del = del || '';
4000
4001 if(attr) {
4002 arr = lib.map(arr, function(v) {
4003 return v[attr];
4004 });
4005 }
4006
4007 return arr.join(del);
4008 },
4009
4010 last: function(arr) {
4011 return arr[arr.length-1];
4012 },
4013
4014 length: function(arr) {
4015 return arr !== undefined ? arr.length : 0;
4016 },
4017
4018 list: function(val) {
4019 if(lib.isString(val)) {
4020 return val.split('');
4021 }
4022 else if(lib.isObject(val)) {
4023 var keys = [];
4024
4025 if(Object.keys) {
4026 keys = Object.keys(val);
4027 }
4028 else {
4029 for(var k in val) {
4030 keys.push(k);
4031 }
4032 }
4033
4034 return lib.map(keys, function(k) {
4035 return { key: k,
4036 value: val[k] };
4037 });
4038 }
4039 else {
4040 throw new lib.TemplateError("list filter: type not iterable");
4041 }
4042 },
4043
4044 lower: function(str) {
4045 return str.toLowerCase();
4046 },
4047
4048 random: function(arr) {
4049 return arr[Math.floor(Math.random() * arr.length)];
4050 },
4051
4052 replace: function(str, old, new_, maxCount) {
4053 var res = str;
4054 var last = res;
4055 var count = 1;
4056 res = res.replace(old, new_);
4057
4058 while(last != res) {
4059 if(count >= maxCount) {
4060 break;
4061 }
4062
4063 last = res;
4064 res = res.replace(old, new_);
4065 count++;
4066 }
4067
4068 return r.copySafeness(str, res);
4069 },
4070
4071 reverse: function(val) {
4072 var arr;
4073 if(lib.isString(val)) {
4074 arr = filters.list(val);
4075 }
4076 else {
4077 // Copy it
4078 arr = lib.map(val, function(v) { return v; });
4079 }
4080
4081 arr.reverse();
4082
4083 if(lib.isString(val)) {
4084 return r.copySafeness(val, arr.join(''));
4085 }
4086 return arr;
4087 },
4088
4089 round: function(val, precision, method) {
4090 precision = precision || 0;
4091 var factor = Math.pow(10, precision);
4092 var rounder;
4093
4094 if(method == 'ceil') {
4095 rounder = Math.ceil;
4096 }
4097 else if(method == 'floor') {
4098 rounder = Math.floor;
4099 }
4100 else {
4101 rounder = Math.round;
4102 }
4103
4104 return rounder(val * factor) / factor;
4105 },
4106
4107 slice: function(arr, slices, fillWith) {
4108 var sliceLength = Math.floor(arr.length / slices);
4109 var extra = arr.length % slices;
4110 var offset = 0;
4111 var res = [];
4112
4113 for(var i=0; i<slices; i++) {
4114 var start = offset + i * sliceLength;
4115 if(i < extra) {
4116 offset++;
4117 }
4118 var end = offset + (i + 1) * sliceLength;
4119
4120 var slice = arr.slice(start, end);
4121 if(fillWith && i >= extra) {
4122 slice.push(fillWith);
4123 }
4124 res.push(slice);
4125 }
4126
4127 return res;
4128 },
4129
4130 sort: function(arr, reverse, caseSens, attr) {
4131 // Copy it
4132 arr = lib.map(arr, function(v) { return v; });
4133
4134 arr.sort(function(a, b) {
4135 var x, y;
4136
4137 if(attr) {
4138 x = a[attr];
4139 y = b[attr];
4140 }
4141 else {
4142 x = a;
4143 y = b;
4144 }
4145
4146 if(!caseSens && lib.isString(x) && lib.isString(y)) {
4147 x = x.toLowerCase();
4148 y = y.toLowerCase();
4149 }
4150
4151 if(x < y) {
4152 return reverse ? 1 : -1;
4153 }
4154 else if(x > y) {
4155 return reverse ? -1: 1;
4156 }
4157 else {
4158 return 0;
4159 }
4160 });
4161
4162 return arr;
4163 },
4164
4165 string: function(obj) {
4166 return r.copySafeness(obj, obj);
4167 },
4168
4169 title: function(str) {
4170 var words = str.split(' ');
4171 for(var i = 0; i < words.length; i++) {
4172 words[i] = filters.capitalize(words[i]);
4173 }
4174 return r.copySafeness(str, words.join(' '));
4175 },
4176
4177 trim: function(str) {
4178 return r.copySafeness(str, str.replace(/^\s*|\s*$/g, ''));
4179 },
4180
4181 truncate: function(input, length, killwords, end) {
4182 var orig = input;
4183 length = length || 255;
4184
4185 if (input.length <= length)
4186 return input;
4187
4188 if (killwords) {
4189 input = input.substring(0, length);
4190 } else {
4191 var idx = input.lastIndexOf(' ', length);
4192 if(idx === -1) {
4193 idx = length;
4194 }
4195
4196 input = input.substring(0, idx);
4197 }
4198
4199 input += (end !== undefined && end !== null) ? end : '...';
4200 return r.copySafeness(orig, input);
4201 },
4202
4203 upper: function(str) {
4204 return str.toUpperCase();
4205 },
4206
4207 urlencode: function(obj) {
4208 var enc = encodeURIComponent;
4209 if (lib.isString(obj)) {
4210 return enc(obj);
4211 } else {
4212 var parts;
4213 if (lib.isArray(obj)) {
4214 parts = obj.map(function(item) {
4215 return enc(item[0]) + '=' + enc(item[1]);
4216 })
4217 } else {
4218 parts = [];
4219 for (var k in obj) {
4220 if (obj.hasOwnProperty(k)) {
4221 parts.push(enc(k) + '=' + enc(obj[k]));
4222 }
4223 }
4224 }
4225 return parts.join('&');
4226 }
4227 },
4228
4229 urlize: function(str, length, nofollow) {
4230 if (isNaN(length)) length = Infinity;
4231
4232 var noFollowAttr = (nofollow === true ? ' rel="nofollow"' : '');
4233
4234 // For the jinja regexp, see
4235 // https://github.com/mitsuhiko/jinja2/blob/f15b814dcba6aa12bc74d1f7d0c881d55f7126be/jinja2/utils.py#L20-L23
4236 var puncRE = /^(?:\(|<|&lt;)?(.*?)(?:\.|,|\)|\n|&gt;)?$/;
4237 // from http://blog.gerv.net/2011/05/html5_email_address_regexp/
4238 var emailRE = /^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i;
4239 var httpHttpsRE = /^https?:\/\/.*$/;
4240 var wwwRE = /^www\./;
4241 var tldRE = /\.(?:org|net|com)(?:\:|\/|$)/;
4242
4243 var words = str.split(/\s+/).filter(function(word) {
4244 // If the word has no length, bail. This can happen for str with
4245 // trailing whitespace.
4246 return word && word.length;
4247 }).map(function(word) {
4248 var matches = word.match(puncRE);
4249
4250 var possibleUrl = matches && matches[1] || word;
4251
4252 // url that starts with http or https
4253 if (httpHttpsRE.test(possibleUrl))
4254 return '<a href="' + possibleUrl + '"' + noFollowAttr + '>' + possibleUrl.substr(0, length) + '</a>';
4255
4256 // url that starts with www.
4257 if (wwwRE.test(possibleUrl))
4258 return '<a href="http://' + possibleUrl + '"' + noFollowAttr + '>' + possibleUrl.substr(0, length) + '</a>';
4259
4260 // an email address of the form username@domain.tld
4261 if (emailRE.test(possibleUrl))
4262 return '<a href="mailto:' + possibleUrl + '">' + possibleUrl + '</a>';
4263
4264 // url that ends in .com, .org or .net that is not an email address
4265 if (tldRE.test(possibleUrl))
4266 return '<a href="http://' + possibleUrl + '"' + noFollowAttr + '>' + possibleUrl.substr(0, length) + '</a>';
4267
4268 return possibleUrl;
4269
4270 });
4271
4272 return words.join(' ');
4273 },
4274
4275 wordcount: function(str) {
4276 var words = (str) ? str.match(/\w+/g) : null;
4277 return (words) ? words.length : null;
4278 },
4279
4280 'float': function(val, def) {
4281 var res = parseFloat(val);
4282 return isNaN(res) ? def : res;
4283 },
4284
4285 'int': function(val, def) {
4286 var res = parseInt(val, 10);
4287 return isNaN(res) ? def : res;
4288 }
4289};
4290
4291// Aliases
4292filters.d = filters['default'];
4293filters.e = filters.escape;
4294
4295modules['filters'] = filters;
4296})();
4297(function() {
4298
4299function cycler(items) {
4300 var index = -1;
4301 var current = null;
4302
4303 return {
4304 reset: function() {
4305 index = -1;
4306 current = null;
4307 },
4308
4309 next: function() {
4310 index++;
4311 if(index >= items.length) {
4312 index = 0;
4313 }
4314
4315 current = items[index];
4316 return current;
4317 }
4318 };
4319
4320}
4321
4322function joiner(sep) {
4323 sep = sep || ',';
4324 var first = true;
4325
4326 return function() {
4327 var val = first ? '' : sep;
4328 first = false;
4329 return val;
4330 };
4331}
4332
4333var globals = {
4334 range: function(start, stop, step) {
4335 if(!stop) {
4336 stop = start;
4337 start = 0;
4338 step = 1;
4339 }
4340 else if(!step) {
4341 step = 1;
4342 }
4343
4344 var arr = [];
4345 for(var i=start; i<stop; i+=step) {
4346 arr.push(i);
4347 }
4348 return arr;
4349 },
4350
4351 // lipsum: function(n, html, min, max) {
4352 // },
4353
4354 cycler: function() {
4355 return cycler(Array.prototype.slice.call(arguments));
4356 },
4357
4358 joiner: function(sep) {
4359 return joiner(sep);
4360 }
4361}
4362
4363modules['globals'] = globals;
4364})();
4365(function() {
4366var Obj = modules["object"];
4367var lib = modules["lib"];
4368
4369var Loader = Obj.extend({
4370 on: function(name, func) {
4371 this.listeners = this.listeners || {};
4372 this.listeners[name] = this.listeners[name] || [];
4373 this.listeners[name].push(func);
4374 },
4375
4376 emit: function(name /*, arg1, arg2, ...*/) {
4377 var args = Array.prototype.slice.call(arguments, 1);
4378
4379 if(this.listeners && this.listeners[name]) {
4380 lib.each(this.listeners[name], function(listener) {
4381 listener.apply(null, args);
4382 });
4383 }
4384 }
4385});
4386
4387modules['loader'] = Loader;
4388})();
4389(function() {
4390var Loader = modules["loader"];
4391
4392var WebLoader = Loader.extend({
4393 init: function(baseURL, neverUpdate) {
4394 // It's easy to use precompiled templates: just include them
4395 // before you configure nunjucks and this will automatically
4396 // pick it up and use it
4397 this.precompiled = window.nunjucksPrecompiled || {};
4398
4399 this.baseURL = baseURL || '';
4400 this.neverUpdate = neverUpdate;
4401 },
4402
4403 getSource: function(name) {
4404 if(this.precompiled[name]) {
4405 return {
4406 src: { type: "code",
4407 obj: this.precompiled[name] },
4408 path: name
4409 };
4410 }
4411 else {
4412 var src = this.fetch(this.baseURL + '/' + name);
4413 if(!src) {
4414 return null;
4415 }
4416
4417 return { src: src,
4418 path: name,
4419 noCache: !this.neverUpdate };
4420 }
4421 },
4422
4423 fetch: function(url, callback) {
4424 // Only in the browser please
4425 var ajax;
4426 var loading = true;
4427 var src;
4428
4429 if(window.XMLHttpRequest) { // Mozilla, Safari, ...
4430 ajax = new XMLHttpRequest();
4431 }
4432 else if(window.ActiveXObject) { // IE 8 and older
4433 ajax = new ActiveXObject("Microsoft.XMLHTTP");
4434 }
4435
4436 ajax.onreadystatechange = function() {
4437 if(ajax.readyState === 4 && (ajax.status === 0 || ajax.status === 200) && loading) {
4438 loading = false;
4439 src = ajax.responseText;
4440 }
4441 };
4442
4443 url += (url.indexOf('?') === -1 ? '?' : '&') + 's=' +
4444 (new Date().getTime());
4445
4446 // Synchronous because this API shouldn't be used in
4447 // production (pre-load compiled templates instead)
4448 ajax.open('GET', url, false);
4449 ajax.send();
4450
4451 return src;
4452 }
4453});
4454
4455modules['web-loaders'] = {
4456 WebLoader: WebLoader
4457};
4458})();
4459(function() {
4460if(typeof window === 'undefined' || window !== this) {
4461 modules['loaders'] = modules["node-loaders"];
4462}
4463else {
4464 modules['loaders'] = modules["web-loaders"];
4465}
4466})();
4467(function() {
4468var path = modules["path"];
4469var lib = modules["lib"];
4470var Obj = modules["object"];
4471var lexer = modules["lexer"];
4472var compiler = modules["compiler"];
4473var builtin_filters = modules["filters"];
4474var builtin_loaders = modules["loaders"];
4475var runtime = modules["runtime"];
4476var globals = modules["globals"];
4477var Frame = runtime.Frame;
4478
4479var Environment = Obj.extend({
4480 init: function(loaders, opts) {
4481 // The dev flag determines the trace that'll be shown on errors.
4482 // If set to true, returns the full trace from the error point,
4483 // otherwise will return trace starting from Template.render
4484 // (the full trace from within nunjucks may confuse developers using
4485 // the library)
4486 // defaults to false
4487 opts = opts || {};
4488 this.dev = !!opts.dev;
4489
4490 // The autoescape flag sets global autoescaping. If true,
4491 // every string variable will be escaped by default.
4492 // If false, strings can be manually escaped using the `escape` filter.
4493 // defaults to false
4494 this.autoesc = !!opts.autoescape;
4495
4496 if(!loaders) {
4497 // The filesystem loader is only available client-side
4498 if(builtin_loaders.FileSystemLoader) {
4499 this.loaders = [new builtin_loaders.FileSystemLoader('views')];
4500 }
4501 else {
4502 this.loaders = [new builtin_loaders.WebLoader('/views')];
4503 }
4504 }
4505 else {
4506 this.loaders = lib.isArray(loaders) ? loaders : [loaders];
4507 }
4508
4509 this.initCache();
4510 this.filters = {};
4511 this.asyncFilters = [];
4512 this.extensions = {};
4513 this.extensionsList = [];
4514
4515 if(opts.tags) {
4516 lexer.setTags(opts.tags);
4517 }
4518
4519 for(var name in builtin_filters) {
4520 this.addFilter(name, builtin_filters[name]);
4521 }
4522 },
4523
4524 initCache: function() {
4525 // Caching and cache busting
4526 var cache = {};
4527
4528 lib.each(this.loaders, function(loader) {
4529 if(typeof loader.on === 'function'){
4530 loader.on('update', function(template) {
4531 cache[template] = null;
4532 });
4533 }
4534 });
4535
4536 this.cache = cache;
4537 },
4538
4539 addExtension: function(name, extension) {
4540 extension._name = name;
4541 this.extensions[name] = extension;
4542 this.extensionsList.push(extension);
4543 },
4544
4545 getExtension: function(name) {
4546 return this.extensions[name];
4547 },
4548
4549 addGlobal: function(name, value) {
4550 globals[name] = value;
4551 },
4552
4553 addFilter: function(name, func, async) {
4554 var wrapped = func;
4555
4556 if(async) {
4557 this.asyncFilters.push(name);
4558 }
4559 this.filters[name] = wrapped;
4560 },
4561
4562 getFilter: function(name) {
4563 if(!this.filters[name]) {
4564 throw new Error('filter not found: ' + name);
4565 }
4566 return this.filters[name];
4567 },
4568
4569 getTemplate: function(name, eagerCompile, cb) {
4570 if(name && name.raw) {
4571 // this fixes autoescape for templates referenced in symbols
4572 name = name.raw;
4573 }
4574
4575 if(lib.isFunction(eagerCompile)) {
4576 cb = eagerCompile;
4577 eagerCompile = false;
4578 }
4579
4580 if(typeof name !== 'string') {
4581 throw new Error('template names must be a string: ' + name);
4582 }
4583
4584 var tmpl = this.cache[name];
4585
4586 if(tmpl) {
4587 if(eagerCompile) {
4588 tmpl.compile();
4589 }
4590
4591 if(cb) {
4592 cb(null, tmpl);
4593 }
4594 else {
4595 return tmpl;
4596 }
4597 } else {
4598 var syncResult;
4599
4600 lib.asyncIter(this.loaders, function(loader, i, next, done) {
4601 function handle(src) {
4602 if(src) {
4603 done(src);
4604 }
4605 else {
4606 next();
4607 }
4608 }
4609
4610 if(loader.async) {
4611 loader.getSource(name, function(err, src) {
4612 if(err) { throw err; }
4613 handle(src);
4614 });
4615 }
4616 else {
4617 handle(loader.getSource(name));
4618 }
4619 }, function(info) {
4620 if(!info) {
4621 var err = new Error('template not found: ' + name);
4622 if(cb) {
4623 cb(err);
4624 }
4625 else {
4626 throw err;
4627 }
4628 }
4629 else {
4630 var tmpl = new Template(info.src, this,
4631 info.path, eagerCompile);
4632
4633 if(!info.noCache) {
4634 this.cache[name] = tmpl;
4635 }
4636
4637 if(cb) {
4638 cb(null, tmpl);
4639 }
4640 else {
4641 syncResult = tmpl;
4642 }
4643 }
4644 }.bind(this));
4645
4646 return syncResult;
4647 }
4648 },
4649
4650 express: function(app) {
4651 var env = this;
4652
4653 function NunjucksView(name, opts) {
4654 this.name = name;
4655 this.path = name;
4656 this.defaultEngine = opts.defaultEngine;
4657 this.ext = path.extname(name);
4658 if (!this.ext && !this.defaultEngine) throw new Error('No default engine was specified and no extension was provided.');
4659 if (!this.ext) this.name += (this.ext = ('.' !== this.defaultEngine[0] ? '.' : '') + this.defaultEngine);
4660 }
4661
4662 NunjucksView.prototype.render = function(opts, cb) {
4663 env.render(this.name, opts, cb);
4664 };
4665
4666 app.set('view', NunjucksView);
4667 },
4668
4669 render: function(name, ctx, cb) {
4670 if(lib.isFunction(ctx)) {
4671 cb = ctx;
4672 ctx = null;
4673 }
4674
4675 // We support a synchronous API to make it easier to migrate
4676 // existing code to async. This works because if you don't do
4677 // anything async work, the whole thing is actually run
4678 // synchronously.
4679 var syncResult = null;
4680
4681 this.getTemplate(name, function(err, tmpl) {
4682 if(err && cb) {
4683 cb(err);
4684 }
4685 else if(err) {
4686 throw err;
4687 }
4688 else {
4689 tmpl.render(ctx, cb || function(err, res) {
4690 if(err) { throw err; }
4691 syncResult = res;
4692 });
4693 }
4694 });
4695
4696 return syncResult;
4697 },
4698
4699 renderString: function(src, ctx, cb) {
4700 var tmpl = new Template(src, this);
4701 return tmpl.render(ctx, cb);
4702 }
4703});
4704
4705var Context = Obj.extend({
4706 init: function(ctx, blocks) {
4707 this.ctx = ctx;
4708 this.blocks = {};
4709 this.exported = [];
4710
4711 for(var name in blocks) {
4712 this.addBlock(name, blocks[name]);
4713 }
4714 },
4715
4716 lookup: function(name) {
4717 // This is one of the most called functions, so optimize for
4718 // the typical case where the name isn't in the globals
4719 if(name in globals && !(name in this.ctx)) {
4720 return globals[name];
4721 }
4722 else {
4723 return this.ctx[name];
4724 }
4725 },
4726
4727 setVariable: function(name, val) {
4728 this.ctx[name] = val;
4729 },
4730
4731 getVariables: function() {
4732 return this.ctx;
4733 },
4734
4735 addBlock: function(name, block) {
4736 this.blocks[name] = this.blocks[name] || [];
4737 this.blocks[name].push(block);
4738 },
4739
4740 getBlock: function(name) {
4741 if(!this.blocks[name]) {
4742 throw new Error('unknown block "' + name + '"');
4743 }
4744
4745 return this.blocks[name][0];
4746 },
4747
4748 getSuper: function(env, name, block, frame, runtime, cb) {
4749 var idx = (this.blocks[name] || []).indexOf(block);
4750 var blk = this.blocks[name][idx + 1];
4751 var context = this;
4752
4753 if(idx == -1 || !blk) {
4754 throw new Error('no super block available for "' + name + '"');
4755 }
4756
4757 blk(env, context, frame, runtime, cb);
4758 },
4759
4760 addExport: function(name) {
4761 this.exported.push(name);
4762 },
4763
4764 getExported: function() {
4765 var exported = {};
4766 for(var i=0; i<this.exported.length; i++) {
4767 var name = this.exported[i];
4768 exported[name] = this.ctx[name];
4769 }
4770 return exported;
4771 }
4772});
4773
4774var Template = Obj.extend({
4775 init: function (src, env, path, eagerCompile) {
4776 this.env = env || new Environment();
4777
4778 if(lib.isObject(src)) {
4779 switch(src.type) {
4780 case 'code': this.tmplProps = src.obj; break;
4781 case 'string': this.tmplStr = src.obj; break;
4782 }
4783 }
4784 else if(lib.isString(src)) {
4785 this.tmplStr = src;
4786 }
4787 else {
4788 throw new Error("src must be a string or an object describing " +
4789 "the source");
4790 }
4791
4792 this.path = path;
4793
4794 if(eagerCompile) {
4795 lib.withPrettyErrors(this.path,
4796 this.env.dev,
4797 this._compile.bind(this));
4798 }
4799 else {
4800 this.compiled = false;
4801 }
4802 },
4803
4804 render: function(ctx, frame, cb) {
4805 if (typeof ctx === 'function') {
4806 cb = ctx;
4807 ctx = {};
4808 }
4809 else if (typeof frame === 'function') {
4810 cb = frame;
4811 frame = null;
4812 }
4813
4814 return lib.withPrettyErrors(this.path, this.env.dev, function() {
4815 this.compile();
4816
4817 var context = new Context(ctx || {}, this.blocks);
4818 var syncResult = null;
4819
4820 this.rootRenderFunc(this.env,
4821 context,
4822 frame || new Frame(),
4823 runtime,
4824 cb || function(err, res) {
4825 if(err) { throw err; }
4826 syncResult = res;
4827 });
4828
4829 return syncResult;
4830 }.bind(this));
4831 },
4832
4833 getExported: function(cb) {
4834 this.compile();
4835
4836 // Run the rootRenderFunc to populate the context with exported vars
4837 var context = new Context({}, this.blocks);
4838 this.rootRenderFunc(this.env,
4839 context,
4840 new Frame(),
4841 runtime,
4842 function() {
4843 cb(null, context.getExported());
4844 });
4845 },
4846
4847 compile: function() {
4848 if(!this.compiled) {
4849 this._compile();
4850 }
4851 },
4852
4853 _compile: function() {
4854 var props;
4855
4856 if(this.tmplProps) {
4857 props = this.tmplProps;
4858 }
4859 else {
4860 var source = compiler.compile(this.tmplStr,
4861 this.env.asyncFilters,
4862 this.env.extensionsList,
4863 this.path);
4864 var func = new Function(source);
4865 props = func();
4866 }
4867
4868 this.blocks = this._getBlocks(props);
4869 this.rootRenderFunc = props.root;
4870 this.compiled = true;
4871 },
4872
4873 _getBlocks: function(props) {
4874 var blocks = {};
4875
4876 for(var k in props) {
4877 if(k.slice(0, 2) == 'b_') {
4878 blocks[k.slice(2)] = props[k];
4879 }
4880 }
4881
4882 return blocks;
4883 }
4884});
4885
4886// test code
4887// var src = '{% macro foo() %}{% include "include.html" %}{% endmacro %}{{ foo() }}';
4888// var env = new Environment(new builtin_loaders.FileSystemLoader('../tests/templates', true), { dev: true });
4889// console.log(env.renderString(src, { name: 'poop' }));
4890
4891modules['environment'] = {
4892 Environment: Environment,
4893 Template: Template
4894};
4895})();
4896var nunjucks;
4897
4898var lib = modules["lib"];
4899var env = modules["environment"];
4900var compiler = modules["compiler"];
4901var parser = modules["parser"];
4902var lexer = modules["lexer"];
4903var runtime = modules["runtime"];
4904var Loader = modules["loader"];
4905var loaders = modules["loaders"];
4906var precompile = modules["precompile"];
4907
4908nunjucks = {};
4909nunjucks.Environment = env.Environment;
4910nunjucks.Template = env.Template;
4911
4912nunjucks.Loader = Loader;
4913nunjucks.FileSystemLoader = loaders.FileSystemLoader;
4914nunjucks.WebLoader = loaders.WebLoader;
4915
4916nunjucks.compiler = compiler;
4917nunjucks.parser = parser;
4918nunjucks.lexer = lexer;
4919nunjucks.runtime = runtime;
4920
4921// A single instance of an environment, since this is so commonly used
4922
4923var e;
4924nunjucks.configure = function(templatesPath, opts) {
4925 opts = opts || {};
4926 if(lib.isObject(templatesPath)) {
4927 opts = templatesPath;
4928 templatesPath = null;
4929 }
4930
4931 var noWatch = 'watch' in opts ? !opts.watch : false;
4932 var loader = loaders.FileSystemLoader || loaders.WebLoader;
4933 e = new env.Environment(new loader(templatesPath, noWatch), opts);
4934
4935 if(opts && opts.express) {
4936 e.express(opts.express);
4937 }
4938
4939 return e;
4940};
4941
4942nunjucks.compile = function(src, env, path, eagerCompile) {
4943 if(!e) {
4944 nunjucks.configure();
4945 }
4946 return new nunjucks.Template(src, env, path, eagerCompile);
4947};
4948
4949nunjucks.render = function(name, ctx, cb) {
4950 if(!e) {
4951 nunjucks.configure();
4952 }
4953
4954 return e.render(name, ctx, cb);
4955};
4956
4957nunjucks.renderString = function(src, ctx, cb) {
4958 if(!e) {
4959 nunjucks.configure();
4960 }
4961
4962 return e.renderString(src, ctx, cb);
4963};
4964
4965if(precompile) {
4966 nunjucks.precompile = precompile.precompile;
4967 nunjucks.precompileString = precompile.precompileString;
4968}
4969
4970nunjucks.require = function(name) { return modules[name]; };
4971
4972if(typeof define === 'function' && define.amd) {
4973 define(function() { return nunjucks; });
4974}
4975else {
4976 window.nunjucks = nunjucks;
4977 if(typeof module !== 'undefined') module.exports = nunjucks;
4978}
4979
4980})();