Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

cleaned up templatesystem + implemented load, include and a few more …

…tags

- fixed require paths
- moved required statements around to prevent double loading in some modules (loader)
- organized tags, filter and nodes better so they can be changed with load tag
- general cleanup
- added load_and_render() function to loader
  • Loading branch information...
commit abad5beb35285e23e01038f310d7de3036952581 1 parent fd449eb
Anders Hellerup Madsen authored
41 template/loader.js
View
@@ -3,11 +3,22 @@
var sys = require('sys');
var fs = require('fs');
+var template_system = require('./template');
var cache = {};
var template_path = '/tmp';
-function load(name, parse_function, callback) {
+
+// TODO: get_template
+ // should support subdirectories
+
+/*
+ template_loader.load_and_render('template.html', test_context, function(rendered) {
+ dj.respond(res, rendered);
+ });
+*/
+
+var load = exports.load = function (name, callback) {
if (!callback) { throw 'loader.load() must be called with a callback'; }
if (cache[name] != undefined) {
@@ -15,21 +26,27 @@ function load(name, parse_function, callback) {
} else {
fs.readFile(template_path + '/' + name, function (error, s) {
if (error) { callback(error); }
- cache[name] = parse_function(s);
+ cache[name] = template_system.parse(s);
callback(false, cache[name]);
});
}
-}
-
-function flush() {
+};
+
+exports.load_and_render = function (name, context, callback) {
+ load(name, function (error, template) {
+ if (error) {
+ callback(error);
+ } else {
+ template.render(context, callback);
+ }
+ });
+};
+
+exports.flush = function () {
cache = {};
-}
+};
-function set_path(path) {
+exports.set_path = function (path) {
template_path = path;
-}
-
-exports.load = load;
-exports.set_path = set_path;
-exports.flush = flush;
+};
37 template/template.js
View
@@ -2,12 +2,9 @@
/*global require, process, exports */
var sys = require('sys');
-var utils = require('utils/utils');
-var template_defaults = require('template/template_defaults');
-var template_loader = require('template/loader');
-
-exports.loader = template_loader;
-exports.load = function (name, callback) { template_loader.load(name, exports.parse, callback); };
+var string_utils = require('../utils/string');
+var html = require('../utils/html');
+var iter = require('../utils/iter');
function normalize(value) {
if (typeof value !== 'string') { return value; }
@@ -31,7 +28,7 @@ function Token(type, contents) {
process.mixin(Token.prototype, {
split_contents: function () {
- return utils.string.smart_split(this.contents);
+ return string_utils.smart_split(this.contents);
}
});
@@ -179,7 +176,7 @@ process.mixin(FilterExpression.prototype, {
var out = this.filter_list.reduce( function (p,c) {
- var filter = template_defaults.filters[c.name];
+ var filter = context.filters[c.name];
var arg;
if (c.arg) {
@@ -199,9 +196,9 @@ process.mixin(FilterExpression.prototype, {
if (safety.must_escape && !safety.is_safe) {
if (typeof out === 'string') {
- return utils.html.escape(out);
+ return html.escape(out);
} else if (out instanceof Array) {
- return out.map( function (o) { return typeof o === 'string' ? utils.html.escape(o) : o; } );
+ return out.map( function (o) { return typeof o === 'string' ? html.escape(o) : o; } );
}
}
return out;
@@ -214,6 +211,10 @@ function Parser(input) {
this.token_list = tokenize(input);
this.indent = 0;
this.blocks = {};
+
+ var defaults = require('./template_defaults');
+ this.tags = defaults.tags;
+ this.nodes = defaults.nodes;
}
function parser_error(e) {
@@ -223,7 +224,7 @@ function parser_error(e) {
function make_nodelist() {
var node_list = [];
node_list.evaluate = function (context, callback) {
- utils.iter.reduce(this, function (p, c, idx, list, next) {
+ iter.reduce(this, function (p, c, idx, list, next) {
c(context, function (error, result) { next(error, p + result); });
}, '', callback);
};
@@ -240,8 +241,6 @@ function make_nodelist() {
process.mixin(Parser.prototype, {
- tags: template_defaults.tags,
-
parse: function () {
var stoppers = Array.prototype.slice.apply(arguments);
@@ -267,7 +266,7 @@ process.mixin(Parser.prototype, {
} else {
//throw parser_error('Unknown tag: ' + token[0]);
node_list.append(
- template_defaults.nodes.TextNode('[[ UNKNOWN ' + token.type + ' ]]'),
+ this.nodes.TextNode('[[ UNKNOWN ' + token.type + ' ]]'),
'UNKNOWN'
);
}
@@ -302,6 +301,7 @@ function Context(o) {
this.extends = '';
this.blocks = {};
this.autoescaping = true;
+ this.filters = require('./template_defaults').filters;
}
process.mixin(Context.prototype, {
@@ -347,7 +347,7 @@ process.mixin(Context.prototype, {
},
pop: function () {
return this.scope.shift();
- }
+ },
});
@@ -370,9 +370,8 @@ process.mixin(Template.prototype, {
if (error) { callback(error); }
if (context.extends) {
- exports.load(context.extends, function (error, parent_template) {
- parent_template.render(context, callback);
- });
+ var template_loader = require('./loader');
+ template_loader.load_and_render(context.extends, context, callback);
} else {
callback(false, rendered);
}
@@ -383,8 +382,6 @@ process.mixin(Template.prototype, {
/********************************************************/
exports.parse = function (input) {
- //var parser = new Parser(input);
- // TODO: Better error handling, this is lame
return new Template(input);
};
4 template/template.test.js
View
@@ -1,6 +1,6 @@
var sys = require('sys');
-process.mixin(GLOBAL, require('utils/test').dsl);
-process.mixin(GLOBAL, require('template/template'));
+process.mixin(GLOBAL, require('../utils/test').dsl);
+process.mixin(GLOBAL, require('./template'));
testcase('Test tokenizer');
test('sanity test', function () {
313 template/template_defaults.js
View
@@ -2,7 +2,12 @@
/*global require, process, exports, escape */
var sys = require('sys');
-var utils = require('utils/utils');
+var string_utils = require('../utils/string');
+var date_utils = require('../utils/date');
+var html = require('../utils/html');
+var iter = require('../utils/iter');
+
+process.mixin(GLOBAL, require('../utils/tags'));
/* TODO: Missing filters
@@ -16,22 +21,19 @@ NOTE:
stringformat() filter is regular sprintf compliant and doesn't have real python syntax
Missing tags:
- for ( missing 'empty' tag )
-
- include
- ssi
- load
+ ssi (will require ALLOWED_INCLUDE_ROOTS somehow)
+
debug
regroup
- spaceless
- templatetag
- url
widthratio
+ url
+
NOTE:
cycle tag does not support legacy syntax (row1,row2,row3)
+ load takes a path - like require. Loaded module must expose tags and filters objects.
*/
var filters = exports.filters = {
@@ -40,13 +42,13 @@ var filters = exports.filters = {
arg = arg - 0;
return (isNaN(value) || isNaN(arg)) ? '' : (value + arg);
},
- addslashes: function (value, arg) { return utils.string.add_slashes("" + value); },
- capfirst: function (value, arg) { return utils.string.cap_first("" + value); },
- center: function (value, arg) { return utils.string.center("" + value, arg - 0); },
+ addslashes: function (value, arg) { return string_utils.add_slashes("" + value); },
+ capfirst: function (value, arg) { return string_utils.cap_first("" + value); },
+ center: function (value, arg) { return string_utils.center("" + value, arg - 0); },
cut: function (value, arg) { return ("" + value).replace(new RegExp(arg, 'g'), ""); },
date: function (value, arg) {
// TODO: this filter may be unsafe...
- return (value instanceof Date) ? utils.date.format_date(value, arg) : '';
+ return (value instanceof Date) ? date_utils.format_date(value, arg) : '';
},
'default': function (value, arg) {
// TODO: this filter may be unsafe...
@@ -105,7 +107,7 @@ var filters = exports.filters = {
},
force_escape: function (value, arg, safety) {
safety.is_safe = true;
- return utils.html.escape("" + value);
+ return html.escape("" + value);
},
get_digit: function (value, arg) {
if (typeof value !== 'number' || typeof arg !== 'number' || arg < 1) { return value; }
@@ -124,12 +126,12 @@ var filters = exports.filters = {
length: function (value, arg) { return value.length ? value.length : 0; },
length_is: function (value, arg) { return value.length === arg; },
linebreaks: function (value, arg, safety) {
- var out = utils.html.linebreaks("" + value, { escape: !safety.is_safe && safety.must_escape });
+ var out = html.linebreaks("" + value, { escape: !safety.is_safe && safety.must_escape });
safety.is_safe = true;
return out;
},
linebreaksbr: function (value, arg, safety) {
- var out = utils.html.linebreaks("" + value, { onlybr: true, escape: !safety.is_safe && safety.must_escape });
+ var out = html.linebreaks("" + value, { onlybr: true, escape: !safety.is_safe && safety.must_escape });
safety.is_safe = true;
return out;
},
@@ -140,9 +142,9 @@ var filters = exports.filters = {
var out = lines
.map(function (s, idx) {
if (!safety.is_safe && safety.must_escape) {
- s = utils.html.escape("" + s);
+ s = html.escape("" + s);
}
- return utils.string.sprintf('%0' + len + 'd. %s', idx + 1, s); })
+ return string_utils.sprintf('%0' + len + 'd. %s', idx + 1, s); })
.join('\n');
safety.is_safe = true;
return out;
@@ -150,7 +152,7 @@ var filters = exports.filters = {
ljust: function (value, arg) {
arg = arg - 0;
try {
- return utils.string.sprintf('%-' + arg + 's', value).substr(0, arg);
+ return string_utils.sprintf('%-' + arg + 's', value).substr(0, arg);
} catch (e) {
return '';
}
@@ -185,7 +187,7 @@ var filters = exports.filters = {
},
rjust: function (value, arg) {
try {
- return utils.string.sprintf('%' + arg + 's', value).substr(0, arg);
+ return string_utils.sprintf('%' + arg + 's', value).substr(0, arg);
} catch (e) {
return '';
}
@@ -222,39 +224,39 @@ var filters = exports.filters = {
},
stringformat: function (value, arg) {
// TODO: this filter may not be safe
- try { return utils.string.sprintf('%' + arg, value); } catch (e) { return ''; }
+ try { return string_utils.sprintf('%' + arg, value); } catch (e) { return ''; }
},
striptags: function (value, arg, safety) {
safety.is_safe = true;
return String(value).replace(/<(.|\n)*?>/g, '');
},
title: function (value, arg) {
- return utils.string.titleCaps( String(value) );
+ return string_utils.titleCaps( String(value) );
},
time: function (value, arg) {
// TODO: this filter may not be safe
- return (value instanceof Date) ? utils.date.format_time(value, arg) : '';
+ return (value instanceof Date) ? date_utils.format_time(value, arg) : '';
},
timesince: function (value, arg) {
// TODO: this filter may not be safe (if people decides to put & or " in formatstrings"
value = new Date(value);
arg = new Date(arg);
if (isNaN(value) || isNaN(arg)) { return ''; }
- return utils.date.timesince(value, arg);
+ return date_utils.timesince(value, arg);
},
timeuntil: function (value, arg) {
// TODO: this filter may not be safe (if people decides to put & or " in formatstrings"
value = new Date(value);
arg = new Date(arg);
if (isNaN(value) || isNaN(arg)) { return ''; }
- return utils.date.timeuntil(value, arg);
+ return date_utils.timeuntil(value, arg);
},
truncatewords: function (value, arg) {
return String(value).split(/\s+/g).slice(0, arg).join(' ') + ' ...';
},
truncatewords_html: function (value, arg, safety) {
safety.is_safe = true;
- return utils.html.truncate_html_words(value, arg - 0);
+ return html.truncate_html_words(value, arg - 0);
},
upper: function (value, arg) {
return (value + '').toUpperCase();
@@ -264,25 +266,25 @@ var filters = exports.filters = {
},
urlize: function (value, arg, safety) {
if (!safety.is_safe && safety.must_escape) {
- var out = utils.html.urlize(value + "", { escape: true });
+ var out = html.urlize(value + "", { escape: true });
safety.is_safe = true;
return out;
}
- return utils.html.urlize(value + "");
+ return html.urlize(value + "");
},
urlizetrunc: function (value, arg, safety) {
if (!safety.is_safe && safety.must_escape) {
- var out = utils.html.urlize(value + "", { escape: true, limit: arg });
+ var out = html.urlize(value + "", { escape: true, limit: arg });
safety.is_safe = true;
return out;
}
- return utils.html.urlize(value + "", { limit: arg });
+ return html.urlize(value + "", { limit: arg });
},
wordcount: function (value, arg) {
return (value + "").split(/\s+/g).length;
},
wordwrap: function (value, arg) {
- return utils.wordwrap(value + "", arg - 0);
+ return string_utils.wordwrap(value + "", arg - 0);
},
yesno: function (value, arg) {
var responses = (arg + "").split(/,/g);
@@ -305,16 +307,25 @@ var nodes = exports.nodes = {
};
},
- ForNode: function (itemname, listname, node_list, isReversed) {
+ ForNode: function (node_list, empty_list, itemname, listname, isReversed) {
return function (context, callback) {
var forloop = { parentloop: context.get('forloop') },
list = context.get(listname),
out = '';
- if (! list instanceof Array) { return nodes.TextNode(''); }
+ if (! list instanceof Array) { throw 'list not iterable' }
if (isReversed) { list = list.slice(0).reverse(); }
+ if (list.length === 0) {
+ if (empty_list) {
+ empty_list.evaluate(context, callback);
+ } else {
+ callback(false, '');
+ }
+ return;
+ }
+
context.push();
context.set('forloop', forloop);
@@ -332,7 +343,7 @@ var nodes = exports.nodes = {
node_list.evaluate( context, function (error, result) { next(error, p + result); });
}
- utils.iter.reduce(list, inner, '', function (error, result) {
+ iter.reduce(list, inner, '', function (error, result) {
context.pop();
callback(error, result);
});
@@ -355,7 +366,7 @@ var nodes = exports.nodes = {
if (isTrue) {
if_node_list.evaluate(context, function (error, result) { callback(error, result); });
- } else if (else_node_list.length) {
+ } else if (else_node_list) {
else_node_list.evaluate(context, function (error, result) { callback(error, result); });
} else {
callback(false, '');
@@ -363,7 +374,7 @@ var nodes = exports.nodes = {
};
},
- IfChangedNode: function (node_list) {
+ IfChangedNode: function (node_list, else_list, parts) {
var last;
return function (context, callback) {
@@ -371,6 +382,8 @@ var nodes = exports.nodes = {
if (result !== last) {
last = result;
callback(error, result);
+ } else if (!error && else_list) {
+ else_list.evaluate(context, callback);
} else {
callback(error, '');
}
@@ -378,20 +391,24 @@ var nodes = exports.nodes = {
};
},
- IfEqualNode: function (node_list, first, second) {
+ IfEqualNode: function (node_list, else_list, first, second) {
return function (context, callback) {
if (context.get(first) == context.get(second)) {
node_list.evaluate(context, callback);
+ } else if (else_list) {
+ else_list.evaluate(context, callback);
} else {
callback(false, '');
}
};
},
- IfNotEqualNode: function (node_list, first, second) {
+ IfNotEqualNode: function (node_list, else_list, first, second) {
return function (context, callback) {
if (context.get(first) != context.get(second)) {
node_list.evaluate(context, callback);
+ } else if (else_list) {
+ else_list.evaluate(context, callback);
} else {
callback(false, '');
}
@@ -451,7 +468,7 @@ var nodes = exports.nodes = {
next(error, result);
});
}
- utils.iter.reduce( context.blocks[name], inner, '', function (error, result) {
+ iter.reduce( context.blocks[name], inner, '', function (error, result) {
context.pop();
callback(error, result);
});
@@ -519,71 +536,63 @@ var nodes = exports.nodes = {
format = format.slice(1, -1);
}
return function (context, callback) {
- callback(false, utils.date.format_date(new Date(), format));
+ callback(false, date_utils.format_date(new Date(), format));
};
- }
-
-};
-
-
-function assert_args_in_token(token, options) {
- options = options || {};
+ },
- var parts = token.split_contents();
+ IncludeNode: function (name) {
+ return function (context, callback) {
+ var loader = require('./loader');
+ loader.load_and_render(context.get(name), context, callback);
+ }
+ },
- if (options.argcount !== undefined && parts.length !== options.argcount + 1) {
- throw 'unexpected syntax in "' + token.type + '" tag: Wrong number of arguments';
- }
+ LoadNode: function (path, package) {
+ return function (context, callback) {
+ process.mixin(context.filters, package.filters);
+ callback(false, '');
+ }
+ },
- var i;
- for (i = 1; i < parts.length; i++) {
- if (options[i + 'mustbe']) {
- var expected = options[i + 'mustbe'];
- if (expected instanceof Array) {
- if (expected.indexOf(parts[i]) === -1) {
- throw 'unexpected syntax in "' + token.type + '" tag: Expected one of "' + expected.join('", "') + '"';
- }
- } else if (expected != parts[i]) {
- throw 'unexpected syntax in "' + token.type + '" tag: Expected "' + options[i + 'mustbe'] + '"';
+ TemplateTagNode: function (type) {
+ return function (context, callback) {
+ var bits = {
+ openblock: '{%',
+ closeblock: '%}',
+ openvariable: '{{',
+ closevariable: '}}',
+ openbrace: '{',
+ closebrace: '}',
+ opencomment: '{#',
+ closecomment: '#}'
+ };
+ if (!bits[type]) {
+ callback('unknown bit');
+ } else {
+ callback(false, bits[type]);
}
}
- }
+ },
- if (options.exclude) {
- if (!(options.exclude instanceof Array)) { options.exclude = [options.exclude] }
- var include = [];
- for (i = 1; i < parts.length; i++) {
- if (options.exclude.indexOf(i) === -1) { include.push(i); }
+ SpacelessNode: function (node_list) {
+ return function (context, callback) {
+ node_list.evaluate(context, function (error, result) {
+ callback(error, html.strip_spaces_between_tags(result + ""));
+ });
}
- parts = include.map(function (x) { return parts[x]; });
- } else {
- parts = parts.slice(1);
- }
-
- return parts;
-}
-
-
-function simple_tag(node, options) {
-
- return function (parser, token) {
- var parts = assert_args_in_token(token, options);
- return node.apply(null, parts);
- };
-
-}
-
-function inclusion_tag(node, options) {
- return function (parser, token) {
+ },
- var parts = assert_args_in_token(token, options);
+ WithRatioNode: function (current, max, constant) {
+ return function (context, callback) {
+ current_val = context.get(current);
+ max_val = context.get(max);
+ constant_val = context.get(constant);
- var node_list = parser.parse('end' + token.type);
- parser.delete_first_token();
+ callback(false, Math.round(current_val / max_val * constant_val) + "");
+ }
+ }
- return node.apply(null, [node_list].concat(parts));
- };
-}
+};
var tags = exports.tags = {
'text': function (parser, token) { return nodes.TextNode(token.contents); },
@@ -600,20 +609,19 @@ var tags = exports.tags = {
'for': function (parser, token) {
- var parts = token.split_contents();
+ var parts = get_args_from_token(token, { exclude: 2, mustbe: { 2: 'in', 4: 'reversed'} });
- if (parts[2] !== 'in' || (parts[4] && parts[4] !== 'reversed')) {
- throw 'unexpected syntax in "for" tag: ' + token.contents;
- }
-
- var itemname = parts[1],
- listname = parts[3],
- isReversed = (parts[4] === 'reversed'),
- node_list = parser.parse('endfor');
+ var itemname = parts[0],
+ listname = parts[1],
+ isReversed = (parts[2] === 'reversed');
- parser.delete_first_token();
+ var node_list = parser.parse('empty', 'end' + token.type);
+ if (parser.next_token().type === 'empty') {
+ var empty_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ }
- return nodes.ForNode(itemname, listname, node_list, isReversed);
+ return nodes.ForNode(node_list, empty_list, itemname, listname, isReversed);
},
'if': function (parser, token) {
@@ -647,25 +655,58 @@ var tags = exports.tags = {
}
}
- var node_list, else_list = [];
+ var node_list, else_list;
- node_list = parser.parse('else', 'endif');
+ node_list = parser.parse('else', 'end' + token.type);
if (parser.next_token().type === 'else') {
- else_list = parser.parse('endif');
+ else_list = parser.parse('end' + token.type);
parser.delete_first_token();
}
return nodes.IfNode(item_names, not_item_names, operator, node_list, else_list);
},
- // TODO: else
- 'ifchanged': inclusion_tag(nodes.IfChangedNode, { argcount: 0 }),
+ 'ifchanged': function (parser, token) {
+ var parts = get_args_from_token(token);
- // TODO: else
- 'ifequal': inclusion_tag(nodes.IfEqualNode, { argcount: 2 }),
+ var node_list, else_list;
+
+ node_list = parser.parse('else', 'end' + token.type);
+ if (parser.next_token().type === 'else') {
+ else_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ }
+
+ return nodes.IfChangedNode(node_list, else_list, parts);
+ },
- // TODO: else
- 'ifnotequal': inclusion_tag(nodes.IfNotEqualNode, { argcount: 2 }),
+ 'ifequal': function (parser, token) {
+ var parts = get_args_from_token(token, { argcount: 2 });
+
+ var node_list, else_list;
+
+ node_list = parser.parse('else', 'end' + token.type);
+ if (parser.next_token().type === 'else') {
+ else_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ }
+
+ return nodes.IfEqualNode(node_list, else_list, parts[0], parts[1]);
+ },
+
+ 'ifnotequal': function (parser, token) {
+ var parts = get_args_from_token(token, { argcount: 2 });
+
+ var node_list, else_list;
+
+ node_list = parser.parse('else', 'end' + token.type);
+ if (parser.next_token().type === 'else') {
+ else_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ }
+
+ return nodes.IfNotEqualNode(node_list, else_list, parts[0], parts[1]);
+ },
'cycle': function (parser, token) {
var parts = token.split_contents();
@@ -710,16 +751,52 @@ var tags = exports.tags = {
return nodes.FilterNode(expr, node_list);
},
- 'autoescape': inclusion_tag(nodes.AutoescapeNode, { argcount: 1, '1mustbe': ['on', 'off'] }),
-
- 'block': inclusion_tag(nodes.BlockNode, { argcount: 1 }),
+ 'autoescape': function (parser, token) {
+ var parts = get_args_from_token(token, { argcount: 1, mustbe: { 1: ['on', 'off'] }});
+ var node_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ return nodes.AutoescapeNode(node_list, parts[0]);
+ },
+
+ 'block': function (parser, token) {
+ var parts = get_args_from_token(token, { argcount: 1 });
+ var node_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ return nodes.BlockNode(node_list, parts[0]);
+ },
'extends': simple_tag(nodes.ExtendsNode, { argcount: 1 }),
'firstof': simple_tag(nodes.FirstOfNode),
- 'with': inclusion_tag(nodes.WithNode, { argcount: 3, exclude: 2, '2mustbe': 'as' }),
+ 'with': function (parser, token) {
+ var parts = get_args_from_token(token, { argcount: 3, exclude: 2, mustbe: { 2: 'as' }});
+ var node_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ return nodes.WithNode(node_list, parts[0], parts[1], parts[2]);
+ },
+
+ 'now': simple_tag(nodes.NowNode, { argcount: 1 }),
+ 'include': simple_tag(nodes.IncludeNode, { argcount: 1 }),
+ 'load': function (parser, token) {
+ var parts = get_args_from_token(token, { argcount: 1 });
+ var name = parts[0];
+ if (name[0] === '"' || name[0] === "'") {
+ name = name.substr(1, name.length - 2);
+ }
- 'now': simple_tag(nodes.NowNode, { argcount: 1 })
+ var package = require(name);
+ process.mixin(parser.tags, package.tags);
+
+ return nodes.LoadNode(name, package);
+ },
+ 'templatetag': simple_tag(nodes.TemplateTagNode, { argcount: 1 }),
+ 'spaceless': function (parser, token) {
+ var parts = get_args_from_token(token, { argcount: 0 });
+ var node_list = parser.parse('end' + token.type);
+ parser.delete_first_token();
+ return nodes.SpacelessNode(node_list);
+ },
+ 'widthratio': simple_tag(nodes.WithRatioNode, { argcount: 3 }),
};
71 template/template_defaults.tags.test.js
View
@@ -1,8 +1,9 @@
var sys = require('sys');
var fs = require('fs');
-var template = require('template/template');
-process.mixin(GLOBAL, require('utils/test').dsl);
-process.mixin(GLOBAL, require('template/template_defaults'));
+var template = require('./template');
+
+process.mixin(GLOBAL, require('../utils/test').dsl);
+process.mixin(GLOBAL, require('./template_defaults'));
function write_file(path, content) {
var file = fs.openSync(path, process.O_WRONLY | process.O_TRUNC | process.O_CREAT, 0666);
@@ -29,8 +30,10 @@ function make_parse_and_execute_test(expected, tpl, name) {
}
testcase('fornode')
- setup( function () { return { obj: { items: [ 1,2,3,4 ] } }; });
+ setup( function () { return { obj: { items: [ 1,2,3,4 ], noitems: [] } }; });
make_parse_and_execute_test(' 1 2 3 4 ', '{% for item in items %} {{ item }} {% endfor %}');
+ make_parse_and_execute_test('hest',
+ '{% for item in notitems %} {{ item }} {% empty %}hest{% endfor %}');
testcase('variable')
setup( function () {
@@ -56,6 +59,7 @@ testcase('variable')
make_parse_and_execute_test('1', '{{ obj.a }}');
make_parse_and_execute_test('2', '{{ obj.b }}');
make_parse_and_execute_test('laks', '{{ obj.c.e.f }}');
+ make_parse_and_execute_test('', '{{ nonexisting }}');
make_parse_and_execute_test('&qout;hest&qout;', '{{ qstr }}');
make_parse_and_execute_test('HEST', '{{ "hest"|upper }}');
make_parse_and_execute_test('16', '{{ 10|add:"6" }}');
@@ -111,8 +115,9 @@ testcase('block and extend')
+ '{% block test2 %} Et cirkus{{ block.super }}{% endblock %}'
);
- template.loader.flush();
- template.loader.set_path('/tmp');
+ var template_loader = require('./loader');
+ template_loader.flush();
+ template_loader.set_path('/tmp');
return { obj: { parent: 'block_test_2.html' } };
})
@@ -184,6 +189,9 @@ testcase('ifchanged')
make_parse_and_execute_test('hestgirafhestgiraf',
'{% for item in list %}{% ifchanged %}{{ item }}{% endifchanged %}{%endfor%}'
);
+ make_parse_and_execute_test('hestgiraf::hestgiraf',
+ '{% for item in list %}{% ifchanged %}{{ item }}{% else %}::{% endifchanged %}{%endfor%}'
+ );
testcase('ifequal')
setup(function () { return {obj:{item: 'hest', other: 'hest', fish: 'laks' } }; });
@@ -192,6 +200,7 @@ testcase('ifequal')
make_parse_and_execute_test('giraf', '{% ifequal item "hest" %}giraf{%endifequal %}');
make_parse_and_execute_test('giraf', '{% ifequal item other %}giraf{%endifequal %}');
make_parse_and_execute_test('', '{% ifequal item fish %}giraf{%endifequal %}');
+ make_parse_and_execute_test('tapir', '{% ifequal item fish %}giraf{% else %}tapir{%endifequal %}');
testcase('ifnotequal')
setup(function () { return {obj:{item: 'hest', other: 'hest', fish: 'laks' } }; });
@@ -200,6 +209,7 @@ testcase('ifnotequal')
make_parse_and_execute_test('laks', '{% ifnotequal item "giraf" %}laks{%endifnotequal %}');
make_parse_and_execute_test('laks', '{% ifnotequal item fish %}laks{%endifnotequal %}');
make_parse_and_execute_test('', '{% ifnotequal item other %}laks{%endifnotequal %}');
+ make_parse_and_execute_test('hest', '{% ifnotequal item other %}laks{% else %}hest{% endifnotequal %}');
testcase('now')
test_async('should work as expected', function (testcontext, complete) {
@@ -218,5 +228,52 @@ testcase('now')
});
});
-run(true);
+testcase('include')
+ setup(function () {
+ write_file('/tmp/include_test.html', 'her er en hest{{ item }}.');
+
+ var template_loader = require('./loader');
+ template_loader.flush();
+ template_loader.set_path('/tmp');
+
+ return { obj: { name: 'include_test.html', item: 'giraf' } };
+ })
+
+ make_parse_and_execute_test('her er en hestgiraf.', '{% include "include_test.html" %}');
+ make_parse_and_execute_test('her er en hestgiraf.', '{% include name %}');
+
+testcase('load')
+
+ exports.filters = { testfilter: function () { return 'hestgiraf'; } }
+ exports.tags = {
+ testtag: function () {
+ return function (context, callback) {
+ callback('', 'hestgiraf')
+ };
+ }
+ };
+
+ make_parse_and_execute_test('hestgiraf', '{% load ./template_defaults.tags.test %}{{ 100|testfilter }}');
+ make_parse_and_execute_test('hestgiraf', '{% load "./template_defaults.tags.test" %}{{ 100|testfilter }}');
+ make_parse_and_execute_test('hestgiraf', '{% load ./template_defaults.tags.test %}{% testtag %}');
+
+testcase('templatetag')
+ make_parse_and_execute_test('{%', '{% templatetag openblock %}');
+ make_parse_and_execute_test('%}', '{% templatetag closeblock %}');
+ make_parse_and_execute_test('{{', '{% templatetag openvariable %}');
+ make_parse_and_execute_test('}}', '{% templatetag closevariable %}');
+ make_parse_and_execute_test('{', '{% templatetag openbrace %}');
+ make_parse_and_execute_test('}', '{% templatetag closebrace %}');
+ make_parse_and_execute_test('{#', '{% templatetag opencomment %}');
+ make_parse_and_execute_test('#}', '{% templatetag closecomment %}');
+
+testcase('spaceless')
+ make_parse_and_execute_test('<p><a href="foo/">Foo</a></p>',
+ '{% spaceless %}<p>\n <a href="foo/">Foo</a>\n </p>{% endspaceless %}');
+
+testcase('widthratio')
+ setup(function () { return {obj:{this_value: 175, max_value: 200 } }; });
+ make_parse_and_execute_test('88', '{% widthratio this_value max_value 100 %}');
+
+run();
4 template/template_defaults.test.js
View
@@ -1,6 +1,6 @@
var sys = require('sys');
-process.mixin(GLOBAL, require('utils/test').dsl);
-process.mixin(GLOBAL, require('template/template_defaults'));
+process.mixin(GLOBAL, require('../utils/test').dsl);
+process.mixin(GLOBAL, require('./template_defaults'));
testcase('add')
test('should add correctly', function () {
43 template_example.js
View
@@ -1,9 +1,10 @@
var sys = require('sys'),
- dj = require('djangode'),
- template_system = require('template/template');
+ dj = require('./djangode'),
+ template_system = require('./template/template');
+ template_loader = require('./template/loader');
// set template path
-template_system.loader.set_path('template-demo');
+template_loader.set_path('template-demo');
// context to use when rendering template. In a real app this would likely come from a database
var test_context = {
@@ -22,27 +23,39 @@ var test_context = {
// make app
var app = dj.makeApp([
- ['^/(template-demo/.*)$', dj.serveFile],
+ ['^/$', function(req, res) {
+ dj.respond(res, '<h1>djangode template demo</h1> \
+ <ul> \
+ <li><a href="/template">The raw template</a></li> \
+ <li><a href="/context">The test context</a></li> \
+ <li><a href="/text">The template rendered as text</a></li> \
+ <li><a href="/html">The template rendered as html</a></li> \
+ </ul> \
+ ');
+ }],
['^/template$', function (req, res) {
dj.serveFile(req, res, 'template-demo/template.html');
}],
+ ['^/context$', function (req, res) {
+ dj.respond(res, sys.inspect(test_context), 'text/plain');
+ }],
+
['^/text$', function (req, res) {
- template_system.load('template.html', function (error, template) {
- template.render(test_context, function (error, html) {
- dj.respond(res, html, 'text/plain');
- });
- })
+ template_loader.load_and_render('template.html', test_context, function (error, result) {
+ dj.respond(res, result, 'text/plain');
+ });
}],
['^/html$', function (req, res) {
- template_system.load('template.html', function (error, template) {
- template.render(test_context, function (error, html) {
- dj.respond(res, html, 'text/html');
- });
- })
- }]
+ template_loader.load_and_render('template.html', test_context, function (error, result) {
+ dj.respond(res, result, 'text/html');
+ });
+ }],
+
+ ['^/(template-demo/.*)$', dj.serveFile],
+
]);
dj.serve(app, 8009);
4 utils/date.test.js
View
@@ -1,5 +1,5 @@
-process.mixin(GLOBAL, require('utils/test').dsl);
-process.mixin(GLOBAL, require('utils/date'));
+process.mixin(GLOBAL, require('./test').dsl);
+process.mixin(GLOBAL, require('./date'));
var sys = require('sys');
8 utils/html.js
View
@@ -166,6 +166,14 @@ exports.urlize = urlize;
+/* Function: strip_spaces_between_tags
+ Returns the given HTML with spaces between tags removed.
+ Arguments:
+ input: string, the html to process
+*/
+exports.strip_spaces_between_tags = function (input) {
+ return input.replace(/>\s+</g, '><');
+}
4 utils/html.test.js
View
@@ -1,5 +1,5 @@
-process.mixin(GLOBAL, require('utils/test').dsl);
-process.mixin(GLOBAL, require('utils/html'));
+process.mixin(GLOBAL, require('./test').dsl);
+process.mixin(GLOBAL, require('./html'));
testcase('tests for linebreaks()')
test('should break lines into <p> and <br /> tags', function () {
4 utils/iter.test.js
View
@@ -1,5 +1,5 @@
-process.mixin(GLOBAL, require('utils/test').dsl);
-process.mixin(GLOBAL, require('utils/iter'));
+process.mixin(GLOBAL, require('./test').dsl);
+process.mixin(GLOBAL, require('./iter'));
var events = require('events');
var sys = require('sys');
4 utils/string.test.js
View
@@ -1,6 +1,6 @@
var sys = require('sys');
-process.mixin(GLOBAL, require('utils/test').dsl);
-process.mixin(GLOBAL, require('utils/string'));
+process.mixin(GLOBAL, require('./test').dsl);
+process.mixin(GLOBAL, require('./string'));
testcase('string utility functions');
test('smart_split should split correctly', function () {
90 utils/tags.js
View
@@ -0,0 +1,90 @@
+/*jslint laxbreak: true, eqeqeq: true, undef: true, regexp: false */
+/*global require, process, exports */
+
+
+/* Function: get_args_from_token
+ split token contents, remove the first part (the tagname) and return the
+ rest. Optionally a set of rules to verify the arguments against can be
+ provided.
+
+ Syntax: get_args_from_token(token [,options])
+
+ Arguments:
+ token - the token
+ options - optional, see options
+
+ Options:
+ argcount - number, verify that there is exactly this number of arguments
+ exclude - mixed, a number or an array of numbers specifying arguments that should
+ be excluded from the returned list
+ mustbe - object, an object with numbers as keys and strings or arrays of strings as
+ values. Arguments specified (by their number) in this object must match one
+ of the values, if they are provided in the token.
+
+ Example:
+ // token contains "with 100 as hest"
+ list = get_args_from_token(token, { exclude: 2 }); // list is [100, "hest"]
+ list = get_args_from_token(token, { mustbe: { 2: "is" } }); // throws an error because "as" != "is"
+ list = get_args_from_token(token, { argcount: 4 }); // throws an error because there is not 4 arguments
+*/
+exports.get_args_from_token = function get_args_from_token(token, options) {
+
+ options = options || {};
+
+ var parts = token.split_contents();
+
+ if (options.argcount !== undefined && parts.length !== options.argcount + 1) {
+ throw 'unexpected syntax in "' + token.type + '" tag: Wrong number of arguments';
+ }
+
+ var i;
+ for (i = 1; i < parts.length; i++) {
+ if (options.mustbe && options.mustbe[i]) {
+ var expected = options.mustbe[i];
+ if (expected instanceof Array) {
+ if (expected.indexOf(parts[i]) === -1) {
+ throw 'unexpected syntax in "' + token.type + '" tag: Expected one of "' + expected.join('", "') + '"';
+ }
+ } else if (expected != parts[i]) {
+ throw 'unexpected syntax in "' + token.type + '" tag: Expected "' + options.mustbe[i] + '"';
+ }
+ }
+ }
+
+ if (options.exclude) {
+ if (!(options.exclude instanceof Array)) { options.exclude = [options.exclude] }
+ var include = [];
+ for (i = 1; i < parts.length; i++) {
+ if (options.exclude.indexOf(i) === -1) { include.push(i); }
+ }
+ parts = include.map(function (x) { return parts[x]; });
+ } else {
+ parts = parts.slice(1);
+ }
+
+ return parts;
+}
+
+
+/* Function: simple_tag
+ Creates a parsefunction for a simple tag. That is a tag that takes a
+ number of arguments -- strings or a template variables -- and return a
+ string after doing some processing based solely on the input argument
+ and some external information.
+
+ Syntax: simple_tag(node[, options]);
+
+ Arguments:
+ node - a function that returns a nodefunction when called with the tag arguments
+ options - optional, passed on to get_args_from_token()
+
+ Returns:
+ a parsefunction
+*/
+exports.simple_tag = function simple_tag(node, options) {
+ return function (parser, token) {
+ var parts = get_args_from_token(token, options);
+ return node.apply(null, parts);
+ };
+}
+
7 utils/utils.js
View
@@ -1,7 +0,0 @@
-/*jslint laxbreak: true, eqeqeq: true, undef: true, regexp: false */
-/*global require, exports */
-exports.string = require('utils/string');
-exports.date = require('utils/date');
-exports.html = require('utils/html');
-exports.iter = require('utils/iter');
-
Please sign in to comment.
Something went wrong with that request. Please try again.