Skip to content

Commit

Permalink
Implemented block and extends tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Anders Hellerup Madsen committed Jan 14, 2010
1 parent 61c3ed0 commit 658506a
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 82 deletions.
2 changes: 1 addition & 1 deletion example.js
@@ -1,4 +1,4 @@
var dj = require('./djangode'); var dj = require('djangode');


var app = dj.makeApp([ var app = dj.makeApp([
['^/$', function(req, res) { ['^/$', function(req, res) {
Expand Down
110 changes: 73 additions & 37 deletions template/template.js
@@ -1,8 +1,10 @@
/*jslint laxbreak: true, eqeqeq: true, undef: true, regexp: false */ /*jslint laxbreak: true, eqeqeq: true, undef: true, regexp: false */
/*global require, process, exports */ /*global require, process, exports */

var sys = require('sys'); var sys = require('sys');
var utils = require('../utils/utils');
var template_defaults = require('./template_defaults'); var utils = require('utils/utils');
var template_defaults = require('template/template_defaults');


/***************** TOKENIZER ******************************/ /***************** TOKENIZER ******************************/


Expand Down Expand Up @@ -68,13 +70,30 @@ function tokenize(input) {


/*********** PARSER **********************************/ /*********** PARSER **********************************/


function Parser(input) {
this.token_list = tokenize(input);
this.indent = 0;
this.blocks = {};
}

function parser_error(e) { function parser_error(e) {
return 'Parsing exception: ' + JSON.stringify(e, 0, 2); return 'Parsing exception: ' + JSON.stringify(e, 0, 2);
} }


function Parser(input) { function make_nodelist() {
this.token_list = tokenize(input); var node_list = [];
this.indent = 0; node_list.evaluate = function (context) {
return this.reduce( function (p, c) { return p + c(context); }, '');
};
node_list.only_types = function (/*args*/) {
var args = Array.prototype.slice.apply(arguments);
return this.filter( function (x) { return args.indexOf(x.type) > -1; } );
};
node_list.append = function (node, type) {
node.type = type;
this.push(node);
};
return node_list;
} }


process.mixin(Parser.prototype, { process.mixin(Parser.prototype, {
Expand All @@ -84,7 +103,7 @@ process.mixin(Parser.prototype, {
parse: function () { parse: function () {


var stoppers = Array.prototype.slice.apply(arguments); var stoppers = Array.prototype.slice.apply(arguments);
var node_list = []; var node_list = make_nodelist();
var token = this.token_list[0]; var token = this.token_list[0];
var callback = null; var callback = null;


Expand All @@ -102,10 +121,13 @@ process.mixin(Parser.prototype, {


callback = this.callbacks[token.type]; callback = this.callbacks[token.type];
if (callback && typeof callback === 'function') { if (callback && typeof callback === 'function') {
node_list.push( callback(this, token) ); node_list.append( callback(this, token), token.type );
} else { } else {
//throw parser_error('Unknown tag: ' + token[0]); //throw parser_error('Unknown tag: ' + token[0]);
node_list.push( template_defaults.TextNode('[[ UNKNOWN ' + token.type + ' ]]')); node_list.append(
template_defaults.nodes.TextNode('[[ UNKNOWN ' + token.type + ' ]]'),
'UNKNOWN'
);
} }
} }
if (stoppers.length) { if (stoppers.length) {
Expand Down Expand Up @@ -142,8 +164,11 @@ function normalize(value) {


/*************** Context *********************************/ /*************** Context *********************************/


function Context(o) { function Context(o, blockmark) {
this.scope = [ o ]; this.scope = [ o ];
this.extends = [];
this.blocks = {};
this.blockmark = blockmark;
} }


process.mixin(Context.prototype, { process.mixin(Context.prototype, {
Expand All @@ -168,7 +193,11 @@ process.mixin(Context.prototype, {
} }
} }


return val; if (typeof val === 'function') {
return val();
} else {
return val;
}
} }
} }


Expand All @@ -182,24 +211,27 @@ process.mixin(Context.prototype, {
}, },
pop: function () { pop: function () {
return this.scope.shift(); return this.scope.shift();
},
block_placeholder: function (name) {
return this.blockmark + name + this.blockmark;
} }
}); });


/*********** FilterExpression **************************/ /*********** FilterExpression **************************/


var FilterExpression = function (expression, constant) { var FilterExpression = function (expression, constant) {


// groups 1 = variable/constant, 2 = arg name, 3 = arg value without qoutes // groups 1 = variable/constant, 2 = arg name, 3 = arg value
this.re = /(^"[^"\\]*(?:\\.[^"\\]*)*"|^[\w\.]+)?(?:\|(\w+\b)(?::"([^"\\]*(?:\\.[^"\\]*)*)")?)?(?=\S|$)/g; this.re = /(^"[^"\\]*(?:\\.[^"\\]*)*"|^[\w\.]+)?(?:\|(\w+\b)(?::("[^"\\]*(?:\\.[^"\\]*)*"|[^\|\s]+))?)?(?=\S|$)/g;
this.re.lastIndex = 0; this.re.lastIndex = 0;


var parsed = this.consume(expression); var parsed = this.consume(expression);
if (!parsed) { if (!parsed) {
throw this.error(expression + ' - 1'); throw this.error(expression);
} }
if (constant) { if (constant) {
if (parsed.variable) { if (parsed.variable) {
throw this.error(expression + ' - 2'); // did not expect variable when constant is defined... throw this.error(expression); // did not expect variable when constant is defined...
} else { } else {
this.constant = constant; this.constant = constant;
} }
Expand All @@ -212,7 +244,7 @@ var FilterExpression = function (expression, constant) {
this.variable = parsed.variable; this.variable = parsed.variable;
} }
} else { } else {
throw this.error(expression + ' - 3'); throw this.error(expression);
} }
} }


Expand Down Expand Up @@ -267,32 +299,42 @@ exports.FilterExpression = FilterExpression;


/*********** Template **********************************/ /*********** Template **********************************/


function Template(node_list) { function Template(input) {
this.node_list = node_list; var parser = new Parser(input);
this.node_list = parser.parse();
} }


var replace_blocks_re = /\u0000\u0000\u0000(\w+)\u0000\u0000\u0000/g

process.mixin(Template.prototype, { process.mixin(Template.prototype, {
render: function (o) { render: function (o, delay_blocks) {
var context = new Context(o);
try { var context = (o instanceof Context) ? o : new Context(o || {}, '\u0000\u0000\u0000');
return evaluate_node_list(this.node_list, context); if (!o instanceof Context)
} catch (e) { var context = new Context(o || {});
sys.debug(e);
return e; var rendered = this.node_list.evaluate(context);

if (context.extends.length > 0) {
rendered = context.extends.pop();
} }
}
if (!delay_blocks) {
rendered = rendered.replace( replace_blocks_re, function (str, name) {
return context.blocks[name];
});
}

return rendered;
},
}); });


/********************************************************/ /********************************************************/


exports.parse = function (input) { exports.parse = function (input) {
var parser = new Parser(input); //var parser = new Parser(input);
// TODO: Better error handling, this is lame // TODO: Better error handling, this is lame
try { return new Template(input);
return new Template(parser.parse());
} catch (e) {
return new Template([ template_defaults.nodes.TextNode(e) ]);
}
}; };


// TODO: Make this a property on a token class // TODO: Make this a property on a token class
Expand All @@ -302,12 +344,6 @@ function split_token(input) {
exports.split_token = split_token; exports.split_token = split_token;




// TODO: Node lists are always created by the Parser class, so it could extend the list with this method.
function evaluate_node_list (node_list, context) {
return node_list.reduce( function (p, c) { return p + c(context); }, '');
}
exports.evaluate_node_list = evaluate_node_list;



// exported for test // exported for test
exports.Context = Context; exports.Context = Context;
Expand Down
30 changes: 21 additions & 9 deletions template/template.test.js
@@ -1,6 +1,6 @@
var sys = require('sys'); var sys = require('sys');
process.mixin(GLOBAL, require('../utils/test').dsl); process.mixin(GLOBAL, require('utils/test').dsl);
process.mixin(GLOBAL, require('./template')); process.mixin(GLOBAL, require('template/template'));


testcase('Test tokenizer'); testcase('Test tokenizer');
test('sanity test', function () { test('sanity test', function () {
Expand Down Expand Up @@ -36,8 +36,8 @@ testcase('Filter Expression tests');
new FilterExpression("item.subitem|add|sub") new FilterExpression("item.subitem|add|sub")
); );
assertEquals( assertEquals(
{ variable: 'item', filter_list: [ { name: 'add', arg: 5 }, { name: 'sub', arg: 2 } ] }, { variable: 'item', filter_list: [ { name: 'add', arg: 5 }, { name: 'sub', arg: "2" } ] },
new FilterExpression('item|add:"5"|sub:"2"') new FilterExpression('item|add:5|sub:"2"')
); );
assertEquals( assertEquals(
{ variable: 'item', filter_list: [ { name: 'concat', arg: 'heste er naijs' } ] }, { variable: 'item', filter_list: [ { name: 'concat', arg: 'heste er naijs' } ] },
Expand All @@ -60,11 +60,12 @@ testcase('Filter Expression tests');
test('should fail on invalid syntax', function () { test('should fail on invalid syntax', function () {
function attempt(s) { return new FilterExpression(s); } function attempt(s) { return new FilterExpression(s); }


shouldThrow(attempt, 'item |add:"2"'); shouldThrow(attempt, 'item |add:2');
shouldThrow(attempt, 'item| add:"2"'); shouldThrow(attempt, 'item| add:2');
shouldThrow(attempt, 'item|add :"2"'); shouldThrow(attempt, 'item|add :2');
shouldThrow(attempt, 'item|add: "2"'); shouldThrow(attempt, 'item|add: 2');
shouldThrow(attempt, 'item|add|:"2"|sub'); shouldThrow(attempt, 'item|add|:2|sub');
shouldThrow(attempt, 'item|add:2 |sub');
}); });


testcase('Context test'); testcase('Context test');
Expand Down Expand Up @@ -114,5 +115,16 @@ testcase('Context test');
assertEquals(tc.plain.a, tc.context.get('a')); assertEquals(tc.plain.a, tc.context.get('a'));
}); });


testcase('parser')
test('should parse', function () {
t = parse('hest');
assertEquals('hest', t.render());
});
test('node_list only_types should return only requested typed', function () {
t = parse('{% comment %}hest{% endcomment %}hest{% comment %}laks{% endcomment %}{% hest %}');
assertEquals(['comment','comment'], t.node_list.only_types('comment').map(function(x){return x.type}));
assertEquals(['text','UNKNOWN'], t.node_list.only_types('text', 'UNKNOWN').map(function(x){return x.type}));
});

run(); run();


0 comments on commit 658506a

Please sign in to comment.