d0fd93b1b97a98ecd15ec72dd5e8fe45faf03b87
2 Shameless port of a shameless port
3 @defunkt => @janl => @aq
5 See http://github.com/defunkt/mustache for more info.
11 * mustache.js - Logic-less {{mustache}} templates with JavaScript
12 * http://github.com/janl/mustache.js
14 var Mustache
= (typeof module
!== "undefined" && module
.exports
) || {};
18 exports
.name
= "mustache.js";
19 exports
.version
= "0.5.0-dev";
20 exports
.tags
= ["{{", "}}"];
21 exports
.parse
= parse
;
22 exports
.compile
= compile
;
23 exports
.render
= render
;
24 exports
.clearCache
= clearCache
;
26 // This is here for backwards compatibility with 0.4.x.
27 exports
.to_html = function (template
, view
, partials
, send
) {
28 var result
= render(template
, view
, partials
);
30 if (typeof send
=== "function") {
37 var _toString
= Object
.prototype.toString
;
38 var _isArray
= Array
.isArray
;
39 var _forEach
= Array
.prototype.forEach
;
40 var _trim
= String
.prototype.trim
;
46 isArray = function (obj
) {
47 return _toString
.call(obj
) === "[object Array]";
53 forEach = function (obj
, callback
, scope
) {
54 return _forEach
.call(obj
, callback
, scope
);
57 forEach = function (obj
, callback
, scope
) {
58 for (var i
= 0, len
= obj
.length
; i
< len
; ++i
) {
59 callback
.call(scope
, obj
[i
], i
, obj
);
64 var spaceRe
= /^\s*$/;
66 function isWhitespace(string
) {
67 return spaceRe
.test(string
);
72 trim = function (string
) {
73 return string
== null ? "" : _trim
.call(string
);
76 var trimLeft
, trimRight
;
78 if (isWhitespace("\xA0")) {
82 // IE doesn't match non-breaking spaces with \s, thanks jQuery.
83 trimLeft
= /^[\s\xA0]+/;
84 trimRight
= /[\s\xA0]+$/;
87 trim = function (string
) {
88 return string
== null ? "" :
89 String(string
).replace(trimLeft
, "").replace(trimRight
, "");
101 function escapeHTML(string
) {
102 return String(string
).replace(/&(?!\w+;)|[<>"']/g, function (s
) {
103 return escapeMap
[s
] || s
;
108 * Adds the `template`, `line`, and `file` properties to the given error
109 * object and alters the message to provide more useful debugging information.
111 function debug(e
, template
, line
, file
) {
112 file
= file
|| "<template>";
114 var lines
= template
.split("\n"),
115 start
= Math
.max(line
- 3, 0),
116 end
= Math
.min(lines
.length
, line
+ 3),
117 context
= lines
.slice(start
, end
);
120 for (var i
= 0, len
= context
.length
; i
< len
; ++i
) {
122 context
[i
] = (c
=== line
? " >> " : " ") + context
[i
];
125 e
.template
= template
;
128 e
.message
= [file
+ ":" + line
, context
.join("\n"), "", e
.message
].join("\n");
134 * Looks up the value of the given `name` in the given context `stack`.
136 function lookup(name
, stack
, defaultValue
) {
138 return stack
[stack
.length
- 1];
141 var names
= name
.split(".");
142 var lastIndex
= names
.length
- 1;
143 var target
= names
[lastIndex
];
145 var value
, context
, i
= stack
.length
, j
, localStack
;
147 localStack
= stack
.slice(0);
148 context
= stack
[--i
];
151 while (j
< lastIndex
) {
152 context
= context
[names
[j
++]];
154 if (context
== null) {
158 localStack
.push(context
);
161 if (context
&& typeof context
=== "object" && target
in context
) {
162 value
= context
[target
];
167 // If the value is a function, call it in the current context.
168 if (typeof value
=== "function") {
169 value
= value
.call(localStack
[localStack
.length
- 1]);
179 function renderSection(name
, stack
, callback
, inverted
) {
181 var value
= lookup(name
, stack
);
184 // From the spec: inverted sections may render text once based on the
185 // inverse value of the key. That is, they will be rendered if the key
186 // doesn't exist, is false, or is an empty list.
187 if (value
== null || value
=== false || (isArray(value
) && value
.length
=== 0)) {
188 buffer
+= callback();
190 } else if (isArray(value
)) {
191 forEach(value
, function (value
) {
193 buffer
+= callback();
196 } else if (typeof value
=== "object") {
198 buffer
+= callback();
200 } else if (typeof value
=== "function") {
201 var scope
= stack
[stack
.length
- 1];
202 var scopedRender = function (template
) {
203 return render(template
, scope
);
205 buffer
+= value
.call(scope
, callback(), scopedRender
) || "";
207 buffer
+= callback();
214 * Parses the given `template` and returns the source of a function that,
215 * with the proper arguments, will render the template. Recognized options
216 * include the following:
218 * - file The name of the file the template comes from (displayed in
220 * - tags An array of open and close tags the `template` uses. Defaults
221 * to the value of Mustache.tags
222 * - debug Set `true` to log the body of the generated function to the
224 * - space Set `true` to preserve whitespace from lines that otherwise
225 * contain only a {{tag}}. Defaults to `false`
227 function parse(template
, options
) {
228 options
= options
|| {};
230 var tags
= options
.tags
|| exports
.tags
,
232 closeTag
= tags
[tags
.length
- 1];
235 'var buffer = "";', // output buffer
236 "\nvar line = 1;", // keep track of source line number
241 var spaces
= [], // indices of whitespace in code on the current line
242 hasTag
= false, // is there a {{tag}} on the current line?
243 nonSpace
= false; // is there a non-space char on the current line?
245 // Strips all space characters from the code array for the current line
246 // if there was a {{tag}} on it and otherwise only spaces.
247 var stripSpace = function () {
248 if (hasTag
&& !nonSpace
&& !options
.space
) {
249 while (spaces
.length
) {
250 code
.splice(spaces
.pop(), 1);
260 var sectionStack
= [], updateLine
, nextOpenTag
, nextCloseTag
;
262 var setTags = function (source
) {
263 tags
= trim(source
).split(/\s+/);
264 nextOpenTag
= tags
[0];
265 nextCloseTag
= tags
[tags
.length
- 1];
268 var includePartial = function (source
) {
272 '\nvar partial = partials["' + trim(source
) + '"];',
274 '\n buffer += render(partial,stack[stack.length - 1],partials);',
280 var openSection = function (source
, inverted
) {
281 var name
= trim(source
);
284 throw debug(new Error("Section name may not be empty"), template
, line
, options
.file
);
287 sectionStack
.push({name
: name
, inverted
: inverted
});
292 '\nvar name = "' + name
+ '";',
293 '\nvar callback = (function () {',
294 '\n return function () {',
295 '\n var buffer = "";',
300 var openInvertedSection = function (source
) {
301 openSection(source
, true);
304 var closeSection = function (source
) {
305 var name
= trim(source
);
306 var openName
= sectionStack
.length
!= 0 && sectionStack
[sectionStack
.length
- 1].name
;
308 if (!openName
|| name
!= openName
) {
309 throw debug(new Error('Section named "' + name
+ '" was never opened'), template
, line
, options
.file
);
312 var section
= sectionStack
.pop();
321 if (section
.inverted
) {
322 code
.push("\nbuffer += renderSection(name,stack,callback,true);");
324 code
.push("\nbuffer += renderSection(name,stack,callback);");
327 code
.push('\nbuffer += "');
330 var sendPlain = function (source
) {
334 '\nbuffer += lookup("' + trim(source
) + '",stack,"");',
339 var sendEscaped = function (source
) {
343 '\nbuffer += escapeHTML(lookup("' + trim(source
) + '",stack,""));',
348 var line
= 1, c
, callback
;
349 for (var i
= 0, len
= template
.length
; i
< len
; ++i
) {
350 if (template
.slice(i
, i
+ openTag
.length
) === openTag
) {
352 c
= template
.substr(i
, 1);
353 updateLine
= '\nline = ' + line
+ ';';
354 nextOpenTag
= openTag
;
355 nextCloseTag
= closeTag
;
363 case "=": // change open/close tags, e.g. {{=<% %>=}}
365 closeTag
= "=" + closeTag
;
368 case ">": // include partial
370 callback
= includePartial
;
372 case "#": // start section
374 callback
= openSection
;
376 case "^": // start inverted section
378 callback
= openInvertedSection
;
380 case "/": // end section
382 callback
= closeSection
;
384 case "{": // plain variable
385 closeTag
= "}" + closeTag
;
387 case "&": // plain variable
390 callback
= sendPlain
;
392 default: // escaped variable
394 callback
= sendEscaped
;
397 var end
= template
.indexOf(closeTag
, i
);
400 throw debug(new Error('Tag "' + openTag
+ '" was not closed properly'), template
, line
, options
.file
);
403 var source
= template
.substring(i
, end
);
409 // Maintain line count for \n in source.
411 while (~(n
= source
.indexOf("\n", n
))) {
416 i
= end
+ closeTag
.length
- 1;
417 openTag
= nextOpenTag
;
418 closeTag
= nextCloseTag
;
420 c
= template
.substr(i
, 1);
429 // Ignore carriage returns.
432 spaces
.push(code
.length
);
434 stripSpace(); // Check for whitespace on the current line.
438 if (isWhitespace(c
)) {
439 spaces
.push(code
.length
);
449 if (sectionStack
.length
!= 0) {
450 throw debug(new Error('Section "' + sectionStack
[sectionStack
.length
- 1].name
+ '" was not closed properly'), template
, line
, options
.file
);
453 // Clean up any whitespace from a closing {{tag}} that was at the end
454 // of the template without a trailing \n.
460 "\n} catch (e) { throw {error: e, line: line}; }"
463 // Ignore `buffer += "";` statements.
464 var body
= code
.join("").replace(/buffer \+= "";\n/g, "");
467 if (typeof console
!= "undefined" && console
.log
) {
469 } else if (typeof print
=== "function") {
478 * Used by `compile` to generate a reusable function for the given `template`.
480 function _compile(template
, options
) {
481 var args
= "view,partials,stack,lookup,escapeHTML,renderSection,render";
482 var body
= parse(template
, options
);
483 var fn
= new Function(args
, body
);
485 // This anonymous function wraps the generated function so we can do
486 // argument coercion, setup some variables, and handle any errors
487 // encountered while executing it.
488 return function (view
, partials
) {
489 partials
= partials
|| {};
491 var stack
= [view
]; // context stack
494 return fn(view
, partials
, stack
, lookup
, escapeHTML
, renderSection
, render
);
496 throw debug(e
.error
, template
, e
.line
, options
.file
);
501 // Cache of pre-compiled templates.
505 * Clear the cache of compiled templates.
507 function clearCache() {
512 * Compiles the given `template` into a reusable function using the given
513 * `options`. In addition to the options accepted by Mustache.parse,
514 * recognized options include the following:
516 * - cache Set `false` to bypass any pre-compiled version of the given
517 * template. Otherwise, a given `template` string will be cached
518 * the first time it is parsed
520 function compile(template
, options
) {
521 options
= options
|| {};
523 // Use a pre-compiled version from the cache if we have one.
524 if (options
.cache
!== false) {
525 if (!_cache
[template
]) {
526 _cache
[template
] = _compile(template
, options
);
529 return _cache
[template
];
532 return _compile(template
, options
);
536 * High-level function that renders the given `template` using the given
537 * `view` and `partials`. If you need to use any of the template options (see
538 * `compile` above), you must compile in a separate step, and then call that
541 function render(template
, view
, partials
) {
542 return compile(template
)(view
, partials
);
547 $.mustache = function (template
, view
, partials
) {
548 return Mustache
.render(template
, view
, partials
);
551 $.fn
.mustache = function (view
, partials
) {
552 return $(this).map(function (i
, elm
) {
553 var template
= $(elm
).html().trim();
554 var output
= $.mustache(template
, view
, partials
);
555 return $(output
).get();