Permalink
Browse files

First version of a django compatible template system. Parser and toke…

…nizer are somewhat usefull.

Text literals and variable tags (TODO: filters and escaping) and variable scoping are implemented.

Template tags implemented:
    For
    If

TODO:
 - parsing and executing is done outside of a scope that djangode can track, which means the server dies on errors instead of reporting 500.
 - implement filters and escaping of variables
 - implement more standard django tags
 - implement more standard django filters
  • Loading branch information...
1 parent 9218725 commit e66d765aa1e83963500732bb0f4df82e3ca372f1 Anders Hellerup Madsen committed Dec 9, 2009
Showing with 597 additions and 0 deletions.
  1. +150 −0 template_defaults.js
  2. +46 −0 template_example.js
  3. +227 −0 template_system.js
  4. +139 −0 template_system_test.js
  5. BIN templates/.template.html.swp
  6. +35 −0 templates/template.html
View
@@ -0,0 +1,150 @@
+var sys = require('sys');
+var template = require('./template_system');
+
+
+exports.callbacks = {
+ 'text': function (parser, token) { return TextNode(token.contents); },
+
+ 'variable': function (parser, token) {
+ // TODO: use split_token here
+ return VariableNode(token.contents[0], token.contents.slice(1));
+ },
+
+ 'for': function (parser, token) {
+
+ var parts = template.split_token(token.contents);
+
+ if (parts[0] !== 'for' || parts[2] !== 'in' || (parts[4] && parts[4] !== 'reversed')) {
+ throw 'unexpected syntax in "for" tag' + sys.inspect(parts);
+ }
+
+ var itemname = parts[1],
+ listname = parts[3],
+ isReversed = (parts[4] === 'reversed'),
+ node_list = parser.parse('endfor');
+
+ parser.delete_first_token();
+
+ return ForNode(itemname, listname, node_list, isReversed);
+ },
+
+ 'if': function (parser, token) {
+
+ var parts = template.split_token( token.contents );
+
+ if (parts[0] !== 'if') { throw 'unexpected syntax in "if" tag'; }
+
+ // get rid of if keyword
+ parts.shift();
+
+ var operator = '',
+ item_names = [],
+ not_item_names = [];
+
+ var p, next_should_be_item = true;
+
+ while (p = parts.shift()) {
+ if (next_should_be_item) {
+ if (p === 'not') {
+ p = parts.shift();
+ if (!p) { throw 'unexpected syntax in "if" tag. Expected item name after not'; }
+ not_item_names.push( p );
+ } else {
+ item_names.push( p );
+ }
+ next_should_be_item = false;
+ } else {
+ if (p !== 'and' && p !== 'or') { throw 'unexpected syntax in "if" tag. Expected "and" or "or"'; }
+ if (operator && p !== operator) { throw 'unexpected syntax in "if" tag. Cannot mix "and" and "or"'; }
+ operator = p;
+ expect_item = true;
+ }
+ }
+
+ var node_list, else_list = [];
+
+ node_list = parser.parse('else', 'endif');
+ if (parser.next_token().type === 'else') {
+ else_list = parser.parse('endif');
+ }
+
+ parser.delete_first_token();
+
+ return IfNode(item_names, not_item_names, operator, node_list, else_list);
+ }
+};
+
+function TextNode(text) {
+ return function () { return text; }
+}
+exports.TextNode = TextNode;
+
+
+function VariableNode(name, filters) {
+
+ // TODO: Filters
+ return function (context) { return context.get(name); }
+}
+exports.VariableNode = VariableNode;
+
+
+function ForNode(itemname, listname, node_list, isReversed) {
+
+ return function (context) {
+ var forloop = { parentloop: context.get('forloop') },
+ list = context.get(listname),
+ out = '';
+
+
+ if (! list instanceof Array) { return TextNode(''); }
+ if (isReversed) { list = list.slice(0).reverse(); }
+
+ context.push();
+ context.set('forloop', forloop);
+
+ list.forEach( function (o, idx, iter) {
+ process.mixin(forloop, {
+ counter: idx + 1,
+ counter0: idx,
+ revcounter: iter.length - idx,
+ revcounter0: iter.length - (idx + 1),
+ first: idx === 0,
+ last: idx === iter.length - 1,
+ });
+ context.set(itemname, o);
+
+ out += template.evaluate_node_list( node_list, context );
+ });
+
+ context.pop();
+
+ return out;
+ };
+}
+exports.ForNode = ForNode;
+
+function IfNode(item_names, not_item_names, operator, if_node_list, else_node_list) {
+
+ return function (context) {
+
+ function not(x) { return !x; }
+ function and(p,c) { return p && c; }
+ function or(p,c) { return p || c; }
+
+ var items = item_names.map( context.get, context ).concat(
+ not_item_names.map( context.get, context ).map( not )
+ );
+
+ var isTrue = items.reduce( operator === 'and' ? and : or, true );
+
+ if (isTrue) {
+ return template.evaluate_node_list( if_node_list, context );
+ } else if (else_node_list.length) {
+ return template.evaluate_node_list( else_node_list, context );
+ } else {
+ return '';
+ }
+ };
+}
+exports.IfNode = IfNode;
+
View
@@ -0,0 +1,46 @@
+var posix = require('posix'),
+ sys = require('sys'),
+ dj = require('./djangode'),
+ template = require('./template_system');
+
+var test_context = {
+ person_name: 'Thomas Hest',
+ company: 'Tobis A/S',
+ ship_date: '2. januar, 2010',
+ item: 'XXX',
+ item_list: [ 'Giraf', 'Fisk', 'Tapir'],
+ ordered_warranty: true,
+ ship: {
+ name: 'M/S Martha',
+ nationality: 'Danish',
+ }
+};
+
+var app = dj.makeApp([
+ ['^/raw$', function (req, res) {
+ posix.cat("templates/template.html").addCallback( function (content) {
+ dj.respond(res, content, 'text/plain');
+ });
+ }],
+ ['^/tokens$', function (req, res) {
+ posix.cat("templates/template.html").addCallback( function (content) {
+ var t = template.tokenize(content);
+ dj.respond(res, sys.inspect(t), 'text/plain');
+ });
+ }],
+ ['^/parsed$', function (req, res) {
+ posix.cat("templates/template.html").addCallback( function (content) {
+ var t = template.parse(content);
+ dj.respond(res, sys.inspect(t), 'text/plain');
+ });
+ }],
+ ['^/rendered$', function (req, res) {
+ posix.cat("templates/template.html").addCallback( function (content) {
+ var t = template.parse(content);
+ dj.respond(res, t.render(test_context), 'text/plain');
+ });
+ }],
+]);
+
+dj.serve(app, 8009);
+
Oops, something went wrong.

0 comments on commit e66d765

Please sign in to comment.