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);