PageRenderTime 35ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/site/tests/serverjs/template_defaults.js

https://github.com/ozten/openwebapps
JavaScript | 853 lines | 741 code | 67 blank | 45 comment | 76 complexity | 5ea8fd002eebacf33afbb8feab7fedd1 MD5 | raw file
  1. /*jslint eqeqeq: true, undef: true, regexp: false */
  2. /*global require, process, exports, escape */
  3. var sys = require('sys');
  4. var string_utils = require('./utils/string');
  5. var date_utils = require('./utils/date');
  6. var html = require('./utils/html');
  7. var iter = require('./utils/iter');
  8. var import = require('./utils/import').import;
  9. import(GLOBAL, require('./utils/tags'));
  10. /* TODO: Missing filters
  11. Don't know how:
  12. iriencode
  13. Not implemented (yet):
  14. unordered_list
  15. NOTE:
  16. stringformat() filter is regular sprintf compliant and doesn't have real python syntax
  17. Missing tags:
  18. ssi (will require ALLOWED_INCLUDE_ROOTS somehow)
  19. debug
  20. NOTE:
  21. cycle tag does not support legacy syntax (row1,row2,row3)
  22. load takes a path - like require. Loaded module must expose tags and filters objects.
  23. url tag relies on app being set in process.djangode_urls
  24. */
  25. var filters = exports.filters = {
  26. add: function (value, arg) {
  27. value = value - 0;
  28. arg = arg - 0;
  29. return (isNaN(value) || isNaN(arg)) ? '' : (value + arg);
  30. },
  31. addslashes: function (value, arg) { return string_utils.add_slashes("" + value); },
  32. capfirst: function (value, arg) { return string_utils.cap_first("" + value); },
  33. center: function (value, arg) { return string_utils.center("" + value, arg - 0); },
  34. cut: function (value, arg) { return ("" + value).replace(new RegExp(arg, 'g'), ""); },
  35. date: function (value, arg) {
  36. // TODO: this filter may be unsafe...
  37. return (value instanceof Date) ? date_utils.format_date(value, arg) : '';
  38. },
  39. 'default': function (value, arg) {
  40. // TODO: this filter may be unsafe...
  41. return value ? value : arg;
  42. },
  43. default_if_none: function (value, arg) {
  44. // TODO: this filter may be unsafe...
  45. return (value === null || value === undefined) ? arg : value;
  46. },
  47. dictsort: function (value, arg) {
  48. var clone = value.slice(0);
  49. clone.sort(function (a, b) { return a[arg] < b[arg] ? -1 : a[arg] > b[arg] ? 1 : 0; });
  50. return clone;
  51. },
  52. dictsortreversed: function (value, arg) {
  53. var tmp = filters.dictsort(value, arg);
  54. tmp.reverse();
  55. return tmp;
  56. },
  57. divisibleby: function (value, arg) { return value % arg === 0; },
  58. escape: function (value, arg, safety) {
  59. safety.must_escape = true;
  60. return value;
  61. },
  62. escapejs: function (value, arg) { return escape(value || ''); },
  63. filesizeformat: function (value, arg) {
  64. var bytes = value - 0;
  65. if (isNaN(bytes)) { return "0 bytes"; }
  66. if (bytes <= 1) { return '1 byte'; }
  67. if (bytes < 1024) { return bytes.toFixed(0) + ' bytes'; }
  68. if (bytes < 1024 * 1024) { return (bytes / 1024).toFixed(1) + 'KB'; }
  69. if (bytes < 1024 * 1024 * 1024) { return (bytes / (1024 * 1024)).toFixed(1) + 'MB'; }
  70. return (bytes / (1024 * 1024 * 1024)).toFixed(1) + 'GB';
  71. },
  72. first: function (value, arg) { return (value instanceof Array) ? value[0] : ""; },
  73. fix_ampersands: function (value, arg, safety) {
  74. safety.is_safe = true;
  75. return ("" + value).replace('&', '&amp;');
  76. },
  77. floatformat: function (value, arg) {
  78. arg = arg - 0 || -1;
  79. var num = value - 0,
  80. show_zeroes = arg > 0,
  81. fix = Math.abs(arg);
  82. if (isNaN(num)) {
  83. return '';
  84. }
  85. var s = num.toFixed(fix);
  86. if (!show_zeroes && s % 1 === 0) {
  87. return num.toFixed(0);
  88. }
  89. return s;
  90. },
  91. force_escape: function (value, arg, safety) {
  92. safety.is_safe = true;
  93. return html.escape("" + value);
  94. },
  95. get_digit: function (value, arg) {
  96. if (typeof value !== 'number' || typeof arg !== 'number' || arg < 1) { return value; }
  97. var s = "" + value;
  98. return s[s.length - arg] - 0;
  99. },
  100. iriencode: function (value, arg) {
  101. // TODO: implement iriencode filter
  102. throw "iri encoding is not implemented";
  103. },
  104. join: function (value, arg) {
  105. // TODO: this filter may be unsafe...
  106. return (value instanceof Array) ? value.join(arg) : '';
  107. },
  108. last: function (value, arg) { return ((value instanceof Array) && value.length) ? value[value.length - 1] : ''; },
  109. length: function (value, arg) { return value.length ? value.length : 0; },
  110. length_is: function (value, arg) { return value.length === arg; },
  111. linebreaks: function (value, arg, safety) {
  112. var out = html.linebreaks("" + value, { escape: !safety.is_safe && safety.must_escape });
  113. safety.is_safe = true;
  114. return out;
  115. },
  116. linebreaksbr: function (value, arg, safety) {
  117. var out = html.linebreaks("" + value, { onlybr: true, escape: !safety.is_safe && safety.must_escape });
  118. safety.is_safe = true;
  119. return out;
  120. },
  121. linenumbers: function (value, arg, safety) {
  122. var lines = String(value).split('\n');
  123. var len = String(lines.length).length;
  124. var out = lines
  125. .map(function (s, idx) {
  126. if (!safety.is_safe && safety.must_escape) {
  127. s = html.escape("" + s);
  128. }
  129. return string_utils.sprintf('%0' + len + 'd. %s', idx + 1, s); })
  130. .join('\n');
  131. safety.is_safe = true;
  132. return out;
  133. },
  134. ljust: function (value, arg) {
  135. arg = arg - 0;
  136. try {
  137. return string_utils.sprintf('%-' + arg + 's', value).substr(0, arg);
  138. } catch (e) {
  139. return '';
  140. }
  141. },
  142. lower: function (value, arg) { return typeof value === 'string' ? value.toLowerCase() : ''; },
  143. make_list: function (value, arg) { return String(value).split(''); },
  144. phone2numeric: function (value, arg) {
  145. value = String(value).toLowerCase();
  146. return value.replace(/[a-pr-y]/g, function (x) {
  147. var code = x.charCodeAt(0) - 91;
  148. if (code > 22) { code = code - 1; }
  149. return Math.floor(code / 3);
  150. });
  151. },
  152. pluralize: function (value, arg) {
  153. // TODO: this filter may not be safe
  154. value = Number(value);
  155. var plural = arg ? String(arg).split(',') : ['', 's'];
  156. if (plural.length === 1) { plural.unshift(''); }
  157. if (isNaN(value)) { return ''; }
  158. return value === 1 ? plural[0] : plural[1];
  159. },
  160. pprint: function (value, arg) { return JSON.stringify(value); },
  161. random: function (value, arg) {
  162. return (value instanceof Array) ? value[ Math.floor( Math.random() * value.length ) ] : '';
  163. },
  164. removetags: function (value, arg, safety) {
  165. arg = String(arg).replace(/\s+/g, '|');
  166. var re = new RegExp( '</?\\s*(' + arg + ')\\b[^>]*/?>', 'ig');
  167. safety.is_safe = true;
  168. return String(value).replace(re, '');
  169. },
  170. rjust: function (value, arg) {
  171. try {
  172. return string_utils.sprintf('%' + arg + 's', value).substr(0, arg);
  173. } catch (e) {
  174. return '';
  175. }
  176. },
  177. safe: function (value, arg, safety) {
  178. safety.is_safe = true;
  179. return value;
  180. },
  181. safeseq: function (value, arg) {
  182. safety.is_safe = true;
  183. return value;
  184. },
  185. slice: function (value, arg) {
  186. if (!(value instanceof Array)) { return []; }
  187. var parts = (arg || '').split(/:/g);
  188. if (parts[1] === '') {
  189. parts[1] = value.length;
  190. }
  191. parts = parts.map(Number);
  192. if (!parts[2]) {
  193. return value.slice(parts[0], parts[1]);
  194. }
  195. var out = [], i = parts[0], end = parts[1];
  196. for (;i < end; i += parts[2]) {
  197. out.push(value[i]);
  198. }
  199. return out;
  200. },
  201. slugify: function (value, arg) {
  202. return String(value).toLowerCase().replace(/[^\w\s]/g, '').replace(/\s+/g, '-');
  203. },
  204. stringformat: function (value, arg) {
  205. // TODO: this filter may not be safe
  206. try { return string_utils.sprintf('%' + arg, value); } catch (e) { return ''; }
  207. },
  208. striptags: function (value, arg, safety) {
  209. safety.is_safe = true;
  210. return String(value).replace(/<(.|\n)*?>/g, '');
  211. },
  212. title: function (value, arg) {
  213. return string_utils.titleCaps( String(value) );
  214. },
  215. time: function (value, arg) {
  216. // TODO: this filter may not be safe
  217. return (value instanceof Date) ? date_utils.format_time(value, arg) : '';
  218. },
  219. timesince: function (value, arg) {
  220. // TODO: this filter may not be safe (if people decides to put & or " in formatstrings"
  221. value = new Date(value);
  222. arg = new Date(arg);
  223. if (isNaN(value) || isNaN(arg)) { return ''; }
  224. return date_utils.timesince(value, arg);
  225. },
  226. timeuntil: function (value, arg) {
  227. // TODO: this filter may not be safe (if people decides to put & or " in formatstrings"
  228. value = new Date(value);
  229. arg = new Date(arg);
  230. if (isNaN(value) || isNaN(arg)) { return ''; }
  231. return date_utils.timeuntil(value, arg);
  232. },
  233. truncatewords: function (value, arg) {
  234. return String(value).split(/\s+/g).slice(0, arg).join(' ') + ' ...';
  235. },
  236. truncatewords_html: function (value, arg, safety) {
  237. safety.is_safe = true;
  238. return html.truncate_html_words(value, arg - 0);
  239. },
  240. upper: function (value, arg) {
  241. return (value + '').toUpperCase();
  242. },
  243. urlencode: function (value, arg) {
  244. return escape(value);
  245. },
  246. urlize: function (value, arg, safety) {
  247. if (!safety.is_safe && safety.must_escape) {
  248. var out = html.urlize(value + "", { escape: true });
  249. safety.is_safe = true;
  250. return out;
  251. }
  252. return html.urlize(value + "");
  253. },
  254. urlizetrunc: function (value, arg, safety) {
  255. if (!safety.is_safe && safety.must_escape) {
  256. var out = html.urlize(value + "", { escape: true, limit: arg });
  257. safety.is_safe = true;
  258. return out;
  259. }
  260. return html.urlize(value + "", { limit: arg });
  261. },
  262. wordcount: function (value, arg) {
  263. return (value + "").split(/\s+/g).length;
  264. },
  265. wordwrap: function (value, arg) {
  266. return string_utils.wordwrap(value + "", arg - 0);
  267. },
  268. yesno: function (value, arg) {
  269. var responses = (arg + "").split(/,/g);
  270. if (responses[2] && (value === undefined || value === null)) { return responses[2]; }
  271. return (value ? responses[0] : responses[1]) || '';
  272. }
  273. };
  274. var nodes = exports.nodes = {
  275. TextNode: function (text) {
  276. return function (context, callback) { callback(false, text); };
  277. },
  278. VariableNode: function (filterexpression) {
  279. return function (context, callback) {
  280. callback(false, filterexpression.resolve(context));
  281. };
  282. },
  283. ForNode: function (node_list, empty_list, itemname, listname, isReversed) {
  284. return function (context, callback) {
  285. var forloop = { parentloop: context.get('forloop') },
  286. list = context.get(listname),
  287. out = '';
  288. if (! list instanceof Array) { throw 'list not iterable' }
  289. if (isReversed) { list = list.slice(0).reverse(); }
  290. if (list.length === 0) {
  291. if (empty_list) {
  292. empty_list.evaluate(context, callback);
  293. } else {
  294. callback(false, '');
  295. }
  296. return;
  297. }
  298. context.push();
  299. context.set('forloop', forloop);
  300. function inner(p, c, idx, list, next) {
  301. import(forloop, {
  302. counter: idx + 1,
  303. counter0: idx,
  304. revcounter: list.length - idx,
  305. revcounter0: list.length - (idx + 1),
  306. first: idx === 0,
  307. last: idx === list.length - 1
  308. });
  309. context.set(itemname, c);
  310. node_list.evaluate( context, function (error, result) { next(error, p + result); });
  311. }
  312. iter.reduce(list, inner, '', function (error, result) {
  313. context.pop();
  314. callback(error, result);
  315. });
  316. };
  317. },
  318. IfNode: function (item_names, not_item_names, operator, if_node_list, else_node_list) {
  319. return function (context, callback) {
  320. function not(x) { return !x; }
  321. function and(p,c) { return p && c; }
  322. function or(p,c) { return p || c; }
  323. var items = item_names.map( context.get, context ).concat(
  324. not_item_names.map( context.get, context ).map( not )
  325. );
  326. var isTrue = items.reduce( operator === 'or' ? or : and, true );
  327. if (isTrue) {
  328. if_node_list.evaluate(context, function (error, result) { callback(error, result); });
  329. } else if (else_node_list) {
  330. else_node_list.evaluate(context, function (error, result) { callback(error, result); });
  331. } else {
  332. callback(false, '');
  333. }
  334. };
  335. },
  336. IfChangedNode: function (node_list, else_list, parts) {
  337. var last;
  338. return function (context, callback) {
  339. node_list.evaluate(context, function (error, result) {
  340. if (result !== last) {
  341. last = result;
  342. callback(error, result);
  343. } else if (!error && else_list) {
  344. else_list.evaluate(context, callback);
  345. } else {
  346. callback(error, '');
  347. }
  348. });
  349. };
  350. },
  351. IfEqualNode: function (node_list, else_list, first, second) {
  352. return function (context, callback) {
  353. if (context.get(first) == context.get(second)) {
  354. node_list.evaluate(context, callback);
  355. } else if (else_list) {
  356. else_list.evaluate(context, callback);
  357. } else {
  358. callback(false, '');
  359. }
  360. };
  361. },
  362. IfNotEqualNode: function (node_list, else_list, first, second) {
  363. return function (context, callback) {
  364. if (context.get(first) != context.get(second)) {
  365. node_list.evaluate(context, callback);
  366. } else if (else_list) {
  367. else_list.evaluate(context, callback);
  368. } else {
  369. callback(false, '');
  370. }
  371. };
  372. },
  373. CycleNode: function (items) {
  374. var cnt = 0;
  375. return function (context, callback) {
  376. var choices = items.map( context.get, context );
  377. var val = choices[cnt];
  378. cnt = (cnt + 1) % choices.length;
  379. callback(false, val);
  380. };
  381. },
  382. FilterNode: function (expression, node_list) {
  383. return function (context, callback) {
  384. node_list.evaluate( context, function (error, constant) {
  385. expression.constant = constant;
  386. callback(error, expression.resolve(context));
  387. });
  388. };
  389. },
  390. BlockNode: function (node_list, name) {
  391. /* upon execution each block stores it's nodelist in the context
  392. * indexed by the blocks name. As templates are executed from child to
  393. * parent, similar named blocks add their nodelist to an array of
  394. * nodelists (still indexed by the blocks name). When the root template
  395. * is reached, the blocks nodelists are executed one after each other
  396. * and the super variable is updated down through the hierachy.
  397. */
  398. return function (context, callback) {
  399. // init block list if it isn't already
  400. if (!context.blocks[name]) {
  401. context.blocks[name] = [];
  402. }
  403. // put this block in front of list
  404. context.blocks[name].unshift( node_list );
  405. // if this is a root template descend through templates and evaluate blocks for overrides
  406. if (!context.extends) {
  407. context.push();
  408. function inner(p, c, idx, block_list, next) {
  409. c.evaluate( context, function (error, result) {
  410. context.set('block', { super: result });
  411. next(error, result);
  412. });
  413. }
  414. iter.reduce( context.blocks[name], inner, '', function (error, result) {
  415. context.pop();
  416. callback(error, result);
  417. });
  418. } else {
  419. // else return empty string
  420. callback(false, '');
  421. }
  422. };
  423. },
  424. ExtendsNode: function (item) {
  425. return function (context, callback) {
  426. context.extends = context.get(item);
  427. callback(false, '');
  428. };
  429. },
  430. AutoescapeNode: function (node_list, enable) {
  431. if (enable.toLowerCase() === 'on') {
  432. enable = true;
  433. } else {
  434. enable = false;
  435. }
  436. return function (context, callback) {
  437. var before = context.autoescaping;
  438. context.autoescaping = enable;
  439. node_list.evaluate( context, function ( error, result ) {
  440. context.autoescaping = before;
  441. callback(error, result);
  442. });
  443. }
  444. },
  445. FirstOfNode: function (/*...*/) {
  446. var choices = Array.prototype.slice.apply(arguments);
  447. return function (context, callback) {
  448. var i, val, found;
  449. for (i = 0; i < choices.length; i++) {
  450. val = context.get(choices[i]);
  451. if (val) { found = true; break; }
  452. }
  453. callback(false, found ? val : '')
  454. };
  455. },
  456. WithNode: function (node_list, variable, name) {
  457. return function (context, callback) {
  458. var item = context.get(variable);
  459. context.push();
  460. context.set(name, item);
  461. node_list.evaluate( context, function (error, result) {
  462. context.pop();
  463. callback(error, result);
  464. });
  465. }
  466. },
  467. NowNode: function (format) {
  468. if (format.match(/^["']/)) {
  469. format = format.slice(1, -1);
  470. }
  471. return function (context, callback) {
  472. callback(false, date_utils.format_date(new Date(), format));
  473. };
  474. },
  475. IncludeNode: function (name) {
  476. return function (context, callback) {
  477. var loader = require('./loader');
  478. loader.load_and_render(context.get(name), context, callback);
  479. }
  480. },
  481. LoadNode: function (path, package) {
  482. return function (context, callback) {
  483. import(context.filters, package.filters);
  484. callback(false, '');
  485. }
  486. },
  487. TemplateTagNode: function (type) {
  488. return function (context, callback) {
  489. var bits = {
  490. openblock: '{%',
  491. closeblock: '%}',
  492. openvariable: '{{',
  493. closevariable: '}}',
  494. openbrace: '{',
  495. closebrace: '}',
  496. opencomment: '{#',
  497. closecomment: '#}'
  498. };
  499. if (!bits[type]) {
  500. callback('unknown bit');
  501. } else {
  502. callback(false, bits[type]);
  503. }
  504. }
  505. },
  506. SpacelessNode: function (node_list) {
  507. return function (context, callback) {
  508. node_list.evaluate(context, function (error, result) {
  509. callback(error, html.strip_spaces_between_tags(result + ""));
  510. });
  511. }
  512. },
  513. WithRatioNode: function (current, max, constant) {
  514. return function (context, callback) {
  515. current_val = context.get(current);
  516. max_val = context.get(max);
  517. constant_val = context.get(constant);
  518. callback(false, Math.round(current_val / max_val * constant_val) + "");
  519. }
  520. },
  521. RegroupNode: function (item, key, name) {
  522. return function (context, callback) {
  523. var list = context.get(item);
  524. if (!list instanceof Array) { callback(false, ''); }
  525. var dict = {};
  526. var grouped = list
  527. .map(function (x) { return x[key]; })
  528. .filter(function (x) { var val = dict[x]; dict[x] = x; return !val; })
  529. .map(function (grp) {
  530. return { grouper: grp, list: list.filter(function (o) { return o[key] === grp }) };
  531. });
  532. context.set(name, grouped);
  533. callback(false, '');
  534. }
  535. },
  536. UrlNode: function (url_name, replacements, item_name) {
  537. return function (context, callback) {
  538. var match = process.djangode_urls[context.get(url_name)]
  539. if (!match) { return callback('no matching urls for ' + url_name); }
  540. var url = string_utils.regex_to_string(match, replacements.map(function (x) { return context.get(x); }));
  541. if (url[0] !== '/') { url = '/' + url; }
  542. if (item_name) {
  543. context.set( item_name, url);
  544. callback(false, '');
  545. } else {
  546. callback(false, url);
  547. }
  548. }
  549. }
  550. };
  551. var tags = exports.tags = {
  552. 'text': function (parser, token) { return nodes.TextNode(token.contents); },
  553. 'variable': function (parser, token) {
  554. return nodes.VariableNode( parser.make_filterexpression(token.contents) );
  555. },
  556. 'comment': function (parser, token) {
  557. parser.parse('end' + token.type);
  558. parser.delete_first_token();
  559. return nodes.TextNode('');
  560. },
  561. 'for': function (parser, token) {
  562. var parts = get_args_from_token(token, { exclude: 2, mustbe: { 2: 'in', 4: 'reversed'} });
  563. var itemname = parts[0],
  564. listname = parts[1],
  565. isReversed = (parts[2] === 'reversed');
  566. var node_list = parser.parse('empty', 'end' + token.type);
  567. if (parser.next_token().type === 'empty') {
  568. var empty_list = parser.parse('end' + token.type);
  569. parser.delete_first_token();
  570. }
  571. return nodes.ForNode(node_list, empty_list, itemname, listname, isReversed);
  572. },
  573. 'if': function (parser, token) {
  574. var parts = token.split_contents();
  575. // get rid of if keyword
  576. parts.shift();
  577. var operator = '',
  578. item_names = [],
  579. not_item_names = [];
  580. var p, next_should_be_item = true;
  581. while (p = parts.shift()) {
  582. if (next_should_be_item) {
  583. if (p === 'not') {
  584. p = parts.shift();
  585. if (!p) { throw 'unexpected syntax in "if" tag. Expected item name after not'; }
  586. not_item_names.push( p );
  587. } else {
  588. item_names.push( p );
  589. }
  590. next_should_be_item = false;
  591. } else {
  592. if (p !== 'and' && p !== 'or') { throw 'unexpected syntax in "if" tag. Expected "and" or "or"'; }
  593. if (operator && p !== operator) { throw 'unexpected syntax in "if" tag. Cannot mix "and" and "or"'; }
  594. operator = p;
  595. next_should_be_item = true;
  596. }
  597. }
  598. var node_list, else_list;
  599. node_list = parser.parse('else', 'end' + token.type);
  600. if (parser.next_token().type === 'else') {
  601. else_list = parser.parse('end' + token.type);
  602. parser.delete_first_token();
  603. }
  604. return nodes.IfNode(item_names, not_item_names, operator, node_list, else_list);
  605. },
  606. 'ifchanged': function (parser, token) {
  607. var parts = get_args_from_token(token);
  608. var node_list, else_list;
  609. node_list = parser.parse('else', 'end' + token.type);
  610. if (parser.next_token().type === 'else') {
  611. else_list = parser.parse('end' + token.type);
  612. parser.delete_first_token();
  613. }
  614. return nodes.IfChangedNode(node_list, else_list, parts);
  615. },
  616. 'ifequal': function (parser, token) {
  617. var parts = get_args_from_token(token, { argcount: 2 });
  618. var node_list, else_list;
  619. node_list = parser.parse('else', 'end' + token.type);
  620. if (parser.next_token().type === 'else') {
  621. else_list = parser.parse('end' + token.type);
  622. parser.delete_first_token();
  623. }
  624. return nodes.IfEqualNode(node_list, else_list, parts[0], parts[1]);
  625. },
  626. 'ifnotequal': function (parser, token) {
  627. var parts = get_args_from_token(token, { argcount: 2 });
  628. var node_list, else_list;
  629. node_list = parser.parse('else', 'end' + token.type);
  630. if (parser.next_token().type === 'else') {
  631. else_list = parser.parse('end' + token.type);
  632. parser.delete_first_token();
  633. }
  634. return nodes.IfNotEqualNode(node_list, else_list, parts[0], parts[1]);
  635. },
  636. 'cycle': function (parser, token) {
  637. var parts = token.split_contents();
  638. var items = parts.slice(1);
  639. var as_idx = items.indexOf('as');
  640. var name = '';
  641. if (items.length === 1) {
  642. if (!parser.cycles || !parser.cycles[items[0]]) {
  643. throw 'no cycle named ' + items[0] + '!';
  644. } else {
  645. return parser.cycles[items[0]];
  646. }
  647. }
  648. if (as_idx > 0) {
  649. if (as_idx === items.length - 1) {
  650. throw 'unexpected syntax in "cycle" tag. Expected name after as';
  651. }
  652. name = items[items.length - 1];
  653. items = items.slice(0, items.length - 2);
  654. if (!parser.cycles) { parser.cycles = {}; }
  655. parser.cycles[name] = nodes.CycleNode(items);
  656. return parser.cycles[name];
  657. }
  658. return nodes.CycleNode(items);
  659. },
  660. 'filter': function (parser, token) {
  661. var parts = token.split_contents();
  662. if (parts.length > 2) { throw 'unexpected syntax in "filter" tag'; }
  663. var expr = parser.make_filterexpression('|' + parts[1]);
  664. var node_list = parser.parse('endfilter');
  665. parser.delete_first_token();
  666. return nodes.FilterNode(expr, node_list);
  667. },
  668. 'autoescape': function (parser, token) {
  669. var parts = get_args_from_token(token, { argcount: 1, mustbe: { 1: ['on', 'off'] }});
  670. var node_list = parser.parse('end' + token.type);
  671. parser.delete_first_token();
  672. return nodes.AutoescapeNode(node_list, parts[0]);
  673. },
  674. 'block': function (parser, token) {
  675. var parts = get_args_from_token(token, { argcount: 1 });
  676. var node_list = parser.parse('end' + token.type);
  677. parser.delete_first_token();
  678. return nodes.BlockNode(node_list, parts[0]);
  679. },
  680. 'extends': simple_tag(nodes.ExtendsNode, { argcount: 1 }),
  681. 'firstof': simple_tag(nodes.FirstOfNode),
  682. 'with': function (parser, token) {
  683. var parts = get_args_from_token(token, { argcount: 3, exclude: 2, mustbe: { 2: 'as' }});
  684. var node_list = parser.parse('end' + token.type);
  685. parser.delete_first_token();
  686. return nodes.WithNode(node_list, parts[0], parts[1], parts[2]);
  687. },
  688. 'now': simple_tag(nodes.NowNode, { argcount: 1 }),
  689. 'include': simple_tag(nodes.IncludeNode, { argcount: 1 }),
  690. 'load': function (parser, token) {
  691. var parts = get_args_from_token(token, { argcount: 1 });
  692. var name = parts[0];
  693. if (name[0] === '"' || name[0] === "'") {
  694. name = name.substr(1, name.length - 2);
  695. }
  696. var package = require(name);
  697. import(parser.tags, package.tags);
  698. return nodes.LoadNode(name, package);
  699. },
  700. 'templatetag': simple_tag(nodes.TemplateTagNode, { argcount: 1 }),
  701. 'spaceless': function (parser, token) {
  702. var parts = get_args_from_token(token, { argcount: 0 });
  703. var node_list = parser.parse('end' + token.type);
  704. parser.delete_first_token();
  705. return nodes.SpacelessNode(node_list);
  706. },
  707. 'widthratio': simple_tag(nodes.WithRatioNode, { argcount: 3 }),
  708. 'regroup': simple_tag(nodes.RegroupNode, { argcount: 5, mustbe: { 2: 'by', 4: 'as' }, exclude: [2, 4] }),
  709. 'url': function (parser, token) {
  710. var parts = token.split_contents();
  711. parts.shift();
  712. var url_name = parts.shift();
  713. if (parts[parts.length - 2] === 'as') {
  714. var item_name = parts.pop();
  715. parts.pop();
  716. }
  717. // TODO: handle qouted strings with commas in them correctly
  718. var replacements = parts.join('').split(/\s*,\s*/)
  719. return nodes.UrlNode(url_name, replacements, item_name);
  720. }
  721. };