Skip to content

Commit

Permalink
Add array literal syntax, add << and >> is-in operators, and re-imple…
Browse files Browse the repository at this point in the history
…ment arrow sub-section renaming
  • Loading branch information
chbrown committed Jan 10, 2014
1 parent 5266586 commit 4f6c183
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 14 deletions.
4 changes: 2 additions & 2 deletions index.js
Expand Up @@ -113,7 +113,7 @@ Manager.create = function(options, callback) {
};

// Manager will also serve as a interface to the singleton manager, with certain defaults.
Manager.set = function(options) {
Manager.set = function(options, callback) {
/** set: Creates a singleton instance and attaches it to the main export, Manager.
Also attaches .stream() and .string() to the main export as helpers, e.g.:
Expand All @@ -129,7 +129,7 @@ Manager.set = function(options) {
...
*/
var manager = Manager.singleton = Manager.create(options);
var manager = Manager.singleton = Manager.create(options, callback);
Manager.stream = manager.stream.bind(manager);
Manager.string = manager.string.bind(manager);
return manager;
Expand Down
4 changes: 4 additions & 0 deletions lib/cache.js
Expand Up @@ -40,3 +40,7 @@ Cache.prototype.get = function(template_name, callback) {
});
}
};

Cache.prototype.reset = function() {
this.templates = {};
};
13 changes: 13 additions & 0 deletions lib/helpers.js
@@ -1,5 +1,14 @@
'use strict'; /*jslint es5: true, node: true, indent: 2 */

exports.contains = function(needle, haystack) {
if (Array.isArray(haystack)) {
return haystack.indexOf(needle) > -1;
}
else if (Object(haystack) === haystack) {
return needle in haystack;
}
return false;
};

exports.extend = function(destination /*, sources*/) {
/** `extend(...)`: just like _.extend(destination, *sources), copy all values
Expand Down Expand Up @@ -223,5 +232,9 @@ exports.flexibleEval = function(context, expression, globals) {
// floating point literal
return parseFloat(trimmed_expression);
}
else if (trimmed_expression.match(/^\[.*]$/)) {
// array literal
return trimmed_expression.slice(1, -1).split(',');
}
return dottedEval(context, trimmed_expression, globals)[0];
};
2 changes: 1 addition & 1 deletion lib/parsers/string.js
Expand Up @@ -40,7 +40,7 @@ StringParser.prototype.parse = function(template_string) {
var index = 0; // pointer to position in template
var root = [];
var stack = [root];
var opts = this.options;
var opts = helpers.extend({}, this.options, {minify: false, open: '{{', close: '}}'});

var push = function(node) {
stack[stack.length - 1].push(node);
Expand Down
38 changes: 27 additions & 11 deletions lib/renderer.js
Expand Up @@ -70,7 +70,9 @@ Renderer.prototype.renderTokens = function(tokens, context, callback) {
// xxx: what about partials with context lookups (variables) for names?
// xxx: or partials with yields?
renderer.template_cache.get(node.val, function(err, subtokens) {
if (err) return callback(err);
// mustache spec says ignore missing partials
if (err && err.code == 'ENOENT') return next();
else if (err) return callback(err);
renderer.renderTokens(subtokens, context, function(err) {
if (err) return callback(err);
next();
Expand Down Expand Up @@ -118,7 +120,20 @@ Renderer.prototype.renderTokens = function(tokens, context, callback) {
var active = node.t == 'section' ? value : !value;
if (active) {
var renderBlock = function(item, callback) {
var block_context = item === Object(item) ? item : {'.': item, __depth: 0};
var block_context = {__depth: 0};
if (node.sub) {
// if the template specifies a "sub" name (with -> or =>), use that to embed each entry
block_context[node.sub] = item;
}
else if (item === Object(item)) {
// if the item is a plain object, it overlays the context, not embedded
block_context = item;
}
else {
// otherwise, if the children are not Objects, and no 'sub' name is specified,
// use the special '.' key as the embedding name.
block_context['.'] = item;
}
helpers.protoPush(block_context, context); // make item.__proto__ == context
renderer.renderTokens(node.block, block_context, function(err) {
if (err) return callback(err);
Expand Down Expand Up @@ -156,25 +171,26 @@ Renderer.prototype.evaluateExpression = function(context, expression) {
// expression can be a function call, a boolean, or a simple string.
var globals = this.globals;
var m;
if ((m = expression.match(/^(\S+)\s*([!=<>]+)\s*(\S+)$/))) {
// booleans: ==, ===, !=, !==, <, <=, >, >=
// a << b returns true if a contains b (b in a), a >> b returns true if b contains a (a in b)
// i.e., the syntax is (haystack << needle), and helpers.contains is function(needle, haystack)
var ops = ['==', '===', '!=', '!==', '<', '<=', '>', '>=', '>>', '<<'];
var op_regex = new RegExp('^(\\S+)\\s*(' + ops.join('|') + ')\\s*(\\S+)$');
if ((m = expression.match(op_regex))) {
var left = helpers.flexibleEval(context, m[1], globals);
var op = m[2];
var right = helpers.flexibleEval(context, m[3], globals);

if (op === '==') return left == right;
else if (op === '===') return left === right;

if (op === '===') return left === right;
else if (op === '!=') return left != right;
else if (op === '!==') return left !== right;
else if (op === '<') return left < right;
else if (op === '<=') return left <= right;
else if (op === '>') return left > right;
else if (op === '>=') return left >= right;
else {
var op_err = new Error('Unrecognized operator "' + op + '" in expression: ' + expression);
this.emit('error', op_err);
return false;
}
else if (op === '>>') return helpers.contains(left, right);
else if (op === '<<') return helpers.contains(right, left);
else /*if (op === '==')*/ return left == right;
}
else if ((m = expression.match(/^(\S+)\(([^\)]*)\)$/))) {
// functions.
Expand Down

0 comments on commit 4f6c183

Please sign in to comment.