|
|
@@ -9,12 +9,18 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {}; |
|
|
exports.name = "mustache.js"; |
|
|
exports.name = "mustache.js"; |
|
|
exports.version = "0.5.0-dev"; |
|
|
exports.version = "0.5.0-dev"; |
|
|
exports.tags = ["{{", "}}"]; |
|
|
exports.tags = ["{{", "}}"]; |
|
|
|
|
|
|
|
|
exports.parse = parse; |
|
|
exports.parse = parse; |
|
|
|
|
|
exports.clearCache = clearCache; |
|
|
exports.compile = compile; |
|
|
exports.compile = compile; |
|
|
|
|
|
exports.compilePartial = compilePartial; |
|
|
exports.render = render; |
|
|
exports.render = render; |
|
|
exports.clearCache = clearCache; |
|
|
|
|
|
|
|
|
|
|
|
// This is here for backwards compatibility with 0.4.x. |
|
|
|
|
|
|
|
|
exports.Scanner = Scanner; |
|
|
|
|
|
exports.Context = Context; |
|
|
|
|
|
exports.Renderer = Renderer; |
|
|
|
|
|
|
|
|
|
|
|
// // This is here for backwards compatibility with 0.4.x. |
|
|
exports.to_html = function (template, view, partials, send) { |
|
|
exports.to_html = function (template, view, partials, send) { |
|
|
var result = render(template, view, partials); |
|
|
var result = render(template, view, partials); |
|
|
|
|
|
|
|
|
@@ -25,63 +31,30 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {}; |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
var _toString = Object.prototype.toString; |
|
|
|
|
|
var _isArray = Array.isArray; |
|
|
|
|
|
var _forEach = Array.prototype.forEach; |
|
|
|
|
|
var _trim = String.prototype.trim; |
|
|
|
|
|
|
|
|
|
|
|
var isArray; |
|
|
|
|
|
if (_isArray) { |
|
|
|
|
|
isArray = _isArray; |
|
|
|
|
|
} else { |
|
|
|
|
|
isArray = function (obj) { |
|
|
|
|
|
return _toString.call(obj) === "[object Array]"; |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var forEach; |
|
|
|
|
|
if (_forEach) { |
|
|
|
|
|
forEach = function (obj, callback, scope) { |
|
|
|
|
|
return _forEach.call(obj, callback, scope); |
|
|
|
|
|
}; |
|
|
|
|
|
} else { |
|
|
|
|
|
forEach = function (obj, callback, scope) { |
|
|
|
|
|
for (var i = 0, len = obj.length; i < len; ++i) { |
|
|
|
|
|
callback.call(scope, obj[i], i, obj); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var spaceRe = /^\s*$/; |
|
|
|
|
|
|
|
|
var whiteRe = /\s*/; |
|
|
|
|
|
var spaceRe = /\s+/; |
|
|
|
|
|
var nonSpaceRe = /\S/; |
|
|
|
|
|
var eqRe = /\s*=/; |
|
|
|
|
|
var curlyRe = /\s*\}/; |
|
|
|
|
|
var tagRe = /#|\^|\/|>|\{|&|=|!/; |
|
|
|
|
|
|
|
|
function isWhitespace(string) { |
|
|
function isWhitespace(string) { |
|
|
return spaceRe.test(string); |
|
|
|
|
|
|
|
|
return !nonSpaceRe.test(string); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var trim; |
|
|
|
|
|
if (_trim) { |
|
|
|
|
|
trim = function (string) { |
|
|
|
|
|
return string == null ? "" : _trim.call(string); |
|
|
|
|
|
}; |
|
|
|
|
|
} else { |
|
|
|
|
|
var trimLeft, trimRight; |
|
|
|
|
|
|
|
|
var isArray = Array.isArray || function (obj) { |
|
|
|
|
|
return Object.prototype.toString.call(obj) === "[object Array]"; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
if (isWhitespace("\xA0")) { |
|
|
|
|
|
trimLeft = /^\s+/; |
|
|
|
|
|
trimRight = /\s+$/; |
|
|
|
|
|
} else { |
|
|
|
|
|
// IE doesn't match non-breaking spaces with \s, thanks jQuery. |
|
|
|
|
|
trimLeft = /^[\s\xA0]+/; |
|
|
|
|
|
trimRight = /[\s\xA0]+$/; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var quote = (typeof JSON !== "undefined" && JSON.stringify) || function (string) { |
|
|
|
|
|
return '"' + String(string).replace(/(^|[^\\])"/g, '\\"') + '"'; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
trim = function (string) { |
|
|
|
|
|
return string == null ? "" : |
|
|
|
|
|
String(string).replace(trimLeft, "").replace(trimRight, ""); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
function escapeRe(string) { |
|
|
|
|
|
return string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var escapeMap = { |
|
|
|
|
|
|
|
|
var entityMap = { |
|
|
"&": "&", |
|
|
"&": "&", |
|
|
"<": "<", |
|
|
"<": "<", |
|
|
">": ">", |
|
|
">": ">", |
|
|
@@ -89,448 +62,519 @@ var Mustache = (typeof module !== "undefined" && module.exports) || {}; |
|
|
"'": ''' |
|
|
"'": ''' |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
function escapeHTML(string) { |
|
|
|
|
|
|
|
|
function escapeHtml(string) { |
|
|
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { |
|
|
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { |
|
|
return escapeMap[s] || s; |
|
|
|
|
|
|
|
|
return entityMap[s]; |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Export these utility functions. |
|
|
|
|
|
exports.isWhitespace = isWhitespace; |
|
|
|
|
|
exports.isArray = isArray; |
|
|
|
|
|
exports.quote = quote; |
|
|
|
|
|
exports.escapeRe = escapeRe; |
|
|
|
|
|
exports.escapeHtml = escapeHtml; |
|
|
|
|
|
|
|
|
|
|
|
function Scanner(string) { |
|
|
|
|
|
this.string = string; |
|
|
|
|
|
this.tail = string; |
|
|
|
|
|
this.pos = 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Adds the `template`, `line`, and `file` properties to the given error |
|
|
|
|
|
* object and alters the message to provide more useful debugging information. |
|
|
|
|
|
|
|
|
* Returns `true` if the tail is empty (end of string). |
|
|
*/ |
|
|
*/ |
|
|
function debug(e, template, line, file) { |
|
|
|
|
|
file = file || "<template>"; |
|
|
|
|
|
|
|
|
|
|
|
var lines = template.split("\n"), |
|
|
|
|
|
start = Math.max(line - 3, 0), |
|
|
|
|
|
end = Math.min(lines.length, line + 3), |
|
|
|
|
|
context = lines.slice(start, end); |
|
|
|
|
|
|
|
|
|
|
|
var c; |
|
|
|
|
|
for (var i = 0, len = context.length; i < len; ++i) { |
|
|
|
|
|
c = i + start + 1; |
|
|
|
|
|
context[i] = (c === line ? " >> " : " ") + context[i]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Scanner.prototype.eos = function () { |
|
|
|
|
|
return this.tail === ""; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
e.template = template; |
|
|
|
|
|
e.line = line; |
|
|
|
|
|
e.file = file; |
|
|
|
|
|
e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n"); |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Tries to match the given regular expression at the current position. |
|
|
|
|
|
* Returns the matched text if it can match, `null` otherwise. |
|
|
|
|
|
*/ |
|
|
|
|
|
Scanner.prototype.scan = function (re) { |
|
|
|
|
|
var match = this.tail.match(re); |
|
|
|
|
|
|
|
|
return e; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (match && match.index === 0) { |
|
|
|
|
|
this.tail = this.tail.substring(match[0].length); |
|
|
|
|
|
this.pos += match[0].length; |
|
|
|
|
|
return match[0]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Looks up the value of the given `name` in the given context `stack`. |
|
|
|
|
|
|
|
|
* Skips all text until the given regular expression can be matched. Returns |
|
|
|
|
|
* the skipped string, which is the entire tail of this scanner if no match |
|
|
|
|
|
* can be made. |
|
|
*/ |
|
|
*/ |
|
|
function lookup(name, stack, defaultValue) { |
|
|
|
|
|
if (name === ".") { |
|
|
|
|
|
return stack[stack.length - 1]; |
|
|
|
|
|
|
|
|
Scanner.prototype.scanUntil = function (re) { |
|
|
|
|
|
var match, pos = this.tail.search(re); |
|
|
|
|
|
|
|
|
|
|
|
switch (pos) { |
|
|
|
|
|
case -1: |
|
|
|
|
|
match = this.tail; |
|
|
|
|
|
this.pos += this.tail.length; |
|
|
|
|
|
this.tail = ""; |
|
|
|
|
|
break; |
|
|
|
|
|
case 0: |
|
|
|
|
|
match = null; |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
match = this.tail.substring(0, pos); |
|
|
|
|
|
this.tail = this.tail.substring(pos); |
|
|
|
|
|
this.pos += pos; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var names = name.split("."); |
|
|
|
|
|
var lastIndex = names.length - 1; |
|
|
|
|
|
var target = names[lastIndex]; |
|
|
|
|
|
|
|
|
return match; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
var value, context, i = stack.length, j, localStack; |
|
|
|
|
|
while (i) { |
|
|
|
|
|
localStack = stack.slice(0); |
|
|
|
|
|
context = stack[--i]; |
|
|
|
|
|
|
|
|
function Context(view, parent) { |
|
|
|
|
|
this.view = view; |
|
|
|
|
|
this.parent = parent; |
|
|
|
|
|
this.clearCache(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
j = 0; |
|
|
|
|
|
while (j < lastIndex) { |
|
|
|
|
|
context = context[names[j++]]; |
|
|
|
|
|
|
|
|
Context.make = function (view) { |
|
|
|
|
|
return (view instanceof Context) ? view : new Context(view); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
if (context == null) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Context.prototype.clearCache = function () { |
|
|
|
|
|
this._cache = {}; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
localStack.push(context); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Context.prototype.push = function (view) { |
|
|
|
|
|
return new Context(view, this); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
if (context && typeof context === "object" && target in context) { |
|
|
|
|
|
value = context[target]; |
|
|
|
|
|
break; |
|
|
|
|
|
|
|
|
Context.prototype.lookup = function (name) { |
|
|
|
|
|
var value = this._cache[name]; |
|
|
|
|
|
|
|
|
|
|
|
if (!value) { |
|
|
|
|
|
if (name === ".") { |
|
|
|
|
|
value = this.view; |
|
|
|
|
|
} else { |
|
|
|
|
|
var context = this; |
|
|
|
|
|
|
|
|
|
|
|
while (context) { |
|
|
|
|
|
if (name.indexOf(".") > 0) { |
|
|
|
|
|
var names = name.split("."), i = 0; |
|
|
|
|
|
|
|
|
|
|
|
value = context.view; |
|
|
|
|
|
|
|
|
|
|
|
while (value && i < names.length) { |
|
|
|
|
|
value = value[names[i++]]; |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
value = context.view[name]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (value != null) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
context = context.parent; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// If the value is a function, call it in the current context. |
|
|
|
|
|
if (typeof value === "function") { |
|
|
|
|
|
value = value.call(localStack[localStack.length - 1]); |
|
|
|
|
|
|
|
|
this._cache[name] = value; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (value == null) { |
|
|
|
|
|
return defaultValue; |
|
|
|
|
|
|
|
|
if (typeof value === "function") { |
|
|
|
|
|
value = value.call(this.view); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return value; |
|
|
return value; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function Renderer() { |
|
|
|
|
|
this.clearCache(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function renderSection(name, stack, callback, inverted) { |
|
|
|
|
|
var buffer = ""; |
|
|
|
|
|
var value = lookup(name, stack); |
|
|
|
|
|
|
|
|
Renderer.prototype.clearCache = function () { |
|
|
|
|
|
this._cache = {}; |
|
|
|
|
|
this._partialCache = {}; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
if (inverted) { |
|
|
|
|
|
// From the spec: inverted sections may render text once based on the |
|
|
|
|
|
// inverse value of the key. That is, they will be rendered if the key |
|
|
|
|
|
// doesn't exist, is false, or is an empty list. |
|
|
|
|
|
if (value == null || value === false || (isArray(value) && value.length === 0)) { |
|
|
|
|
|
buffer += callback(); |
|
|
|
|
|
} |
|
|
|
|
|
} else if (isArray(value)) { |
|
|
|
|
|
forEach(value, function (value) { |
|
|
|
|
|
stack.push(value); |
|
|
|
|
|
buffer += callback(); |
|
|
|
|
|
stack.pop(); |
|
|
|
|
|
}); |
|
|
|
|
|
} else if (typeof value === "object") { |
|
|
|
|
|
stack.push(value); |
|
|
|
|
|
buffer += callback(); |
|
|
|
|
|
stack.pop(); |
|
|
|
|
|
} else if (typeof value === "function") { |
|
|
|
|
|
var scope = stack[stack.length - 1]; |
|
|
|
|
|
var scopedRender = function (template) { |
|
|
|
|
|
return render(template, scope); |
|
|
|
|
|
}; |
|
|
|
|
|
buffer += value.call(scope, callback(), scopedRender) || ""; |
|
|
|
|
|
} else if (value) { |
|
|
|
|
|
buffer += callback(); |
|
|
|
|
|
|
|
|
Renderer.prototype.compile = function (tokens, tags) { |
|
|
|
|
|
var fn = compileTokens(tokens), |
|
|
|
|
|
self = this; |
|
|
|
|
|
|
|
|
|
|
|
return function (view) { |
|
|
|
|
|
return fn(Context.make(view), self); |
|
|
|
|
|
}; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Renderer.prototype.compilePartial = function (name, tokens, tags) { |
|
|
|
|
|
this._partialCache[name] = this.compile(tokens, tags); |
|
|
|
|
|
return this._partialCache[name]; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Renderer.prototype.render = function (template, view) { |
|
|
|
|
|
var fn = this._cache[template]; |
|
|
|
|
|
|
|
|
|
|
|
if (!fn) { |
|
|
|
|
|
fn = this.compile(template); |
|
|
|
|
|
this._cache[template] = fn; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return buffer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return fn(view); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Parses the given `template` and returns the source of a function that, |
|
|
|
|
|
* with the proper arguments, will render the template. Recognized options |
|
|
|
|
|
* include the following: |
|
|
|
|
|
* |
|
|
|
|
|
* - file The name of the file the template comes from (displayed in |
|
|
|
|
|
* error messages) |
|
|
|
|
|
* - tags An array of open and close tags the `template` uses. Defaults |
|
|
|
|
|
* to the value of Mustache.tags |
|
|
|
|
|
* - debug Set `true` to log the body of the generated function to the |
|
|
|
|
|
* console |
|
|
|
|
|
* - space Set `true` to preserve whitespace from lines that otherwise |
|
|
|
|
|
* contain only a {{tag}}. Defaults to `false` |
|
|
|
|
|
*/ |
|
|
|
|
|
function parse(template, options) { |
|
|
|
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
|
|
|
var tags = options.tags || exports.tags, |
|
|
|
|
|
openTag = tags[0], |
|
|
|
|
|
closeTag = tags[tags.length - 1]; |
|
|
|
|
|
|
|
|
|
|
|
var code = [ |
|
|
|
|
|
'var buffer = "";', // output buffer |
|
|
|
|
|
"\nvar line = 1;", // keep track of source line number |
|
|
|
|
|
"\ntry {", |
|
|
|
|
|
'\nbuffer += "' |
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
var spaces = [], // indices of whitespace in code on the current line |
|
|
|
|
|
hasTag = false, // is there a {{tag}} on the current line? |
|
|
|
|
|
nonSpace = false; // is there a non-space char on the current line? |
|
|
|
|
|
|
|
|
|
|
|
// Strips all space characters from the code array for the current line |
|
|
|
|
|
// if there was a {{tag}} on it and otherwise only spaces. |
|
|
|
|
|
var stripSpace = function () { |
|
|
|
|
|
if (hasTag && !nonSpace && !options.space) { |
|
|
|
|
|
while (spaces.length) { |
|
|
|
|
|
code.splice(spaces.pop(), 1); |
|
|
|
|
|
|
|
|
Renderer.prototype._section = function (name, context, callback) { |
|
|
|
|
|
var value = context.lookup(name); |
|
|
|
|
|
|
|
|
|
|
|
switch (typeof value) { |
|
|
|
|
|
case "object": |
|
|
|
|
|
if (isArray(value)) { |
|
|
|
|
|
var buffer = ""; |
|
|
|
|
|
for (var i = 0, len = value.length; i < len; ++i) { |
|
|
|
|
|
buffer += callback(context.push(value[i]), this); |
|
|
} |
|
|
} |
|
|
|
|
|
return buffer; |
|
|
} else { |
|
|
} else { |
|
|
spaces = []; |
|
|
|
|
|
|
|
|
return callback(context.push(value), this); |
|
|
|
|
|
} |
|
|
|
|
|
break; |
|
|
|
|
|
case "function": |
|
|
|
|
|
var sectionText = callback(context, this), self = this; |
|
|
|
|
|
function scopedRender(template) { |
|
|
|
|
|
return self.render(template, context); |
|
|
} |
|
|
} |
|
|
|
|
|
return value.call(context.view, sectionText, scopedRender) || ""; |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
if (value) { |
|
|
|
|
|
return callback(context, this); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
hasTag = false; |
|
|
|
|
|
nonSpace = false; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
return ""; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
var sectionStack = [], updateLine, nextOpenTag, nextCloseTag; |
|
|
|
|
|
|
|
|
Renderer.prototype._inverted = function (name, context, callback) { |
|
|
|
|
|
var value = context.lookup(name); |
|
|
|
|
|
|
|
|
var setTags = function (source) { |
|
|
|
|
|
tags = trim(source).split(/\s+/); |
|
|
|
|
|
nextOpenTag = tags[0]; |
|
|
|
|
|
nextCloseTag = tags[tags.length - 1]; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// From the spec: inverted sections may render text once based on the |
|
|
|
|
|
// inverse value of the key. That is, they will be rendered if the key |
|
|
|
|
|
// doesn't exist, is false, or is an empty list. |
|
|
|
|
|
if (value == null || value === false || (isArray(value) && value.length === 0)) { |
|
|
|
|
|
return callback(context, this); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var includePartial = function (source) { |
|
|
|
|
|
code.push( |
|
|
|
|
|
'";', |
|
|
|
|
|
updateLine, |
|
|
|
|
|
'\nvar partial = partials["' + trim(source) + '"];', |
|
|
|
|
|
'\nif (partial) {', |
|
|
|
|
|
'\n buffer += render(partial,stack[stack.length - 1],partials);', |
|
|
|
|
|
'\n}', |
|
|
|
|
|
'\nbuffer += "' |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
return ""; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
var openSection = function (source, inverted) { |
|
|
|
|
|
var name = trim(source); |
|
|
|
|
|
|
|
|
Renderer.prototype._partial = function (name, context) { |
|
|
|
|
|
var fn = this._partialCache[name]; |
|
|
|
|
|
|
|
|
if (name === "") { |
|
|
|
|
|
throw debug(new Error("Section name may not be empty"), template, line, options.file); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (fn) { |
|
|
|
|
|
return fn(context, this); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
sectionStack.push({name: name, inverted: inverted}); |
|
|
|
|
|
|
|
|
|
|
|
code.push( |
|
|
|
|
|
'";', |
|
|
|
|
|
updateLine, |
|
|
|
|
|
'\nvar name = "' + name + '";', |
|
|
|
|
|
'\nvar callback = (function () {', |
|
|
|
|
|
'\n return function () {', |
|
|
|
|
|
'\n var buffer = "";', |
|
|
|
|
|
'\nbuffer += "' |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
return ""; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
var openInvertedSection = function (source) { |
|
|
|
|
|
openSection(source, true); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
Renderer.prototype._name = function (name, context, escape) { |
|
|
|
|
|
var value = context.lookup(name); |
|
|
|
|
|
|
|
|
var closeSection = function (source) { |
|
|
|
|
|
var name = trim(source); |
|
|
|
|
|
var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name; |
|
|
|
|
|
|
|
|
if (typeof value === "function") { |
|
|
|
|
|
value = value.call(context.view); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (!openName || name != openName) { |
|
|
|
|
|
throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var string = (value == null) ? "" : String(value); |
|
|
|
|
|
|
|
|
var section = sectionStack.pop(); |
|
|
|
|
|
|
|
|
if (escape) { |
|
|
|
|
|
return escapeHtml(string); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
code.push( |
|
|
|
|
|
'";', |
|
|
|
|
|
'\n return buffer;', |
|
|
|
|
|
'\n };', |
|
|
|
|
|
'\n})();' |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
return string; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
if (section.inverted) { |
|
|
|
|
|
code.push("\nbuffer += renderSection(name,stack,callback,true);"); |
|
|
|
|
|
} else { |
|
|
|
|
|
code.push("\nbuffer += renderSection(name,stack,callback);"); |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Low-level function that compiles the given `tokens` into a |
|
|
|
|
|
* function that accepts two arguments: a Context and a |
|
|
|
|
|
* Renderer. Returns the body of the function as a string if |
|
|
|
|
|
* `returnBody` is true. |
|
|
|
|
|
*/ |
|
|
|
|
|
function compileTokens(tokens, returnBody) { |
|
|
|
|
|
if (typeof tokens === "string") { |
|
|
|
|
|
tokens = parse(tokens); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var body = ['""']; |
|
|
|
|
|
var token, method, escape; |
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0, len = tokens.length; i < len; ++i) { |
|
|
|
|
|
token = tokens[i]; |
|
|
|
|
|
|
|
|
|
|
|
switch (token.type) { |
|
|
|
|
|
case "#": |
|
|
|
|
|
case "^": |
|
|
|
|
|
method = (token.type === "#") ? "_section" : "_inverted"; |
|
|
|
|
|
body.push("r." + method + "(" + quote(token.value) + ", c, function (c, r) {\n" + |
|
|
|
|
|
" " + compileTokens(token.tokens, true) + "\n" + |
|
|
|
|
|
"})"); |
|
|
|
|
|
break; |
|
|
|
|
|
case "{": |
|
|
|
|
|
case "&": |
|
|
|
|
|
case "name": |
|
|
|
|
|
escape = token.type === "name" ? "true" : "false"; |
|
|
|
|
|
body.push("r._name(" + quote(token.value) + ", c, " + escape + ")"); |
|
|
|
|
|
break; |
|
|
|
|
|
case ">": |
|
|
|
|
|
body.push("r._partial(" + quote(token.value) + ", c)"); |
|
|
|
|
|
break; |
|
|
|
|
|
case "text": |
|
|
|
|
|
body.push(quote(token.value)); |
|
|
|
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
code.push('\nbuffer += "'); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// Convert to a string body. |
|
|
|
|
|
body = "return " + body.join(" + ") + ";"; |
|
|
|
|
|
|
|
|
var sendPlain = function (source) { |
|
|
|
|
|
code.push( |
|
|
|
|
|
'";', |
|
|
|
|
|
updateLine, |
|
|
|
|
|
'\nbuffer += lookup("' + trim(source) + '",stack,"");', |
|
|
|
|
|
'\nbuffer += "' |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// Good for debugging. |
|
|
|
|
|
// console.log(body); |
|
|
|
|
|
|
|
|
var sendEscaped = function (source) { |
|
|
|
|
|
code.push( |
|
|
|
|
|
'";', |
|
|
|
|
|
updateLine, |
|
|
|
|
|
'\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));', |
|
|
|
|
|
'\nbuffer += "' |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
if (returnBody) { |
|
|
|
|
|
return body; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var line = 1, c, callback; |
|
|
|
|
|
for (var i = 0, len = template.length; i < len; ++i) { |
|
|
|
|
|
if (template.slice(i, i + openTag.length) === openTag) { |
|
|
|
|
|
i += openTag.length; |
|
|
|
|
|
c = template.substr(i, 1); |
|
|
|
|
|
updateLine = '\nline = ' + line + ';'; |
|
|
|
|
|
nextOpenTag = openTag; |
|
|
|
|
|
nextCloseTag = closeTag; |
|
|
|
|
|
hasTag = true; |
|
|
|
|
|
|
|
|
|
|
|
switch (c) { |
|
|
|
|
|
case "!": // comment |
|
|
|
|
|
i++; |
|
|
|
|
|
callback = null; |
|
|
|
|
|
break; |
|
|
|
|
|
case "=": // change open/close tags, e.g. {{=<% %>=}} |
|
|
|
|
|
i++; |
|
|
|
|
|
closeTag = "=" + closeTag; |
|
|
|
|
|
callback = setTags; |
|
|
|
|
|
break; |
|
|
|
|
|
case ">": // include partial |
|
|
|
|
|
i++; |
|
|
|
|
|
callback = includePartial; |
|
|
|
|
|
break; |
|
|
|
|
|
case "#": // start section |
|
|
|
|
|
i++; |
|
|
|
|
|
callback = openSection; |
|
|
|
|
|
break; |
|
|
|
|
|
case "^": // start inverted section |
|
|
|
|
|
i++; |
|
|
|
|
|
callback = openInvertedSection; |
|
|
|
|
|
break; |
|
|
|
|
|
case "/": // end section |
|
|
|
|
|
i++; |
|
|
|
|
|
callback = closeSection; |
|
|
|
|
|
break; |
|
|
|
|
|
case "{": // plain variable |
|
|
|
|
|
closeTag = "}" + closeTag; |
|
|
|
|
|
// fall through |
|
|
|
|
|
case "&": // plain variable |
|
|
|
|
|
i++; |
|
|
|
|
|
nonSpace = true; |
|
|
|
|
|
callback = sendPlain; |
|
|
|
|
|
break; |
|
|
|
|
|
default: // escaped variable |
|
|
|
|
|
nonSpace = true; |
|
|
|
|
|
callback = sendEscaped; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// For great evil! |
|
|
|
|
|
return new Function("c, r", body); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var end = template.indexOf(closeTag, i); |
|
|
|
|
|
|
|
|
function escapeTags(tags) { |
|
|
|
|
|
if (tags.length === 2) { |
|
|
|
|
|
return [ |
|
|
|
|
|
new RegExp(escapeRe(tags[0]) + "\\s*"), |
|
|
|
|
|
new RegExp("\\s*" + escapeRe(tags[1])) |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (end === -1) { |
|
|
|
|
|
throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file); |
|
|
|
|
|
|
|
|
throw new Error("Invalid tags: " + tags.join(" ")); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Forms the given linear array of `tokens` into a nested tree structure |
|
|
|
|
|
* where tokens that represent a section have a "tokens" array property |
|
|
|
|
|
* that contains all tokens that are in that section. |
|
|
|
|
|
*/ |
|
|
|
|
|
function nestTokens(tokens) { |
|
|
|
|
|
var tree = []; |
|
|
|
|
|
var collector = tree; |
|
|
|
|
|
var sections = []; |
|
|
|
|
|
var token, section; |
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < tokens.length; ++i) { |
|
|
|
|
|
token = tokens[i]; |
|
|
|
|
|
|
|
|
|
|
|
switch (token.type) { |
|
|
|
|
|
case "#": |
|
|
|
|
|
case "^": |
|
|
|
|
|
token.tokens = []; |
|
|
|
|
|
sections.push(token); |
|
|
|
|
|
collector.push(token); |
|
|
|
|
|
collector = token.tokens; |
|
|
|
|
|
break; |
|
|
|
|
|
case "/": |
|
|
|
|
|
if (sections.length === 0) { |
|
|
|
|
|
throw new Error("Unopened section: " + token.value); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var source = template.substring(i, end); |
|
|
|
|
|
|
|
|
section = sections.pop(); |
|
|
|
|
|
|
|
|
if (callback) { |
|
|
|
|
|
callback(source); |
|
|
|
|
|
|
|
|
if (section.value !== token.value) { |
|
|
|
|
|
throw new Error("Unclosed section: " + section.value); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Maintain line count for \n in source. |
|
|
|
|
|
var n = 0; |
|
|
|
|
|
while (~(n = source.indexOf("\n", n))) { |
|
|
|
|
|
line++; |
|
|
|
|
|
n++; |
|
|
|
|
|
|
|
|
if (sections.length > 0) { |
|
|
|
|
|
collector = sections[sections.length - 1].tokens; |
|
|
|
|
|
} else { |
|
|
|
|
|
collector = tree; |
|
|
} |
|
|
} |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
collector.push(token); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Make sure there were no open sections when we're done. |
|
|
|
|
|
section = sections.pop(); |
|
|
|
|
|
|
|
|
|
|
|
if (section) { |
|
|
|
|
|
throw new Error("Unclosed section: " + section.value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return tree; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Combines the values of consecutive text tokens in the given `tokens` array |
|
|
|
|
|
* to a single token. |
|
|
|
|
|
*/ |
|
|
|
|
|
function squashTokens(tokens) { |
|
|
|
|
|
var lastToken; |
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < tokens.length; ++i) { |
|
|
|
|
|
token = tokens[i]; |
|
|
|
|
|
|
|
|
|
|
|
if (lastToken && lastToken.type === "text" && token.type === "text") { |
|
|
|
|
|
lastToken.value += token.value; |
|
|
|
|
|
tokens.splice(i--, 1); // Remove this token from the array. |
|
|
|
|
|
} else { |
|
|
|
|
|
lastToken = token; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
i = end + closeTag.length - 1; |
|
|
|
|
|
openTag = nextOpenTag; |
|
|
|
|
|
closeTag = nextCloseTag; |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Breaks up the given `template` string into a tree of token objects. If |
|
|
|
|
|
* `tags` is given here it must be an array with two string values: the |
|
|
|
|
|
* opening and closing tags used in the template (e.g. ["<%", "%>"]). Of |
|
|
|
|
|
* course, the default is to use mustaches (i.e. Mustache.tags). |
|
|
|
|
|
*/ |
|
|
|
|
|
function parse(template, tags) { |
|
|
|
|
|
tags = escapeTags(tags || exports.tags); |
|
|
|
|
|
|
|
|
|
|
|
var scanner = new Scanner(template); |
|
|
|
|
|
|
|
|
|
|
|
var tokens = [], // Buffer to hold the tokens |
|
|
|
|
|
spaces = [], // Indices of whitespace tokens on the current line |
|
|
|
|
|
hasTag = false, // Is there a {{tag}} on the current line? |
|
|
|
|
|
nonSpace = false; // Is there a non-space char on the current line? |
|
|
|
|
|
|
|
|
|
|
|
// Strips all whitespace tokens array for the current line |
|
|
|
|
|
// if there was a {{#tag}} on it and otherwise only space. |
|
|
|
|
|
function stripSpace() { |
|
|
|
|
|
if (hasTag && !nonSpace) { |
|
|
|
|
|
while (spaces.length) { |
|
|
|
|
|
tokens.splice(spaces.pop(), 1); |
|
|
|
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
c = template.substr(i, 1); |
|
|
|
|
|
|
|
|
|
|
|
switch (c) { |
|
|
|
|
|
case '"': |
|
|
|
|
|
case "\\": |
|
|
|
|
|
nonSpace = true; |
|
|
|
|
|
code.push("\\" + c); |
|
|
|
|
|
break; |
|
|
|
|
|
case "\r": |
|
|
|
|
|
// Ignore carriage returns. |
|
|
|
|
|
break; |
|
|
|
|
|
case "\n": |
|
|
|
|
|
spaces.push(code.length); |
|
|
|
|
|
code.push("\\n"); |
|
|
|
|
|
stripSpace(); // Check for whitespace on the current line. |
|
|
|
|
|
line++; |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
if (isWhitespace(c)) { |
|
|
|
|
|
spaces.push(code.length); |
|
|
|
|
|
|
|
|
spaces = []; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
hasTag = false; |
|
|
|
|
|
nonSpace = false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var type, value, chr; |
|
|
|
|
|
|
|
|
|
|
|
while (!scanner.eos()) { |
|
|
|
|
|
value = scanner.scanUntil(tags[0]); |
|
|
|
|
|
|
|
|
|
|
|
if (value) { |
|
|
|
|
|
for (var i = 0, len = value.length; i < len; ++i) { |
|
|
|
|
|
chr = value[i]; |
|
|
|
|
|
|
|
|
|
|
|
if (isWhitespace(chr)) { |
|
|
|
|
|
spaces.push(tokens.length); |
|
|
} else { |
|
|
} else { |
|
|
nonSpace = true; |
|
|
nonSpace = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
code.push(c); |
|
|
|
|
|
|
|
|
tokens.push({type: "text", value: chr}); |
|
|
|
|
|
|
|
|
|
|
|
if (chr === "\n") { |
|
|
|
|
|
stripSpace(); // Check for whitespace on the current line. |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (sectionStack.length != 0) { |
|
|
|
|
|
throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Match the opening tag. |
|
|
|
|
|
if (!scanner.scan(tags[0])) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
hasTag = true; |
|
|
|
|
|
type = scanner.scan(tagRe) || "name"; |
|
|
|
|
|
|
|
|
|
|
|
// Skip any whitespace between tag and value. |
|
|
|
|
|
scanner.scan(whiteRe); |
|
|
|
|
|
|
|
|
|
|
|
// Extract the tag value. |
|
|
|
|
|
if (type === "=") { |
|
|
|
|
|
value = scanner.scanUntil(eqRe); |
|
|
|
|
|
scanner.scan(eqRe); |
|
|
|
|
|
scanner.scanUntil(tags[1]); |
|
|
|
|
|
} else if (type === "{") { |
|
|
|
|
|
value = scanner.scanUntil(curlyRe); |
|
|
|
|
|
scanner.scan(curlyRe); |
|
|
|
|
|
scanner.scanUntil(tags[1]); |
|
|
|
|
|
} else { |
|
|
|
|
|
value = scanner.scanUntil(tags[1]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Clean up any whitespace from a closing {{tag}} that was at the end |
|
|
|
|
|
// of the template without a trailing \n. |
|
|
|
|
|
stripSpace(); |
|
|
|
|
|
|
|
|
// Match the closing tag. |
|
|
|
|
|
if (!scanner.scan(tags[1])) { |
|
|
|
|
|
throw new Error("Unclosed tag at " + scanner.pos); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
code.push( |
|
|
|
|
|
'";', |
|
|
|
|
|
"\nreturn buffer;", |
|
|
|
|
|
"\n} catch (e) { throw {error: e, line: line}; }" |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
tokens.push({type: type, value: value}); |
|
|
|
|
|
|
|
|
// Ignore `buffer += "";` statements. |
|
|
|
|
|
var body = code.join("").replace(/buffer \+= "";\n/g, ""); |
|
|
|
|
|
|
|
|
if (type === "name" || type === "{" || type === "&") { |
|
|
|
|
|
nonSpace = true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (options.debug) { |
|
|
|
|
|
if (typeof console != "undefined" && console.log) { |
|
|
|
|
|
console.log(body); |
|
|
|
|
|
} else if (typeof print === "function") { |
|
|
|
|
|
print(body); |
|
|
|
|
|
|
|
|
// Set the tags for the next time around. |
|
|
|
|
|
if (type === "=") { |
|
|
|
|
|
tags = escapeTags(value.split(spaceRe)); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return body; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
squashTokens(tokens); |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Used by `compile` to generate a reusable function for the given `template`. |
|
|
|
|
|
*/ |
|
|
|
|
|
function _compile(template, options) { |
|
|
|
|
|
var args = "view,partials,stack,lookup,escapeHTML,renderSection,render"; |
|
|
|
|
|
var body = parse(template, options); |
|
|
|
|
|
var fn = new Function(args, body); |
|
|
|
|
|
|
|
|
|
|
|
// This anonymous function wraps the generated function so we can do |
|
|
|
|
|
// argument coercion, setup some variables, and handle any errors |
|
|
|
|
|
// encountered while executing it. |
|
|
|
|
|
return function (view, partials) { |
|
|
|
|
|
partials = partials || {}; |
|
|
|
|
|
|
|
|
|
|
|
var stack = [view]; // context stack |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
return fn(view, partials, stack, lookup, escapeHTML, renderSection, render); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
throw debug(e.error, template, e.line, options.file); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
return nestTokens(tokens); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Cache of pre-compiled templates. |
|
|
|
|
|
var _cache = {}; |
|
|
|
|
|
|
|
|
// The high-level clearCache, compile, compilePartial, and render functions |
|
|
|
|
|
// use this default renderer. |
|
|
|
|
|
var _renderer = new Renderer; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Clear the cache of compiled templates. |
|
|
|
|
|
|
|
|
* Clears all cached templates and partials. |
|
|
*/ |
|
|
*/ |
|
|
function clearCache() { |
|
|
function clearCache() { |
|
|
_cache = {}; |
|
|
|
|
|
|
|
|
_renderer.clearCache(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Compiles the given `template` into a reusable function using the given |
|
|
|
|
|
* `options`. In addition to the options accepted by Mustache.parse, |
|
|
|
|
|
* recognized options include the following: |
|
|
|
|
|
* |
|
|
|
|
|
* - cache Set `false` to bypass any pre-compiled version of the given |
|
|
|
|
|
* template. Otherwise, a given `template` string will be cached |
|
|
|
|
|
* the first time it is parsed |
|
|
|
|
|
|
|
|
* High-level API for compiling the given `tokens` down to a reusable |
|
|
|
|
|
* function. If `tokens` is a string it will be parsed using the given `tags` |
|
|
|
|
|
* before it is compiled. |
|
|
*/ |
|
|
*/ |
|
|
function compile(template, options) { |
|
|
|
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
|
|
|
// Use a pre-compiled version from the cache if we have one. |
|
|
|
|
|
if (options.cache !== false) { |
|
|
|
|
|
if (!_cache[template]) { |
|
|
|
|
|
_cache[template] = _compile(template, options); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return _cache[template]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
function compile(tokens, tags) { |
|
|
|
|
|
return _renderer.compile(tokens, tags); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return _compile(template, options); |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* High-level API for compiling the `tokens` for the partial with the given |
|
|
|
|
|
* `name` down to a reusable function. If `tokens` is a string it will be |
|
|
|
|
|
* parsed using the given `tags` before it is compiled. |
|
|
|
|
|
*/ |
|
|
|
|
|
function compilePartial(name, tokens, tags) { |
|
|
|
|
|
return _renderer.compilePartial(name, tokens, tags); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* High-level function that renders the given `template` using the given |
|
|
|
|
|
* `view` and `partials`. If you need to use any of the template options (see |
|
|
|
|
|
* `compile` above), you must compile in a separate step, and then call that |
|
|
|
|
|
* compiled function. |
|
|
|
|
|
|
|
|
* High-level API for rendering the `template` using the given `view`. The |
|
|
|
|
|
* optional `partials` object may be given here for convenience, but note that |
|
|
|
|
|
* it will cause all partials to be re-compiled, thus hurting performance. Of |
|
|
|
|
|
* course, this only matters if you're going to render the same template more |
|
|
|
|
|
* than once. If so, it is best to call `compilePartial` before calling this |
|
|
|
|
|
* function and to leave the `partials` argument blank. |
|
|
*/ |
|
|
*/ |
|
|
function render(template, view, partials) { |
|
|
function render(template, view, partials) { |
|
|
return compile(template)(view, partials); |
|
|
|
|
|
|
|
|
if (partials) { |
|
|
|
|
|
for (var name in partials) { |
|
|
|
|
|
compilePartial(name, partials[name]); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return _renderer.render(template, view); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
})(Mustache); |
|
|
})(Mustache); |