vendor/assets/javascripts/hogan.js in hogan_assets-1.1.0 vs vendor/assets/javascripts/hogan.js in hogan_assets-1.2.0
- old
+ new
@@ -16,16 +16,19 @@
var Hogan = {};
(function (Hogan, useArrayBuffer) {
- Hogan.Template = function (renderFunc, text, compiler, options) {
- this.r = renderFunc || this.r;
+ Hogan.Template = function (codeObj, text, compiler, options) {
+ codeObj = codeObj || {};
+ this.r = codeObj.code || this.r;
this.c = compiler;
this.options = options;
this.text = text || '';
- this.buf = (useArrayBuffer) ? [] : '';
+ this.partials = codeObj.partials || {};
+ this.subs = codeObj.subs || {};
+ this.ib();
}
Hogan.Template.prototype = {
// render: replaced by generated code.
r: function (context, partials, indent) { return ''; },
@@ -43,22 +46,49 @@
// render internal -- a hook for overrides that catches partials too
ri: function (context, partials, indent) {
return this.r(context, partials, indent);
},
- // tries to find a partial in the curent scope and render it
- rp: function(name, context, partials, indent) {
- var partial = partials[name];
+ // ensurePartial
+ ep: function(symbol, partials) {
+ var partial = this.partials[symbol];
- if (!partial) {
- return '';
+ // check to see that if we've instantiated this partial before
+ var template = partials[partial.name];
+ if (partial.instance && partial.base == template) {
+ return partial.instance;
}
- if (this.c && typeof partial == 'string') {
- partial = this.c.compile(partial, this.options);
+ if (typeof template == 'string') {
+ if (!this.c) {
+ throw new Error("No compiler available.");
+ }
+ template = this.c.compile(template, this.options);
}
+ if (!template) {
+ return null;
+ }
+
+ // We use this to check whether the partials dictionary has changed
+ this.partials[symbol].base = template;
+
+ if (partial.subs) {
+ template = createSpecializedPartial(template, partial.subs, partial.partials);
+ }
+
+ this.partials[symbol].instance = template;
+ return template;
+ },
+
+ // tries to find a partial in the curent scope and render it
+ rp: function(symbol, context, partials, indent) {
+ var partial = this.ep(symbol, partials);
+ if (!partial) {
+ return '';
+ }
+
return partial.ri(context, partials, indent);
},
// render a section
rs: function(context, partials, section) {
@@ -83,11 +113,11 @@
if (isArray(val) && val.length === 0) {
return false;
}
if (typeof val == 'function') {
- val = this.ls(val, ctx, partials, inverted, start, end, tags);
+ val = this.ms(val, ctx, partials, inverted, start, end, tags);
}
pass = (val === '') || !!val;
if (!inverted && pass && ctx) {
@@ -106,11 +136,11 @@
if (key === '.' && isArray(ctx[ctx.length - 2])) {
return ctx[ctx.length - 1];
}
for (var i = 1; i < names.length; i++) {
- if (val && typeof val == 'object' && names[i] in val) {
+ if (val && typeof val == 'object' && val[names[i]] != null) {
cx = val;
val = val[names[i]];
} else {
val = '';
}
@@ -120,11 +150,11 @@
return false;
}
if (!returnFound && typeof val == 'function') {
ctx.push(cx);
- val = this.lv(val, ctx, partials);
+ val = this.mv(val, ctx, partials);
ctx.pop();
}
return val;
},
@@ -135,11 +165,11 @@
v = null,
found = false;
for (var i = ctx.length - 1; i >= 0; i--) {
v = ctx[i];
- if (v && typeof v == 'object' && key in v) {
+ if (v && typeof v == 'object' && v[key] != null) {
val = v[key];
found = true;
break;
}
}
@@ -147,82 +177,111 @@
if (!found) {
return (returnFound) ? false : "";
}
if (!returnFound && typeof val == 'function') {
- val = this.lv(val, ctx, partials);
+ val = this.mv(val, ctx, partials);
}
return val;
},
// higher order templates
- ho: function(val, cx, partials, text, tags) {
- var compiler = this.c;
- var options = this.options;
- options.delimiters = tags;
- var t = val.call(cx, text, function(t) {
- return compiler.compile(t, options).render(cx, partials);
- });
- this.b(compiler.compile(t.toString(), options).render(cx, partials));
+ ls: function(func, cx, partials, text, tags) {
+ var oldTags = this.options.delimiters;
+
+ this.options.delimiters = tags;
+ this.b(this.ct(coerceToString(func.call(cx, text)), cx, partials));
+ this.options.delimiters = oldTags;
+
return false;
},
+ // compile text
+ ct: function(text, cx, partials) {
+ if (this.options.disableLambda) {
+ throw new Error('Lambda features disabled.');
+ }
+ return this.c.compile(text, this.options).render(cx, partials);
+ },
+
// template result buffering
b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
function(s) { this.buf += s; },
+
fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
function() { var r = this.buf; this.buf = ''; return r; },
+ // init the buffer
+ ib: function () {
+ this.buf = (useArrayBuffer) ? [] : '';
+ },
- // lambda replace section
- ls: function(val, ctx, partials, inverted, start, end, tags) {
+ // method replace section
+ ms: function(func, ctx, partials, inverted, start, end, tags) {
var cx = ctx[ctx.length - 1],
- t = null;
+ result = func.call(cx);
- if (!inverted && this.c && val.length > 0) {
- return this.ho(val, cx, partials, this.text.substring(start, end), tags);
- }
-
- t = val.call(cx);
-
- if (typeof t == 'function') {
+ if (typeof result == 'function') {
if (inverted) {
return true;
- } else if (this.c) {
- return this.ho(t, cx, partials, this.text.substring(start, end), tags);
+ } else {
+ return this.ls(result, cx, partials, this.text.substring(start, end), tags);
}
}
- return t;
+ return result;
},
- // lambda replace variable
- lv: function(val, ctx, partials) {
+ // method replace variable
+ mv: function(func, ctx, partials) {
var cx = ctx[ctx.length - 1];
- var result = val.call(cx);
+ var result = func.call(cx);
+
if (typeof result == 'function') {
- result = result.call(cx);
+ return this.ct(coerceToString(result.call(cx)), cx, partials);
}
- result = coerceToString(result);
- if (this.c && ~result.indexOf("{\u007B")) {
- return this.c.compile(result, this.options).render(cx, partials);
- }
-
return result;
+ },
+
+ sub: function(name, context, partials, indent) {
+ var f = this.subs[name];
+ if (f) {
+ f(context, partials, this, indent);
+ }
}
};
+ function createSpecializedPartial(instance, subs, partials) {
+ function PartialTemplate() {};
+ PartialTemplate.prototype = instance;
+ function Substitutions() {};
+ Substitutions.prototype = instance.subs;
+ var key;
+ var partial = new PartialTemplate();
+ partial.subs = new Substitutions();
+ partial.ib();
+
+ for (key in subs) {
+ partial.subs[key] = subs[key];
+ }
+
+ for (key in partials) {
+ partial.partials[key] = partials[key];
+ }
+
+ return partial;
+ }
+
var rAmp = /&/g,
rLt = /</g,
rGt = />/g,
rApos =/\'/g,
rQuot = /\"/g,
hChars =/[&<>\"\']/;
-
function coerceToString(val) {
return String((val === null || val === undefined) ? '' : val);
}
function hoganEscape(str) {
@@ -243,24 +302,25 @@
})(typeof exports !== 'undefined' ? exports : Hogan);
-
(function (Hogan) {
// Setup regex assignments
// remove whitespace according to Mustache spec
var rIsWhitespace = /\S/,
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,
IN_TEXT = 0,
IN_TAG_TYPE = 1,
IN_TAG = 2,
@@ -275,21 +335,21 @@
otag = '{{',
ctag = '}}';
function addBuf() {
if (buf.length > 0) {
- tokens.push(new String(buf));
+ tokens.push({tag: '_t', text: new String(buf)});
buf = '';
}
}
function lineIsWhitespace() {
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;
}
}
@@ -299,14 +359,14 @@
function filterLine(haveSeenTag, noNewLine) {
addBuf();
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);
}
}
} else if (!noNewLine) {
@@ -349,11 +409,11 @@
buf += text.charAt(i);
}
}
} 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);
state = IN_TEXT;
} else {
@@ -364,11 +424,11 @@
}
seenTag = i;
} else {
if (tagChange(ctag, text, i)) {
tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
- i: (tagType == '/') ? seenTag - ctag.length : i + otag.length});
+ i: (tagType == '/') ? seenTag - otag.length : i + ctag.length});
buf = '';
i += ctag.length - 1;
state = IN_TEXT;
if (tagType == '{') {
if (ctag == '}}') {
@@ -414,34 +474,46 @@
}
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);
} else if (token.tag == '/') {
if (stack.length === 0) {
throw new Error('Closing tag without opener: /' + token.n);
}
opener = stack.pop();
if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
}
opener.end = token.i;
return instructions;
- } else {
- instructions.push(token);
+ } else if (token.tag == '\n') {
+ token.last = (tokens.length == 0) || (tokens[0].tag == '\n');
}
+
+ instructions.push(token);
}
if (stack.length > 0) {
throw new Error('missing closing tag: ' + stack.pop().n);
}
@@ -464,22 +536,66 @@
return true;
}
}
}
- function writeCode(tree) {
- return 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
+ function stringifySubstitutions(obj) {
+ var items = [];
+ for (var key in obj) {
+ items.push('"' + esc(key) + '": function(c,p,t,i) {' + obj[key] + '}');
+ }
+ return "{ " + items.join(",") + " }";
}
- Hogan.generate = function (code, text, options) {
+ function stringifyPartials(codeObj) {
+ var partials = [];
+ for (var key in codeObj.partials) {
+ partials.push('"' + esc(key) + '":{name:"' + esc(codeObj.partials[key].name) + '", ' + stringifyPartials(codeObj.partials[key]) + "}");
+ }
+ return "partials: {" + partials.join(",") + "}, subs: " + stringifySubstitutions(codeObj.subs);
+ }
+
+ Hogan.stringify = function(codeObj, text, options) {
+ return "{code: function (c,p,i) { " + Hogan.wrapMain(codeObj.code) + " }," + stringifyPartials(codeObj) + "}";
+ }
+
+ var serialNo = 0;
+ 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.template = Hogan.Template;
+
+ Hogan.makeTemplate = function(codeObj, text, options) {
+ var template = this.makePartials(codeObj);
+ template.code = new Function('c', 'p', 'i', this.wrapMain(codeObj.code));
+ return new this.template(template, text, this, options);
+ }
+
+ Hogan.makePartials = function(codeObj) {
+ var key, template = {subs: {}, partials: codeObj.partials, name: codeObj.name};
+ for (key in template.partials) {
+ template.partials[key] = this.makePartials(template.partials[key]);
+ }
+ for (key in codeObj.subs) {
+ template.subs[key] = new Function('c', 'p', 't', 'i', codeObj.subs[key]);
+ }
+ return template;
+ }
+
function esc(s) {
return s.replace(rSlash, '\\\\')
.replace(rQuot, '\\\"')
.replace(rNewline, '\\n')
.replace(rCr, '\\r');
@@ -487,95 +603,105 @@
function chooseMethod(s) {
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;
+ function createPartial(node, context) {
+ var prefix = "<" + (context.prefix || "");
+ var sym = prefix + node.n + serialNo++;
+ context.partials[sym] = {name: node.n, partials: {}};
+ 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 invertedSection(nodes, id, method) {
- return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
- walk(nodes) +
- '};';
- }
+ '^': 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 += '};';
+ },
- function partial(tok) {
- return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
+ '>': createPartial,
+ '<': function(node, context) {
+ var ctx = {partials: {}, code: '', subs: {}, inPartial: true};
+ Hogan.walk(node.nodes, ctx);
+ var template = context.partials[createPartial(node, context)];
+ template.subs = ctx.subs;
+ template.partials = ctx.partials;
+ },
+
+ '$': function(node, context) {
+ var ctx = {subs: {}, code: '', partials: context.partials, prefix: node.n};
+ Hogan.walk(node.nodes, ctx);
+ context.subs[node.n] = ctx.code;
+ if (!context.inPartial) {
+ context.code += 't.sub("' + esc(node.n) + '",c,p,i);';
+ }
+ },
+
+ '\n': function(node, context) {
+ context.code += write('"\\n"' + (node.last ? '' : ' + i'));
+ },
+
+ '_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];
+ func && func(nodelist[i], context);
+ }
+ return context;
}
Hogan.parse = function(tokens, text, options) {
options = options || {};
return buildTree(tokens, '', [], options.sectionTags || []);
},
Hogan.cache = {};
+ Hogan.cacheKey = function(text, options) {
+ return [text, !!options.asString, !!options.disableLambda].join('||');
+ },
+
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(writeCode(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);