Permalink
Browse files

Hogan 3. Add template inheritance, allow disabling of lambda replace,

document compilation options, make the compiler more existensible,
performance improvements.
  • Loading branch information...
1 parent 74c2115 commit 73ec68b72df84553cccb55bc6dd69c198c13723a Rob Sayre committed Mar 20, 2012
Showing with 549 additions and 165 deletions.
  1. +28 −0 README.md
  2. +151 −79 lib/compiler.js
  3. +96 −45 lib/template.js
  4. +1 −1 package.json
  5. +273 −40 test/index.js
View
@@ -56,6 +56,34 @@ Why another templating library?
Hogan.js was written to meet three templating library requirements: good
performance, standalone template objects, and a parser API.
+## Compilation options
+
+The second argument to Hogan.compile is an options hash.
+```js
+var text = "my <%example%> template."
+Hogan.compile(text, {delimiters: '<% %>'});
+```
+
+There are current four valid options.
+
+asString: return the compiled template as a string. This feature is used
+by hulk to produce strings containing pre-compiled templates.
+
+sectionTags: allow custom tags that require opening and closing tags, and
+treat them as though they were section tags.
+
+```js
+var text = "my {{_foo}}example{{/foo}} template."
+Hogan.compile(text, { sectionTags: [{o: '_foo', c: 'foo'}]});
+```
+
+The value is an array of object with o and c fields that indicate names
+for custom section tags. The example above allows parsing of {{_foo}}{{/foo}}.
+
+delimiters: A string that overrides the default delimiters. Example: "<% %>".
+
+disableLambda: disables the higher-order sections / lambda-replace features of Mustache.
+
## Issues
Have a bug? Please create an issue here on GitHub!
View
@@ -20,11 +20,13 @@
rQuot = /\"/g,
rNewline = /\n/g,
rCr = /\r/g,
- rSlash = /\\/g,
- tagTypes = {
- '#': 1, '^': 2, '/': 3, '!': 4, '>': 5,
- '<': 6, '=': 7, '_v': 8, '{': 9, '&': 10
- };
+ rSlash = /\\/g;
+
+ Hogan.tags = {
+ '#': 1, '^': 2, '<': 3, '$': 4,
+ '/': 5, '!': 6, '>': 7, '=': 8, '_v': 9,
+ '{': 10, '&': 11, '_t': 12
+ };
Hogan.scan = function scan(text, delimiters) {
var len = text.length,
@@ -44,7 +46,7 @@
function addBuf() {
if (buf.length > 0) {
- tokens.push(new String(buf));
+ tokens.push({tag: '_t', text: new String(buf)});
buf = '';
}
}
@@ -53,8 +55,8 @@
var isAllWhitespace = true;
for (var j = lineStart; j < tokens.length; j++) {
isAllWhitespace =
- (tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) ||
- (!tokens[j].tag && tokens[j].match(rIsWhitespace) === null);
+ (Hogan.tags[tokens[j].tag] < Hogan.tags['_v']) ||
+ (tokens[j].tag == '_t' && tokens[j].text.match(rIsWhitespace) === null);
if (!isAllWhitespace) {
return false;
}
@@ -68,10 +70,10 @@
if (haveSeenTag && lineIsWhitespace()) {
for (var j = lineStart, next; j < tokens.length; j++) {
- if (!tokens[j].tag) {
+ if (tokens[j].text) {
if ((next = tokens[j+1]) && next.tag == '>') {
// set indent to token value
- next.indent = tokens[j].toString()
+ next.indent = tokens[j].text.toString()
}
tokens.splice(j, 1);
}
@@ -118,7 +120,7 @@
}
} else if (state == IN_TAG_TYPE) {
i += otag.length - 1;
- tag = tagTypes[text.charAt(i + 1)];
+ tag = Hogan.tags[text.charAt(i + 1)];
tagType = tag ? text.charAt(i + 1) : '_v';
if (tagType == '=') {
i = changeDelimiters(text, i);
@@ -183,14 +185,25 @@
return true;
}
+ // the tags allowed inside super templates
+ var allowedInSuper = {'_t': true, '\n': true, '$': true, '/': true};
+
function buildTree(tokens, kind, stack, customTags) {
var instructions = [],
opener = null,
+ tail = null,
token = null;
+ tail = stack[stack.length - 1];
+
while (tokens.length > 0) {
token = tokens.shift();
- if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) {
+
+ if (tail && tail.tag == '<' && !(token.tag in allowedInSuper)) {
+ throw new Error('Illegal content in < super tag.');
+ }
+
+ if (Hogan.tags[token.tag] <= Hogan.tags['$'] || isOpener(token, customTags)) {
stack.push(token);
token.nodes = buildTree(tokens, token.tag, stack, customTags);
instructions.push(token);
@@ -204,6 +217,9 @@
}
opener.end = token.i;
return instructions;
+ } else if (token.tag == '\n') {
+ token.last = (tokens.length == 0) || (tokens[0].tag == '\n');
+ instructions.push(token);
} else {
instructions.push(token);
}
@@ -233,13 +249,58 @@
}
}
- Hogan.generate = function (tree, text, options) {
- var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
+ function stringifyFunctions(obj) {
+ var items = [];
+ for (var key in obj) {
+ items.push('"' + esc(key) + '": function(c,p,t) {' + obj[key] + '}');
+ }
+ return "{ " + items.join(",") + " }";
+ }
+
+ Hogan.stringify = function(codeObj, text, options) {
+ var partials = [];
+ var result = "{main: function (c,p,i) { " + Hogan.wrapMain(codeObj.code) + " }, partials: {";
+ for (var key in codeObj.partials) {
+ partials.push('"' + esc(key) + '": "' + esc(codeObj.partials[key].name) + '"');
+ }
+ result += partials.join(",") + "}, subs: " + stringifyFunctions(codeObj.subs) + " }";
+ return result;
+ }
+
+ Hogan.generate = function(tree, text, options) {
+ serialNo = 0;
+ var context = { code: '', subs: {}, partials: {} };
+ Hogan.walk(tree, context);
+
if (options.asString) {
- return 'function(c,p,i){' + code + ';}';
+ return this.stringify(context, text, options);
}
- return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options);
+ return this.makeTemplate(context, text, options);
+ }
+
+ Hogan.wrapMain = function(code) {
+ return 'var t=this;t.b(i=i||"");' + code + 'return t.fl();';
+ }
+
+ Hogan.makeTemplate = function(codeObj, text, options) {
+ var code = {
+ main: new Function('c', 'p', 'i', this.wrapMain(codeObj.code)),
+ subs: {},
+ partials: {}
+ };
+ code.partials = codeObj.partials;
+ for (var key in code.partials) {
+ addFunctionsToObject(code.partials[key].subs, code.partials[key].subs);
+ }
+ addFunctionsToObject(codeObj.subs, code.subs);
+ return new Hogan.Template(code, text, Hogan, options);
+ }
+
+ function addFunctionsToObject(source, target, asString) {
+ for (var key in source) {
+ target[key] = new Function('c', 'p', 't', source[key]);
+ }
}
function esc(s) {
@@ -253,60 +314,79 @@
return (~s.indexOf('.')) ? 'd' : 'f';
}
- function walk(tree) {
- var code = '';
- for (var i = 0, l = tree.length; i < l; i++) {
- var tag = tree[i].tag;
- if (tag == '#') {
- code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n),
- tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag);
- } else if (tag == '^') {
- code += invertedSection(tree[i].nodes, tree[i].n,
- chooseMethod(tree[i].n));
- } else if (tag == '<' || tag == '>') {
- code += partial(tree[i]);
- } else if (tag == '{' || tag == '&') {
- code += tripleStache(tree[i].n, chooseMethod(tree[i].n));
- } else if (tag == '\n') {
- code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i'));
- } else if (tag == '_v') {
- code += variable(tree[i].n, chooseMethod(tree[i].n));
- } else if (tag === undefined) {
- code += text('"' + esc(tree[i]) + '"');
- }
- }
- return code;
+ var serialNo = 0;
+ function createPartial(node, context) {
+ var sym = node.n + serialNo++;
+ context.partials[sym] = {name: node.n};
+ context.code += 't.b(t.rp("' + esc(sym) + '",c,p,"' + (node.indent || '') + '"));';
+ return sym;
}
- function section(nodes, id, method, start, end, tags) {
- return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' +
- 'c,p,0,' + start + ',' + end + ',"' + tags + '")){' +
- '_.rs(c,p,' +
- 'function(c,p,_){' +
- walk(nodes) +
- '});c.pop();}';
- }
+ Hogan.codegen = {
+ '#': function(node, context) {
+ context.code += 'if(t.s(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,1),' +
+ 'c,p,0,' + node.i + ',' + node.end + ',"' + node.otag + " " + node.ctag + '")){' +
+ 't.rs(c,p,' + 'function(c,p,t){';
+ Hogan.walk(node.nodes, context);
+ context.code += '});c.pop();}';
+ },
+
+ '^': function(node, context) {
+ context.code += 'if(!t.s(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,1),c,p,1,0,0,"")){';
+ Hogan.walk(node.nodes, context);
+ context.code += '};';
+ },
+
+ '>': createPartial,
+ '<': function(node, context) {
+ var ctx = {partials: context.partials, code: '', subs: {}, inPartial: true};
+ Hogan.walk(node.nodes, ctx);
+ context.partials[createPartial(node, context)].subs = ctx.subs;
+ },
+
+ '$': function(node, context) {
+ var ctx = {subs: {}, code: '', partials: context.partials};
+ Hogan.walk(node.nodes, ctx);
+ context.subs[node.n] = ctx.code;
+ if (!context.inPartial) {
+ context.code += 't.sub("' + esc(node.n) + '",c,p);';
+ }
+ },
- function invertedSection(nodes, id, method) {
- return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
- walk(nodes) +
- '};';
- }
+ '\n': function(node, context) {
+ context.code += write('"\\n"' + (node.last ? '' : ' + i'));
+ },
- function partial(tok) {
- return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
+ '_v': function(node, context) {
+ context.code += 't.b(t.v(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,0)));';
+ },
+
+ '_t': function(node, context) {
+ context.code += write('"' + esc(node.text) + '"');
+ },
+
+ '{': tripleStache,
+
+ '&': tripleStache
}
- function tripleStache(id, method) {
- return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));';
+ function tripleStache(node, context) {
+ context.code += 't.b(t.t(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,0)));';
}
- function variable(id, method) {
- return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));';
+ function write(s) {
+ return 't.b(' + s + ');';
}
- function text(id) {
- return '_.b(' + id + ');';
+ Hogan.walk = function (nodelist, context) {
+ var func;
+ for (var i = 0, l = nodelist.length; i < l; i++) {
+ func = Hogan.codegen[nodelist[i].tag];
+ if (func) {
+ func(nodelist[i], context);
+ }
+ }
+ return context;
}
Hogan.parse = function(tokens, text, options) {
@@ -316,29 +396,21 @@
Hogan.cache = {};
+ Hogan.cacheKey = function(text, options) {
+ var key = text + "||" + !!options.asString + '||' + !!options.disableLambda;
+ return key;
+ },
+
Hogan.compile = function(text, options) {
- // options
- //
- // asString: false (default)
- //
- // sectionTags: [{o: '_foo', c: 'foo'}]
- // An array of object with o and c fields that indicate names for custom
- // section tags. The example above allows parsing of {{_foo}}{{/foo}}.
- //
- // delimiters: A string that overrides the default delimiters.
- // Example: "<% %>"
- //
options = options || {};
+ var key = Hogan.cacheKey(text, options);
+ var template = this.cache[key];
- var key = text + '||' + !!options.asString;
-
- var t = this.cache[key];
-
- if (t) {
- return t;
+ if (template) {
+ return template;
}
- t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
- return this.cache[key] = t;
+ template = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
+ return this.cache[key] = template;
};
})(typeof exports !== 'undefined' ? exports : Hogan);
Oops, something went wrong.

0 comments on commit 73ec68b

Please sign in to comment.