PageRenderTime 32ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/files/nunjucks/1.0.0/nunjucks.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1858 lines | 1638 code | 162 blank | 58 comment | 202 complexity | b6cfa29709eb5e174a487e0a7690f826 MD5 | raw file
  1. // Browser bundle of nunjucks 1.0.0
  2. (function() {
  3. var modules = {};
  4. (function() {
  5. // A simple class system, more documentation to come
  6. function extend(cls, name, props) {
  7. // This does that same thing as Object.create, but with support for IE8
  8. var F = function() {};
  9. F.prototype = cls.prototype;
  10. var prototype = new F();
  11. var fnTest = /xyz/.test(function(){ xyz; }) ? /\bparent\b/ : /.*/;
  12. props = props || {};
  13. for(var k in props) {
  14. var src = props[k];
  15. var parent = prototype[k];
  16. if(typeof parent == "function" &&
  17. typeof src == "function" &&
  18. fnTest.test(src)) {
  19. prototype[k] = (function (src, parent) {
  20. return function() {
  21. // Save the current parent method
  22. var tmp = this.parent;
  23. // Set parent to the previous method, call, and restore
  24. this.parent = parent;
  25. var res = src.apply(this, arguments);
  26. this.parent = tmp;
  27. return res;
  28. };
  29. })(src, parent);
  30. }
  31. else {
  32. prototype[k] = src;
  33. }
  34. }
  35. prototype.typename = name;
  36. var new_cls = function() {
  37. if(prototype.init) {
  38. prototype.init.apply(this, arguments);
  39. }
  40. };
  41. new_cls.prototype = prototype;
  42. new_cls.prototype.constructor = new_cls;
  43. new_cls.extend = function(name, props) {
  44. if(typeof name == "object") {
  45. props = name;
  46. name = "anonymous";
  47. }
  48. return extend(new_cls, name, props);
  49. };
  50. return new_cls;
  51. }
  52. modules['object'] = extend(Object, "Object", {});
  53. })();
  54. (function() {
  55. var ArrayProto = Array.prototype;
  56. var ObjProto = Object.prototype;
  57. var escapeMap = {
  58. '&': '&',
  59. '"': '"',
  60. "'": ''',
  61. "<": '&lt;',
  62. ">": '&gt;'
  63. };
  64. var lookupEscape = function(ch) {
  65. return escapeMap[ch];
  66. };
  67. var exports = modules['lib'] = {};
  68. exports.withPrettyErrors = function(path, withInternals, func) {
  69. try {
  70. return func();
  71. } catch (e) {
  72. if (!e.Update) {
  73. // not one of ours, cast it
  74. e = new exports.TemplateError(e);
  75. }
  76. e.Update(path);
  77. // Unless they marked the dev flag, show them a trace from here
  78. if (!withInternals) {
  79. var old = e;
  80. e = new Error(old.message);
  81. e.name = old.name;
  82. }
  83. throw e;
  84. }
  85. };
  86. exports.TemplateError = function(message, lineno, colno) {
  87. var err = this;
  88. if (message instanceof Error) { // for casting regular js errors
  89. err = message;
  90. message = message.name + ": " + message.message;
  91. } else {
  92. if(Error.captureStackTrace) {
  93. Error.captureStackTrace(err);
  94. }
  95. }
  96. err.name = 'Template render error';
  97. err.message = message;
  98. err.lineno = lineno;
  99. err.colno = colno;
  100. err.firstUpdate = true;
  101. err.Update = function(path) {
  102. var message = "(" + (path || "unknown path") + ")";
  103. // only show lineno + colno next to path of template
  104. // where error occurred
  105. if (this.firstUpdate) {
  106. if(this.lineno && this.colno) {
  107. message += ' [Line ' + this.lineno + ', Column ' + this.colno + ']';
  108. }
  109. else if(this.lineno) {
  110. message += ' [Line ' + this.lineno + ']';
  111. }
  112. }
  113. message += '\n ';
  114. if (this.firstUpdate) {
  115. message += ' ';
  116. }
  117. this.message = message + (this.message || '');
  118. this.firstUpdate = false;
  119. return this;
  120. };
  121. return err;
  122. };
  123. exports.TemplateError.prototype = Error.prototype;
  124. exports.escape = function(val) {
  125. return val.replace(/[&"'<>]/g, lookupEscape);
  126. };
  127. exports.isFunction = function(obj) {
  128. return ObjProto.toString.call(obj) == '[object Function]';
  129. };
  130. exports.isArray = Array.isArray || function(obj) {
  131. return ObjProto.toString.call(obj) == '[object Array]';
  132. };
  133. exports.isString = function(obj) {
  134. return ObjProto.toString.call(obj) == '[object String]';
  135. };
  136. exports.isObject = function(obj) {
  137. return obj === Object(obj);
  138. };
  139. exports.groupBy = function(obj, val) {
  140. var result = {};
  141. var iterator = exports.isFunction(val) ? val : function(obj) { return obj[val]; };
  142. for(var i=0; i<obj.length; i++) {
  143. var value = obj[i];
  144. var key = iterator(value, i);
  145. (result[key] || (result[key] = [])).push(value);
  146. }
  147. return result;
  148. };
  149. exports.toArray = function(obj) {
  150. return Array.prototype.slice.call(obj);
  151. };
  152. exports.without = function(array) {
  153. var result = [];
  154. if (!array) {
  155. return result;
  156. }
  157. var index = -1,
  158. length = array.length,
  159. contains = exports.toArray(arguments).slice(1);
  160. while(++index < length) {
  161. if(contains.indexOf(array[index]) === -1) {
  162. result.push(array[index]);
  163. }
  164. }
  165. return result;
  166. };
  167. exports.extend = function(obj, obj2) {
  168. for(var k in obj2) {
  169. obj[k] = obj2[k];
  170. }
  171. return obj;
  172. };
  173. exports.repeat = function(char_, n) {
  174. var str = '';
  175. for(var i=0; i<n; i++) {
  176. str += char_;
  177. }
  178. return str;
  179. };
  180. exports.each = function(obj, func, context) {
  181. if(obj == null) {
  182. return;
  183. }
  184. if(ArrayProto.each && obj.each == ArrayProto.each) {
  185. obj.forEach(func, context);
  186. }
  187. else if(obj.length === +obj.length) {
  188. for(var i=0, l=obj.length; i<l; i++) {
  189. func.call(context, obj[i], i, obj);
  190. }
  191. }
  192. };
  193. exports.map = function(obj, func) {
  194. var results = [];
  195. if(obj == null) {
  196. return results;
  197. }
  198. if(ArrayProto.map && obj.map === ArrayProto.map) {
  199. return obj.map(func);
  200. }
  201. for(var i=0; i<obj.length; i++) {
  202. results[results.length] = func(obj[i], i);
  203. }
  204. if(obj.length === +obj.length) {
  205. results.length = obj.length;
  206. }
  207. return results;
  208. };
  209. exports.asyncParallel = function(funcs, done) {
  210. var count = funcs.length,
  211. result = new Array(count),
  212. current = 0;
  213. var makeNext = function(i) {
  214. return function(res) {
  215. result[i] = res;
  216. current += 1;
  217. if (current === count) {
  218. done(result);
  219. }
  220. };
  221. };
  222. for (var i = 0; i < count; i++) {
  223. funcs[i](makeNext(i));
  224. }
  225. };
  226. exports.asyncIter = function(arr, iter, cb) {
  227. var i = -1;
  228. function next() {
  229. i++;
  230. if(i < arr.length) {
  231. iter(arr[i], i, next, cb);
  232. }
  233. else {
  234. cb();
  235. }
  236. }
  237. next();
  238. };
  239. exports.asyncFor = function(obj, iter, cb) {
  240. var keys = exports.keys(obj);
  241. var len = keys.length;
  242. var i = -1;
  243. function next() {
  244. i++;
  245. var k = keys[i];
  246. if(i < len) {
  247. iter(k, obj[k], i, len, next);
  248. }
  249. else {
  250. cb();
  251. }
  252. }
  253. next();
  254. };
  255. if(!Array.prototype.indexOf) {
  256. Array.prototype.indexOf = function(array, searchElement /*, fromIndex */) {
  257. if (array == null) {
  258. throw new TypeError();
  259. }
  260. var t = Object(array);
  261. var len = t.length >>> 0;
  262. if (len === 0) {
  263. return -1;
  264. }
  265. var n = 0;
  266. if (arguments.length > 2) {
  267. n = Number(arguments[2]);
  268. if (n != n) { // shortcut for verifying if it's NaN
  269. n = 0;
  270. } else if (n != 0 && n != Infinity && n != -Infinity) {
  271. n = (n > 0 || -1) * Math.floor(Math.abs(n));
  272. }
  273. }
  274. if (n >= len) {
  275. return -1;
  276. }
  277. var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
  278. for (; k < len; k++) {
  279. if (k in t && t[k] === searchElement) {
  280. return k;
  281. }
  282. }
  283. return -1;
  284. };
  285. }
  286. if(!Array.prototype.map) {
  287. Array.prototype.map = function() {
  288. throw new Error("map is unimplemented for this js engine");
  289. };
  290. }
  291. exports.keys = function(obj) {
  292. if(Object.prototype.keys) {
  293. return obj.keys();
  294. }
  295. else {
  296. var keys = [];
  297. for(var k in obj) {
  298. if(obj.hasOwnProperty(k)) {
  299. keys.push(k);
  300. }
  301. }
  302. return keys;
  303. }
  304. }
  305. })();
  306. (function() {
  307. var util = modules["util"];
  308. var lib = modules["lib"];
  309. var Object = modules["object"];
  310. function traverseAndCheck(obj, type, results) {
  311. if(obj instanceof type) {
  312. results.push(obj);
  313. }
  314. if(obj instanceof Node) {
  315. obj.findAll(type, results);
  316. }
  317. }
  318. var Node = Object.extend("Node", {
  319. init: function(lineno, colno) {
  320. this.lineno = lineno;
  321. this.colno = colno;
  322. var fields = this.fields;
  323. for(var i=0, l=fields.length; i<l; i++) {
  324. var field = fields[i];
  325. // The first two args are line/col numbers, so offset by 2
  326. var val = arguments[i + 2];
  327. // Fields should never be undefined, but null. It makes
  328. // testing easier to normalize values.
  329. if(val === undefined) {
  330. val = null;
  331. }
  332. this[field] = val;
  333. }
  334. },
  335. findAll: function(type, results) {
  336. results = results || [];
  337. if(this instanceof NodeList) {
  338. var children = this.children;
  339. for(var i=0, l=children.length; i<l; i++) {
  340. traverseAndCheck(children[i], type, results);
  341. }
  342. }
  343. else {
  344. var fields = this.fields;
  345. for(var i=0, l=fields.length; i<l; i++) {
  346. traverseAndCheck(this[fields[i]], type, results);
  347. }
  348. }
  349. return results;
  350. },
  351. iterFields: function(func) {
  352. lib.each(this.fields, function(field) {
  353. func(this[field], field);
  354. }, this);
  355. }
  356. });
  357. // Abstract nodes
  358. var Value = Node.extend("Value", { fields: ['value'] });
  359. // Concrete nodes
  360. var NodeList = Node.extend("NodeList", {
  361. fields: ['children'],
  362. init: function(lineno, colno, nodes) {
  363. this.parent(lineno, colno, nodes || []);
  364. },
  365. addChild: function(node) {
  366. this.children.push(node);
  367. }
  368. });
  369. var Root = NodeList.extend("Root");
  370. var Literal = Value.extend("Literal");
  371. var Symbol = Value.extend("Symbol");
  372. var Group = NodeList.extend("Group");
  373. var Array = NodeList.extend("Array");
  374. var Pair = Node.extend("Pair", { fields: ['key', 'value'] });
  375. var Dict = NodeList.extend("Dict");
  376. var LookupVal = Node.extend("LookupVal", { fields: ['target', 'val'] });
  377. var If = Node.extend("If", { fields: ['cond', 'body', 'else_'] });
  378. var IfAsync = If.extend("IfAsync");
  379. var InlineIf = Node.extend("InlineIf", { fields: ['cond', 'body', 'else_'] });
  380. var For = Node.extend("For", { fields: ['arr', 'name', 'body'] });
  381. var AsyncEach = For.extend("AsyncEach");
  382. var AsyncAll = For.extend("AsyncAll");
  383. var Macro = Node.extend("Macro", { fields: ['name', 'args', 'body'] });
  384. var Import = Node.extend("Import", { fields: ['template', 'target'] });
  385. var FromImport = Node.extend("FromImport", {
  386. fields: ['template', 'names'],
  387. init: function(lineno, colno, template, names) {
  388. this.parent(lineno, colno,
  389. template,
  390. names || new NodeList());
  391. }
  392. });
  393. var FunCall = Node.extend("FunCall", { fields: ['name', 'args'] });
  394. var Filter = FunCall.extend("Filter");
  395. var FilterAsync = Filter.extend("FilterAsync", {
  396. fields: ['name', 'args', 'symbol']
  397. });
  398. var KeywordArgs = Dict.extend("KeywordArgs");
  399. var Block = Node.extend("Block", { fields: ['name', 'body'] });
  400. var Super = Node.extend("Super", { fields: ['blockName', 'symbol'] });
  401. var TemplateRef = Node.extend("TemplateRef", { fields: ['template'] });
  402. var Extends = TemplateRef.extend("Extends");
  403. var Include = TemplateRef.extend("Include");
  404. var Set = Node.extend("Set", { fields: ['targets', 'value'] });
  405. var Output = NodeList.extend("Output");
  406. var TemplateData = Literal.extend("TemplateData");
  407. var UnaryOp = Node.extend("UnaryOp", { fields: ['target'] });
  408. var BinOp = Node.extend("BinOp", { fields: ['left', 'right'] });
  409. var Or = BinOp.extend("Or");
  410. var And = BinOp.extend("And");
  411. var Not = UnaryOp.extend("Not");
  412. var Add = BinOp.extend("Add");
  413. var Sub = BinOp.extend("Sub");
  414. var Mul = BinOp.extend("Mul");
  415. var Div = BinOp.extend("Div");
  416. var FloorDiv = BinOp.extend("FloorDiv");
  417. var Mod = BinOp.extend("Mod");
  418. var Pow = BinOp.extend("Pow");
  419. var Neg = UnaryOp.extend("Neg");
  420. var Pos = UnaryOp.extend("Pos");
  421. var Compare = Node.extend("Compare", { fields: ['expr', 'ops'] });
  422. var CompareOperand = Node.extend("CompareOperand", {
  423. fields: ['expr', 'type']
  424. });
  425. var CustomTag = Node.extend("CustomTag", {
  426. init: function(lineno, colno, name) {
  427. this.lineno = lineno;
  428. this.colno = colno;
  429. this.name = name;
  430. }
  431. });
  432. var CallExtension = Node.extend("CallExtension", {
  433. fields: ['extName', 'prop', 'args', 'contentArgs'],
  434. init: function(ext, prop, args, contentArgs) {
  435. this.extName = ext._name || ext;
  436. this.prop = prop;
  437. this.args = args || new NodeList();
  438. this.contentArgs = contentArgs || [];
  439. }
  440. });
  441. var CallExtensionAsync = CallExtension.extend("CallExtensionAsync");
  442. // Print the AST in a nicely formatted tree format for debuggin
  443. function printNodes(node, indent) {
  444. indent = indent || 0;
  445. // This is hacky, but this is just a debugging function anyway
  446. function print(str, indent, inline) {
  447. var lines = str.split("\n");
  448. for(var i=0; i<lines.length; i++) {
  449. if(lines[i]) {
  450. if((inline && i > 0) || !inline) {
  451. for(var j=0; j<indent; j++) {
  452. util.print(" ");
  453. }
  454. }
  455. }
  456. if(i === lines.length-1) {
  457. util.print(lines[i]);
  458. }
  459. else {
  460. util.puts(lines[i]);
  461. }
  462. }
  463. }
  464. print(node.typename + ": ", indent);
  465. if(node instanceof NodeList) {
  466. print('\n');
  467. lib.each(node.children, function(n) {
  468. printNodes(n, indent + 2);
  469. });
  470. }
  471. else if(node instanceof CallExtension) {
  472. print(node.extName + '.' + node.prop);
  473. print('\n');
  474. if(node.args) {
  475. printNodes(node.args, indent + 2);
  476. }
  477. if(node.contentArgs) {
  478. lib.each(node.contentArgs, function(n) {
  479. printNodes(n, indent + 2);
  480. });
  481. }
  482. }
  483. else {
  484. var nodes = null;
  485. var props = null;
  486. node.iterFields(function(val, field) {
  487. if(val instanceof Node) {
  488. nodes = nodes || {};
  489. nodes[field] = val;
  490. }
  491. else {
  492. props = props || {};
  493. props[field] = val;
  494. }
  495. });
  496. if(props) {
  497. print(util.inspect(props, true, null) + '\n', null, true);
  498. }
  499. else {
  500. print('\n');
  501. }
  502. if(nodes) {
  503. for(var k in nodes) {
  504. printNodes(nodes[k], indent + 2);
  505. }
  506. }
  507. }
  508. }
  509. // var t = new NodeList(0, 0,
  510. // [new Value(0, 0, 3),
  511. // new Value(0, 0, 10),
  512. // new Pair(0, 0,
  513. // new Value(0, 0, 'key'),
  514. // new Value(0, 0, 'value'))]);
  515. // printNodes(t);
  516. modules['nodes'] = {
  517. Node: Node,
  518. Root: Root,
  519. NodeList: NodeList,
  520. Value: Value,
  521. Literal: Literal,
  522. Symbol: Symbol,
  523. Group: Group,
  524. Array: Array,
  525. Pair: Pair,
  526. Dict: Dict,
  527. Output: Output,
  528. TemplateData: TemplateData,
  529. If: If,
  530. IfAsync: IfAsync,
  531. InlineIf: InlineIf,
  532. For: For,
  533. AsyncEach: AsyncEach,
  534. AsyncAll: AsyncAll,
  535. Macro: Macro,
  536. Import: Import,
  537. FromImport: FromImport,
  538. FunCall: FunCall,
  539. Filter: Filter,
  540. FilterAsync: FilterAsync,
  541. KeywordArgs: KeywordArgs,
  542. Block: Block,
  543. Super: Super,
  544. Extends: Extends,
  545. Include: Include,
  546. Set: Set,
  547. LookupVal: LookupVal,
  548. BinOp: BinOp,
  549. Or: Or,
  550. And: And,
  551. Not: Not,
  552. Add: Add,
  553. Sub: Sub,
  554. Mul: Mul,
  555. Div: Div,
  556. FloorDiv: FloorDiv,
  557. Mod: Mod,
  558. Pow: Pow,
  559. Neg: Neg,
  560. Pos: Pos,
  561. Compare: Compare,
  562. CompareOperand: CompareOperand,
  563. CallExtension: CallExtension,
  564. CallExtensionAsync: CallExtensionAsync,
  565. printNodes: printNodes
  566. };
  567. })();
  568. (function() {
  569. var lib = modules["lib"];
  570. var Object = modules["object"];
  571. // Frames keep track of scoping both at compile-time and run-time so
  572. // we know how to access variables. Block tags can introduce special
  573. // variables, for example.
  574. var Frame = Object.extend({
  575. init: function(parent) {
  576. this.variables = {};
  577. this.parent = parent;
  578. },
  579. set: function(name, val) {
  580. // Allow variables with dots by automatically creating the
  581. // nested structure
  582. var parts = name.split('.');
  583. var obj = this.variables;
  584. for(var i=0; i<parts.length - 1; i++) {
  585. var id = parts[i];
  586. if(!obj[id]) {
  587. obj[id] = {};
  588. }
  589. obj = obj[id];
  590. }
  591. obj[parts[parts.length - 1]] = val;
  592. },
  593. get: function(name) {
  594. var val = this.variables[name];
  595. if(val !== undefined && val !== null) {
  596. return val;
  597. }
  598. return null;
  599. },
  600. lookup: function(name) {
  601. var p = this.parent;
  602. var val = this.variables[name];
  603. if(val !== undefined && val !== null) {
  604. return val;
  605. }
  606. return p && p.lookup(name);
  607. },
  608. push: function() {
  609. return new Frame(this);
  610. },
  611. pop: function() {
  612. return this.parent;
  613. }
  614. });
  615. function makeMacro(argNames, kwargNames, func) {
  616. return function() {
  617. var argCount = numArgs(arguments);
  618. var args;
  619. var kwargs = getKeywordArgs(arguments);
  620. if(argCount > argNames.length) {
  621. args = Array.prototype.slice.call(arguments, 0, argNames.length);
  622. // Positional arguments that should be passed in as
  623. // keyword arguments (essentially default values)
  624. var vals = Array.prototype.slice.call(arguments, args.length, argCount);
  625. for(var i=0; i<vals.length; i++) {
  626. if(i < kwargNames.length) {
  627. kwargs[kwargNames[i]] = vals[i];
  628. }
  629. }
  630. args.push(kwargs);
  631. }
  632. else if(argCount < argNames.length) {
  633. args = Array.prototype.slice.call(arguments, 0, argCount);
  634. for(var i=argCount; i<argNames.length; i++) {
  635. var arg = argNames[i];
  636. // Keyword arguments that should be passed as
  637. // positional arguments, i.e. the caller explicitly
  638. // used the name of a positional arg
  639. args.push(kwargs[arg]);
  640. delete kwargs[arg];
  641. }
  642. args.push(kwargs);
  643. }
  644. else {
  645. args = arguments;
  646. }
  647. return func.apply(this, args);
  648. };
  649. }
  650. function makeKeywordArgs(obj) {
  651. obj.__keywords = true;
  652. return obj;
  653. }
  654. function getKeywordArgs(args) {
  655. var len = args.length;
  656. if(len) {
  657. var lastArg = args[len - 1];
  658. if(lastArg && lastArg.hasOwnProperty('__keywords')) {
  659. return lastArg;
  660. }
  661. }
  662. return {};
  663. }
  664. function numArgs(args) {
  665. var len = args.length;
  666. if(len === 0) {
  667. return 0;
  668. }
  669. var lastArg = args[len - 1];
  670. if(lastArg && lastArg.hasOwnProperty('__keywords')) {
  671. return len - 1;
  672. }
  673. else {
  674. return len;
  675. }
  676. }
  677. // A SafeString object indicates that the string should not be
  678. // autoescaped. This happens magically because autoescaping only
  679. // occurs on primitive string objects.
  680. function SafeString(val) {
  681. if(typeof val != 'string') {
  682. return val;
  683. }
  684. this.toString = function() {
  685. return val;
  686. };
  687. this.length = val.length;
  688. var methods = [
  689. 'charAt', 'charCodeAt', 'concat', 'contains',
  690. 'endsWith', 'fromCharCode', 'indexOf', 'lastIndexOf',
  691. 'length', 'localeCompare', 'match', 'quote', 'replace',
  692. 'search', 'slice', 'split', 'startsWith', 'substr',
  693. 'substring', 'toLocaleLowerCase', 'toLocaleUpperCase',
  694. 'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight'
  695. ];
  696. for(var i=0; i<methods.length; i++) {
  697. this[methods[i]] = markSafe(val[methods[i]]);
  698. }
  699. }
  700. function copySafeness(dest, target) {
  701. if(dest instanceof SafeString) {
  702. return new SafeString(target);
  703. }
  704. return target.toString();
  705. }
  706. function markSafe(val) {
  707. var type = typeof val;
  708. if(type === 'string') {
  709. return new SafeString(val);
  710. }
  711. else if(type !== 'function') {
  712. return val;
  713. }
  714. else {
  715. return function() {
  716. var ret = val.apply(this, arguments);
  717. if(typeof ret === 'string') {
  718. return new SafeString(ret);
  719. }
  720. return ret;
  721. };
  722. }
  723. }
  724. function suppressValue(val, autoescape) {
  725. val = (val !== undefined && val !== null) ? val : "";
  726. if(autoescape && typeof val === "string") {
  727. val = lib.escape(val);
  728. }
  729. return val;
  730. }
  731. function memberLookup(obj, val) {
  732. obj = obj || {};
  733. if(typeof obj[val] === 'function') {
  734. return function() {
  735. return obj[val].apply(obj, arguments);
  736. };
  737. }
  738. return obj[val];
  739. }
  740. function callWrap(obj, name, args) {
  741. if(!obj) {
  742. throw new Error('Unable to call `' + name + '`, which is undefined or falsey');
  743. }
  744. else if(typeof obj !== 'function') {
  745. throw new Error('Unable to call `' + name + '`, which is not a function');
  746. }
  747. return obj.apply(this, args);
  748. }
  749. function contextOrFrameLookup(context, frame, name) {
  750. var val = frame.lookup(name);
  751. return (val !== undefined && val !== null) ?
  752. val :
  753. context.lookup(name);
  754. }
  755. function handleError(error, lineno, colno) {
  756. if(error.lineno) {
  757. return error;
  758. }
  759. else {
  760. return new lib.TemplateError(error, lineno, colno);
  761. }
  762. }
  763. function asyncEach(arr, dimen, iter, cb) {
  764. if(lib.isArray(arr)) {
  765. var len = arr.length;
  766. lib.asyncIter(arr, function(item, i, next) {
  767. switch(dimen) {
  768. case 1: iter(item, i, len, next); break;
  769. case 2: iter(item[0], item[1], i, len, next); break;
  770. case 3: iter(item[0], item[1], item[2], i, len, next); break;
  771. default:
  772. item.push(i, next);
  773. iter.apply(this, item);
  774. }
  775. }, cb);
  776. }
  777. else {
  778. lib.asyncFor(arr, function(key, val, i, len, next) {
  779. iter(key, val, i, len, next);
  780. }, cb);
  781. }
  782. }
  783. function asyncAll(arr, dimen, func, cb) {
  784. var finished = 0;
  785. var len;
  786. var outputArr;
  787. function done(i, output) {
  788. finished++;
  789. outputArr[i] = output;
  790. if(finished == len) {
  791. cb(null, outputArr.join(''));
  792. }
  793. }
  794. if(lib.isArray(arr)) {
  795. len = arr.length;
  796. outputArr = new Array(len);
  797. if(len == 0) {
  798. cb(null, '');
  799. }
  800. else {
  801. for(var i=0; i<arr.length; i++) {
  802. var item = arr[i];
  803. switch(dimen) {
  804. case 1: func(item, i, len, done); break;
  805. case 2: func(item[0], item[1], i, len, done); break;
  806. case 3: func(item[0], item[1], item[2], i, len, done); break;
  807. default:
  808. item.push(i, done);
  809. func.apply(this, item);
  810. }
  811. }
  812. }
  813. }
  814. else {
  815. var keys = lib.keys(arr);
  816. len = keys.length;
  817. outputArr = new Array(len);
  818. if(len == 0) {
  819. cb(null, '');
  820. }
  821. else {
  822. for(var i=0; i<keys.length; i++) {
  823. var k = keys[i];
  824. func(k, arr[k], i, len, done);
  825. }
  826. }
  827. }
  828. }
  829. modules['runtime'] = {
  830. Frame: Frame,
  831. makeMacro: makeMacro,
  832. makeKeywordArgs: makeKeywordArgs,
  833. numArgs: numArgs,
  834. suppressValue: suppressValue,
  835. memberLookup: memberLookup,
  836. contextOrFrameLookup: contextOrFrameLookup,
  837. callWrap: callWrap,
  838. handleError: handleError,
  839. isArray: lib.isArray,
  840. asyncEach: lib.asyncEach,
  841. keys: lib.keys,
  842. SafeString: SafeString,
  843. copySafeness: copySafeness,
  844. markSafe: markSafe,
  845. asyncEach: asyncEach,
  846. asyncAll: asyncAll
  847. };
  848. })();
  849. (function() {
  850. var lib = modules["lib"];
  851. var whitespaceChars = " \n\t\r";
  852. var delimChars = "()[]{}%*-+/#,:|.<>=!";
  853. var intChars = "0123456789";
  854. var BLOCK_START = "{%";
  855. var BLOCK_END = "%}";
  856. var VARIABLE_START = "{{";
  857. var VARIABLE_END = "}}";
  858. var COMMENT_START = "{#";
  859. var COMMENT_END = "#}";
  860. var TOKEN_STRING = "string";
  861. var TOKEN_WHITESPACE = "whitespace";
  862. var TOKEN_DATA = "data";
  863. var TOKEN_BLOCK_START = "block-start";
  864. var TOKEN_BLOCK_END = "block-end";
  865. var TOKEN_VARIABLE_START = "variable-start";
  866. var TOKEN_VARIABLE_END = "variable-end";
  867. var TOKEN_COMMENT = "comment";
  868. var TOKEN_LEFT_PAREN = "left-paren";
  869. var TOKEN_RIGHT_PAREN = "right-paren";
  870. var TOKEN_LEFT_BRACKET = "left-bracket";
  871. var TOKEN_RIGHT_BRACKET = "right-bracket";
  872. var TOKEN_LEFT_CURLY = "left-curly";
  873. var TOKEN_RIGHT_CURLY = "right-curly";
  874. var TOKEN_OPERATOR = "operator";
  875. var TOKEN_COMMA = "comma";
  876. var TOKEN_COLON = "colon";
  877. var TOKEN_PIPE = "pipe";
  878. var TOKEN_INT = "int";
  879. var TOKEN_FLOAT = "float";
  880. var TOKEN_BOOLEAN = "boolean";
  881. var TOKEN_SYMBOL = "symbol";
  882. var TOKEN_SPECIAL = "special";
  883. function token(type, value, lineno, colno) {
  884. return {
  885. type: type,
  886. value: value,
  887. lineno: lineno,
  888. colno: colno
  889. };
  890. }
  891. function Tokenizer(str) {
  892. this.str = str;
  893. this.index = 0;
  894. this.len = str.length;
  895. this.lineno = 0;
  896. this.colno = 0;
  897. this.in_code = false;
  898. }
  899. Tokenizer.prototype.nextToken = function() {
  900. var lineno = this.lineno;
  901. var colno = this.colno;
  902. if(this.in_code) {
  903. // Otherwise, if we are in a block parse it as code
  904. var cur = this.current();
  905. var tok;
  906. if(this.is_finished()) {
  907. // We have nothing else to parse
  908. return null;
  909. }
  910. else if(cur == "\"" || cur == "'") {
  911. // We've hit a string
  912. return token(TOKEN_STRING, this.parseString(cur), lineno, colno);
  913. }
  914. else if((tok = this._extract(whitespaceChars))) {
  915. // We hit some whitespace
  916. return token(TOKEN_WHITESPACE, tok, lineno, colno);
  917. }
  918. else if((tok = this._extractString(BLOCK_END)) ||
  919. (tok = this._extractString('-' + BLOCK_END))) {
  920. // Special check for the block end tag
  921. //
  922. // It is a requirement that start and end tags are composed of
  923. // delimiter characters (%{}[] etc), and our code always
  924. // breaks on delimiters so we can assume the token parsing
  925. // doesn't consume these elsewhere
  926. this.in_code = false;
  927. return token(TOKEN_BLOCK_END, tok, lineno, colno);
  928. }
  929. else if((tok = this._extractString(VARIABLE_END))) {
  930. // Special check for variable end tag (see above)
  931. this.in_code = false;
  932. return token(TOKEN_VARIABLE_END, tok, lineno, colno);
  933. }
  934. else if(delimChars.indexOf(cur) != -1) {
  935. // We've hit a delimiter (a special char like a bracket)
  936. this.forward();
  937. var complexOps = ['==', '!=', '<=', '>=', '//', '**'];
  938. var curComplex = cur + this.current();
  939. var type;
  940. if(complexOps.indexOf(curComplex) !== -1) {
  941. this.forward();
  942. cur = curComplex;
  943. }
  944. switch(cur) {
  945. case "(": type = TOKEN_LEFT_PAREN; break;
  946. case ")": type = TOKEN_RIGHT_PAREN; break;
  947. case "[": type = TOKEN_LEFT_BRACKET; break;
  948. case "]": type = TOKEN_RIGHT_BRACKET; break;
  949. case "{": type = TOKEN_LEFT_CURLY; break;
  950. case "}": type = TOKEN_RIGHT_CURLY; break;
  951. case ",": type = TOKEN_COMMA; break;
  952. case ":": type = TOKEN_COLON; break;
  953. case "|": type = TOKEN_PIPE; break;
  954. default: type = TOKEN_OPERATOR;
  955. }
  956. return token(type, cur, lineno, colno);
  957. }
  958. else {
  959. // We are not at whitespace or a delimiter, so extract the
  960. // text and parse it
  961. tok = this._extractUntil(whitespaceChars + delimChars);
  962. if(tok.match(/^[-+]?[0-9]+$/)) {
  963. if(this.current() == '.') {
  964. this.forward();
  965. var dec = this._extract(intChars);
  966. return token(TOKEN_FLOAT, tok + '.' + dec, lineno, colno);
  967. }
  968. else {
  969. return token(TOKEN_INT, tok, lineno, colno);
  970. }
  971. }
  972. else if(tok.match(/^(true|false)$/)) {
  973. return token(TOKEN_BOOLEAN, tok, lineno, colno);
  974. }
  975. else if(tok) {
  976. return token(TOKEN_SYMBOL, tok, lineno, colno);
  977. }
  978. else {
  979. throw new Error("Unexpected value while parsing: " + tok);
  980. }
  981. }
  982. }
  983. else {
  984. // Parse out the template text, breaking on tag
  985. // delimiters because we need to look for block/variable start
  986. // tags (don't use the full delimChars for optimization)
  987. var beginChars = (BLOCK_START.charAt(0) +
  988. VARIABLE_START.charAt(0) +
  989. COMMENT_START.charAt(0) +
  990. COMMENT_END.charAt(0));
  991. var tok;
  992. if(this.is_finished()) {
  993. return null;
  994. }
  995. else if((tok = this._extractString(BLOCK_START + '-')) ||
  996. (tok = this._extractString(BLOCK_START))) {
  997. this.in_code = true;
  998. return token(TOKEN_BLOCK_START, tok, lineno, colno);
  999. }
  1000. else if((tok = this._extractString(VARIABLE_START))) {
  1001. this.in_code = true;
  1002. return token(TOKEN_VARIABLE_START, tok, lineno, colno);
  1003. }
  1004. else {
  1005. tok = '';
  1006. var data;
  1007. var in_comment = false;
  1008. if(this._matches(COMMENT_START)) {
  1009. in_comment = true;
  1010. tok = this._extractString(COMMENT_START);
  1011. }
  1012. // Continually consume text, breaking on the tag delimiter
  1013. // characters and checking to see if it's a start tag.
  1014. //
  1015. // We could hit the end of the template in the middle of
  1016. // our looping, so check for the null return value from
  1017. // _extractUntil
  1018. while((data = this._extractUntil(beginChars)) !== null) {
  1019. tok += data;
  1020. if((this._matches(BLOCK_START) ||
  1021. this._matches(VARIABLE_START) ||
  1022. this._matches(COMMENT_START)) &&
  1023. !in_comment) {
  1024. // If it is a start tag, stop looping
  1025. break;
  1026. }
  1027. else if(this._matches(COMMENT_END)) {
  1028. if(!in_comment) {
  1029. throw new Error("unexpected end of comment");
  1030. }
  1031. tok += this._extractString(COMMENT_END);
  1032. break;
  1033. }
  1034. else {
  1035. // It does not match any tag, so add the character and
  1036. // carry on
  1037. tok += this.current();
  1038. this.forward();
  1039. }
  1040. }
  1041. if(data === null && in_comment) {
  1042. throw new Error("expected end of comment, got end of file");
  1043. }
  1044. return token(in_comment ? TOKEN_COMMENT : TOKEN_DATA,
  1045. tok,
  1046. lineno,
  1047. colno);
  1048. }
  1049. }
  1050. throw new Error("Could not parse text");
  1051. };
  1052. Tokenizer.prototype.parseString = function(delimiter) {
  1053. this.forward();
  1054. var lineno = this.lineno;
  1055. var colno = this.colno;
  1056. var str = "";
  1057. while(!this.is_finished() && this.current() != delimiter) {
  1058. var cur = this.current();
  1059. if(cur == "\\") {
  1060. this.forward();
  1061. switch(this.current()) {
  1062. case "n": str += "\n"; break;
  1063. case "t": str += "\t"; break;
  1064. case "r": str += "\r"; break;
  1065. default:
  1066. str += this.current();
  1067. }
  1068. this.forward();
  1069. }
  1070. else {
  1071. str += cur;
  1072. this.forward();
  1073. }
  1074. }
  1075. this.forward();
  1076. return str;
  1077. };
  1078. Tokenizer.prototype._matches = function(str) {
  1079. if(this.index + str.length > this.length) {
  1080. return null;
  1081. }
  1082. var m = this.str.slice(this.index, this.index + str.length);
  1083. return m == str;
  1084. };
  1085. Tokenizer.prototype._extractString = function(str) {
  1086. if(this._matches(str)) {
  1087. this.index += str.length;
  1088. return str;
  1089. }
  1090. return null;
  1091. };
  1092. Tokenizer.prototype._extractUntil = function(charString) {
  1093. // Extract all non-matching chars, with the default matching set
  1094. // to everything
  1095. return this._extractMatching(true, charString || "");
  1096. };
  1097. Tokenizer.prototype._extract = function(charString) {
  1098. // Extract all matching chars (no default, so charString must be
  1099. // explicit)
  1100. return this._extractMatching(false, charString);
  1101. };
  1102. Tokenizer.prototype._extractMatching = function (breakOnMatch, charString) {
  1103. // Pull out characters until a breaking char is hit.
  1104. // If breakOnMatch is false, a non-matching char stops it.
  1105. // If breakOnMatch is true, a matching char stops it.
  1106. if(this.is_finished()) {
  1107. return null;
  1108. }
  1109. var first = charString.indexOf(this.current());
  1110. // Only proceed if the first character doesn't meet our condition
  1111. if((breakOnMatch && first == -1) ||
  1112. (!breakOnMatch && first != -1)) {
  1113. var t = this.current();
  1114. this.forward();
  1115. // And pull out all the chars one at a time until we hit a
  1116. // breaking char
  1117. var idx = charString.indexOf(this.current());
  1118. while(((breakOnMatch && idx == -1) ||
  1119. (!breakOnMatch && idx != -1)) && !this.is_finished()) {
  1120. t += this.current();
  1121. this.forward();
  1122. idx = charString.indexOf(this.current());
  1123. }
  1124. return t;
  1125. }
  1126. return "";
  1127. };
  1128. Tokenizer.prototype.is_finished = function() {
  1129. return this.index >= this.len;
  1130. };
  1131. Tokenizer.prototype.forwardN = function(n) {
  1132. for(var i=0; i<n; i++) {
  1133. this.forward();
  1134. }
  1135. };
  1136. Tokenizer.prototype.forward = function() {
  1137. this.index++;
  1138. if(this.previous() == "\n") {
  1139. this.lineno++;
  1140. this.colno = 0;
  1141. }
  1142. else {
  1143. this.colno++;
  1144. }
  1145. };
  1146. Tokenizer.prototype.backN = function(n) {
  1147. for(var i=0; i<n; i++) {
  1148. self.back();
  1149. }
  1150. };
  1151. Tokenizer.prototype.back = function() {
  1152. this.index--;
  1153. if(this.current() == "\n") {
  1154. this.lineno--;
  1155. var idx = this.src.lastIndexOf("\n", this.index-1);
  1156. if(idx == -1) {
  1157. this.colno = this.index;
  1158. }
  1159. else {
  1160. this.colno = this.index - idx;
  1161. }
  1162. }
  1163. else {
  1164. this.colno--;
  1165. }
  1166. };
  1167. Tokenizer.prototype.current = function() {
  1168. if(!this.is_finished()) {
  1169. return this.str.charAt(this.index);
  1170. }
  1171. return "";
  1172. };
  1173. Tokenizer.prototype.previous = function() {
  1174. return this.str.charAt(this.index-1);
  1175. };
  1176. modules['lexer'] = {
  1177. lex: function(src) {
  1178. return new Tokenizer(src);
  1179. },
  1180. setTags: function(tags) {
  1181. BLOCK_START = tags.blockStart || BLOCK_START;
  1182. BLOCK_END = tags.blockEnd || BLOCK_END;
  1183. VARIABLE_START = tags.variableStart || VARIABLE_START;
  1184. VARIABLE_END = tags.variableEnd || VARIABLE_END;
  1185. COMMENT_START = tags.commentStart || COMMENT_START;
  1186. COMMENT_END = tags.commentEnd || COMMENT_END;
  1187. },
  1188. TOKEN_STRING: TOKEN_STRING,
  1189. TOKEN_WHITESPACE: TOKEN_WHITESPACE,
  1190. TOKEN_DATA: TOKEN_DATA,
  1191. TOKEN_BLOCK_START: TOKEN_BLOCK_START,
  1192. TOKEN_BLOCK_END: TOKEN_BLOCK_END,
  1193. TOKEN_VARIABLE_START: TOKEN_VARIABLE_START,
  1194. TOKEN_VARIABLE_END: TOKEN_VARIABLE_END,
  1195. TOKEN_COMMENT: TOKEN_COMMENT,
  1196. TOKEN_LEFT_PAREN: TOKEN_LEFT_PAREN,
  1197. TOKEN_RIGHT_PAREN: TOKEN_RIGHT_PAREN,
  1198. TOKEN_LEFT_BRACKET: TOKEN_LEFT_BRACKET,
  1199. TOKEN_RIGHT_BRACKET: TOKEN_RIGHT_BRACKET,
  1200. TOKEN_LEFT_CURLY: TOKEN_LEFT_CURLY,
  1201. TOKEN_RIGHT_CURLY: TOKEN_RIGHT_CURLY,
  1202. TOKEN_OPERATOR: TOKEN_OPERATOR,
  1203. TOKEN_COMMA: TOKEN_COMMA,
  1204. TOKEN_COLON: TOKEN_COLON,
  1205. TOKEN_PIPE: TOKEN_PIPE,
  1206. TOKEN_INT: TOKEN_INT,
  1207. TOKEN_FLOAT: TOKEN_FLOAT,
  1208. TOKEN_BOOLEAN: TOKEN_BOOLEAN,
  1209. TOKEN_SYMBOL: TOKEN_SYMBOL,
  1210. TOKEN_SPECIAL: TOKEN_SPECIAL
  1211. };
  1212. })();
  1213. (function() {
  1214. var lexer = modules["lexer"];
  1215. var nodes = modules["nodes"];
  1216. var Object = modules["object"];
  1217. var lib = modules["lib"];
  1218. var Parser = Object.extend({
  1219. init: function (tokens) {
  1220. this.tokens = tokens;
  1221. this.peeked = null;
  1222. this.breakOnBlocks = null;
  1223. this.dropLeadingWhitespace = false;
  1224. this.extensions = [];
  1225. },
  1226. nextToken: function (withWhitespace) {
  1227. var tok;
  1228. if(this.peeked) {
  1229. if(!withWhitespace && this.peeked.type == lexer.TOKEN_WHITESPACE) {
  1230. this.peeked = null;
  1231. }
  1232. else {
  1233. tok = this.peeked;
  1234. this.peeked = null;
  1235. return tok;
  1236. }
  1237. }
  1238. tok = this.tokens.nextToken();
  1239. if(!withWhitespace) {
  1240. while(tok && tok.type == lexer.TOKEN_WHITESPACE) {
  1241. tok = this.tokens.nextToken();
  1242. }
  1243. }
  1244. return tok;
  1245. },
  1246. peekToken: function () {
  1247. this.peeked = this.peeked || this.nextToken();
  1248. return this.peeked;
  1249. },
  1250. pushToken: function(tok) {
  1251. if(this.peeked) {
  1252. throw new Error("pushToken: can only push one token on between reads");
  1253. }
  1254. this.peeked = tok;
  1255. },
  1256. fail: function (msg, lineno, colno) {
  1257. if((lineno === undefined || colno === undefined) && this.peekToken()) {
  1258. var tok = this.peekToken();
  1259. lineno = tok.lineno;
  1260. colno = tok.colno;
  1261. }
  1262. if (lineno !== undefined) lineno += 1;
  1263. if (colno !== undefined) colno += 1;
  1264. throw new lib.TemplateError(msg, lineno, colno);
  1265. },
  1266. skip: function(type) {
  1267. var tok = this.nextToken();
  1268. if(!tok || tok.type != type) {
  1269. this.pushToken(tok);
  1270. return false;
  1271. }
  1272. return true;
  1273. },
  1274. expect: function(type) {
  1275. var tok = this.nextToken();
  1276. if(!tok.type == type) {
  1277. this.fail('expected ' + type + ', got ' + tok.type,
  1278. tok.lineno,
  1279. tok.colno);
  1280. }
  1281. return tok;
  1282. },
  1283. skipValue: function(type, val) {
  1284. var tok = this.nextToken();
  1285. if(!tok || tok.type != type || tok.value != val) {
  1286. this.pushToken(tok);
  1287. return false;
  1288. }
  1289. return true;
  1290. },
  1291. skipWhitespace: function () {
  1292. return this.skip(lexer.TOKEN_WHITESPACE);
  1293. },
  1294. skipSymbol: function(val) {
  1295. return this.skipValue(lexer.TOKEN_SYMBOL, val);
  1296. },
  1297. advanceAfterBlockEnd: function(name) {
  1298. if(!name) {
  1299. var tok = this.peekToken();
  1300. if(!tok) {
  1301. this.fail('unexpected end of file');
  1302. }
  1303. if(tok.type != lexer.TOKEN_SYMBOL) {
  1304. this.fail("advanceAfterBlockEnd: expected symbol token or " +
  1305. "explicit name to be passed");
  1306. }
  1307. name = this.nextToken().value;
  1308. }
  1309. var tok = this.nextToken();
  1310. if(tok && tok.type == lexer.TOKEN_BLOCK_END) {
  1311. if(tok.value.charAt(0) === '-') {
  1312. this.dropLeadingWhitespace = true;
  1313. }
  1314. }
  1315. else {
  1316. this.fail("expected block end in " + name + " statement");
  1317. }
  1318. },
  1319. advanceAfterVariableEnd: function() {
  1320. if(!this.skip(lexer.TOKEN_VARIABLE_END)) {
  1321. this.fail("expected variable end");
  1322. }
  1323. },
  1324. parseFor: function() {
  1325. var forTok = this.peekToken();
  1326. var node;
  1327. var endBlock;
  1328. if(this.skipSymbol('for')) {
  1329. node = new nodes.For(forTok.lineno, forTok.colno);
  1330. endBlock = 'endfor';
  1331. }
  1332. else if(this.skipSymbol('asyncEach')) {
  1333. node = new nodes.AsyncEach(forTok.lineno, forTok.colno);
  1334. endBlock = 'endeach';
  1335. }
  1336. else if(this.skipSymbol('asyncAll')) {
  1337. node = new nodes.AsyncAll(forTok.lineno, forTok.colno);
  1338. endBlock = 'endall';
  1339. }
  1340. else {
  1341. this.fail("parseFor: expected for{Async}", forTok.lineno, forTok.colno);
  1342. }
  1343. node.name = this.parsePrimary();
  1344. if(!(node.name instanceof nodes.Symbol)) {
  1345. this.fail('parseFor: variable name expected for loop');
  1346. }
  1347. var type = this.peekToken().type;
  1348. if(type == lexer.TOKEN_COMMA) {
  1349. // key/value iteration
  1350. var key = node.name;
  1351. node.name = new nodes.Array(key.lineno, key.colno);
  1352. node.name.addChild(key);
  1353. while(this.skip(lexer.TOKEN_COMMA)) {
  1354. var prim = this.parsePrimary();
  1355. node.name.addChild(prim);
  1356. }
  1357. }
  1358. if(!this.skipSymbol('in')) {
  1359. this.fail('parseFor: expected "in" keyword for loop',
  1360. forTok.lineno,
  1361. forTok.colno);
  1362. }
  1363. node.arr = this.parseExpression();
  1364. this.advanceAfterBlockEnd(forTok.value);
  1365. node.body = this.parseUntilBlocks(endBlock);
  1366. this.advanceAfterBlockEnd();
  1367. return node;
  1368. },
  1369. parseMacro: function() {
  1370. var macroTok = this.peekToken();
  1371. if(!this.skipSymbol('macro')) {
  1372. this.fail("expected macro");
  1373. }
  1374. var name = this.parsePrimary(true);
  1375. var args = this.parseSignature();
  1376. var node = new nodes.Macro(macroTok.lineno,
  1377. macroTok.colno,
  1378. name,
  1379. args);
  1380. this.advanceAfterBlockEnd(macroTok.value);
  1381. node.body = this.parseUntilBlocks('endmacro');
  1382. this.advanceAfterBlockEnd();
  1383. return node;
  1384. },
  1385. parseImport: function() {
  1386. var importTok = this.peekToken();
  1387. if(!this.skipSymbol('import')) {
  1388. this.fail("parseImport: expected import",
  1389. importTok.lineno,
  1390. importTok.colno);
  1391. }
  1392. var template = this.parsePrimary();
  1393. if(!this.skipSymbol('as')) {
  1394. this.fail('parseImport: expected "as" keyword',
  1395. importTok.lineno,
  1396. importTok.colno);
  1397. }
  1398. var target = this.parsePrimary();
  1399. var node = new nodes.Import(importTok.lineno,
  1400. importTok.colno,
  1401. template,
  1402. target);
  1403. this.advanceAfterBlockEnd(importTok.value);
  1404. return node;
  1405. },
  1406. parseFrom: function() {
  1407. var fromTok = this.peekToken();
  1408. if(!this.skipSymbol('from')) {
  1409. this.fail("parseFrom: expected from");
  1410. }
  1411. var template = this.parsePrimary();
  1412. var node = new nodes.FromImport(fromTok.lineno,
  1413. fromTok.colno,
  1414. template,
  1415. new nodes.NodeList());
  1416. if(!this.skipSymbol('import')) {
  1417. this.fail("parseFrom: expected import",
  1418. fromTok.lineno,
  1419. fromTok.colno);
  1420. }
  1421. var names = node.names;
  1422. while(1) {
  1423. var nextTok = this.peekToken();
  1424. if(nextTok.type == lexer.TOKEN_BLOCK_END) {
  1425. if(!names.children.length) {
  1426. this.fail('parseFrom: Expected at least one import name',
  1427. fromTok.lineno,
  1428. fromTok.colno);
  1429. }
  1430. // Since we are manually advancing past the block end,
  1431. // need to keep track of whitespace control (normally
  1432. // this is done in `advanceAfterBlockEnd`
  1433. if(nextTok.value.charAt(0) == '-') {
  1434. this.dropLeadingWhitespace = true;
  1435. }
  1436. this.nextToken();
  1437. break;
  1438. }
  1439. if(names.children.length > 0 && !this.skip(lexer.TOKEN_COMMA)) {
  1440. this.fail('parseFrom: expected comma',
  1441. fromTok.lineno,
  1442. fromTok.colno);
  1443. }
  1444. var name = this.parsePrimary();
  1445. if(name.value.charAt(0) == '_') {
  1446. this.fail('parseFrom: names starting with an underscore ' +
  1447. 'cannot be imported',
  1448. name.lineno,
  1449. name.colno);
  1450. }
  1451. if(this.skipSymbol('as')) {
  1452. var alias = this.parsePrimary();
  1453. names.addChild(new nodes.Pair(name.lineno,
  1454. name.colno,
  1455. name,
  1456. alias));
  1457. }
  1458. else {
  1459. names.addChild(name);
  1460. }
  1461. }
  1462. return node;
  1463. },
  1464. parseBlock: function() {
  1465. var tag = this.peekToken();
  1466. if(!this.skipSymbol('block')) {
  1467. this.fail('parseBlock: expected block', tag.lineno, tag.colno);
  1468. }
  1469. var node = new nodes.Block(tag.lineno, tag.colno);
  1470. node.name = this.parsePrimary();
  1471. if(!(node.name instanceof nodes.Symbol)) {
  1472. this.fail('parseBlock: variable name expected',
  1473. tag.lineno,
  1474. tag.colno);
  1475. }
  1476. this.advanceAfterBlockEnd(tag.value);
  1477. node.body = this.parseUntilBlocks('endblock');
  1478. if(!this.peekToken()) {
  1479. this.fail('parseBlock: expected endblock, got end of file');
  1480. }
  1481. this.advanceAfterBlockEnd();
  1482. return node;
  1483. },
  1484. parseTemplateRef: function(tagName, nodeType) {
  1485. var tag = this.peekToken();
  1486. if(!this.skipSymbol(tagName)) {
  1487. this.fail('parseTemplateRef: expected '+ tagName);
  1488. }
  1489. var node = new nodeType(tag.lineno, tag.colno);
  1490. node.template = this.parsePrimary();
  1491. this.advanceAfterBlockEnd(tag.value);
  1492. return node;
  1493. },
  1494. parseExtends: function() {
  1495. return this.parseTemplateRef('extends', nodes.Extends);
  1496. },
  1497. parseInclude: function() {
  1498. return this.parseTemplateRef('include', nodes.Include);
  1499. },
  1500. parseIf: function() {
  1501. var tag = this.peekToken();
  1502. var node;
  1503. if(this.skipSymbol('if') || this.skipSymbol('elif')) {
  1504. node = new nodes.If(tag.lineno, tag.colno);
  1505. }
  1506. else if(this.skipSymbol('ifAsync')) {
  1507. node = new nodes.IfAsync(tag.lineno, tag.colno);
  1508. }
  1509. else {
  1510. this.fail("parseIf: expected if or elif",
  1511. tag.lineno,
  1512. tag.colno);
  1513. }
  1514. node.cond = this.parseExpression();
  1515. this.advanceAfterBlockEnd(tag.value);
  1516. node.body = this.parseUntilBlocks('elif', 'else', 'endif');
  1517. var tok = this.peekToken();
  1518. switch(tok && tok.value) {
  1519. case "elif":
  1520. node.else_ = this.parseIf();
  1521. break;
  1522. case "else":
  1523. this.advanceAfterBlockEnd();
  1524. node.else_ = this.parseUntilBlocks("endif");
  1525. this.advanceAfterBlockEnd();
  1526. break;
  1527. case "endif":
  1528. node.else_ = null;
  1529. this.advanceAfterBlockEnd();
  1530. break;
  1531. default:
  1532. this.fail('parseIf: expected endif, else, or endif, ' +
  1533. 'got end of file');
  1534. }
  1535. return node;
  1536. },
  1537. parseSet: function() {
  1538. var tag = this.peekToken();
  1539. if(!this.skipSymbol('set')) {
  1540. this.fail('parseSet: expected set', tag.lineno, tag.colno);
  1541. }
  1542. var node = new nodes.Set(tag.lineno, tag.colno, []);
  1543. var target;
  1544. while((target = this.parsePrimary())) {
  1545. node.targets.push(target);
  1546. if(!this.skip(lexer.TOKEN_COMMA)) {
  1547. break;
  1548. }
  1549. }
  1550. if(!this.skipValue(lexer.TOKEN_OPERATOR, '=')) {
  1551. this.fail('parseSet: expected = in set tag',
  1552. tag.lineno,
  1553. tag.colno);
  1554. }
  1555. node.value = this.parseExpression();
  1556. this.advanceAfterBlockEnd(tag.value);
  1557. return node;
  1558. },
  1559. parseStatement: function () {
  1560. var tok = this.peekToken();
  1561. var node;
  1562. if(tok.type != lexer.TOKEN_SYMBOL) {
  1563. this.fail('tag name expected', tok.lineno, tok.colno);
  1564. }
  1565. if(this.breakOnBlocks &&
  1566. this.breakOnBlocks.indexOf(tok.value) !== -1) {
  1567. return null;
  1568. }
  1569. switch(tok.value) {