d0fd93b1b97a98ecd15ec72dd5e8fe45faf03b87
[weblabels.fsf.org.git] / defectivebydesign.org / 20130322 / files-new-min / crm.fsf.org / jquery.mustache.js
1 /*
2 Shameless port of a shameless port
3 @defunkt => @janl => @aq
4
5 See http://github.com/defunkt/mustache for more info.
6 */
7
8 ;(function($) {
9
10 /*!
11 * mustache.js - Logic-less {{mustache}} templates with JavaScript
12 * http://github.com/janl/mustache.js
13 */
14 var Mustache = (typeof module !== "undefined" && module.exports) || {};
15
16 (function (exports) {
17
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;
25
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);
29
30 if (typeof send === "function") {
31 send(result);
32 } else {
33 return result;
34 }
35 };
36
37 var _toString = Object.prototype.toString;
38 var _isArray = Array.isArray;
39 var _forEach = Array.prototype.forEach;
40 var _trim = String.prototype.trim;
41
42 var isArray;
43 if (_isArray) {
44 isArray = _isArray;
45 } else {
46 isArray = function (obj) {
47 return _toString.call(obj) === "[object Array]";
48 };
49 }
50
51 var forEach;
52 if (_forEach) {
53 forEach = function (obj, callback, scope) {
54 return _forEach.call(obj, callback, scope);
55 };
56 } else {
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);
60 }
61 };
62 }
63
64 var spaceRe = /^\s*$/;
65
66 function isWhitespace(string) {
67 return spaceRe.test(string);
68 }
69
70 var trim;
71 if (_trim) {
72 trim = function (string) {
73 return string == null ? "" : _trim.call(string);
74 };
75 } else {
76 var trimLeft, trimRight;
77
78 if (isWhitespace("\xA0")) {
79 trimLeft = /^\s+/;
80 trimRight = /\s+$/;
81 } else {
82 // IE doesn't match non-breaking spaces with \s, thanks jQuery.
83 trimLeft = /^[\s\xA0]+/;
84 trimRight = /[\s\xA0]+$/;
85 }
86
87 trim = function (string) {
88 return string == null ? "" :
89 String(string).replace(trimLeft, "").replace(trimRight, "");
90 };
91 }
92
93 var escapeMap = {
94 "&": "&amp;",
95 "<": "&lt;",
96 ">": "&gt;",
97 '"': '&quot;',
98 "'": '&#39;'
99 };
100
101 function escapeHTML(string) {
102 return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
103 return escapeMap[s] || s;
104 });
105 }
106
107 /**
108 * Adds the `template`, `line`, and `file` properties to the given error
109 * object and alters the message to provide more useful debugging information.
110 */
111 function debug(e, template, line, file) {
112 file = file || "<template>";
113
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);
118
119 var c;
120 for (var i = 0, len = context.length; i < len; ++i) {
121 c = i + start + 1;
122 context[i] = (c === line ? " >> " : " ") + context[i];
123 }
124
125 e.template = template;
126 e.line = line;
127 e.file = file;
128 e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
129
130 return e;
131 }
132
133 /**
134 * Looks up the value of the given `name` in the given context `stack`.
135 */
136 function lookup(name, stack, defaultValue) {
137 if (name === ".") {
138 return stack[stack.length - 1];
139 }
140
141 var names = name.split(".");
142 var lastIndex = names.length - 1;
143 var target = names[lastIndex];
144
145 var value, context, i = stack.length, j, localStack;
146 while (i) {
147 localStack = stack.slice(0);
148 context = stack[--i];
149
150 j = 0;
151 while (j < lastIndex) {
152 context = context[names[j++]];
153
154 if (context == null) {
155 break;
156 }
157
158 localStack.push(context);
159 }
160
161 if (context && typeof context === "object" && target in context) {
162 value = context[target];
163 break;
164 }
165 }
166
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]);
170 }
171
172 if (value == null) {
173 return defaultValue;
174 }
175
176 return value;
177 }
178
179 function renderSection(name, stack, callback, inverted) {
180 var buffer = "";
181 var value = lookup(name, stack);
182
183 if (inverted) {
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();
189 }
190 } else if (isArray(value)) {
191 forEach(value, function (value) {
192 stack.push(value);
193 buffer += callback();
194 stack.pop();
195 });
196 } else if (typeof value === "object") {
197 stack.push(value);
198 buffer += callback();
199 stack.pop();
200 } else if (typeof value === "function") {
201 var scope = stack[stack.length - 1];
202 var scopedRender = function (template) {
203 return render(template, scope);
204 };
205 buffer += value.call(scope, callback(), scopedRender) || "";
206 } else if (value) {
207 buffer += callback();
208 }
209
210 return buffer;
211 }
212
213 /**
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:
217 *
218 * - file The name of the file the template comes from (displayed in
219 * error messages)
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
223 * console
224 * - space Set `true` to preserve whitespace from lines that otherwise
225 * contain only a {{tag}}. Defaults to `false`
226 */
227 function parse(template, options) {
228 options = options || {};
229
230 var tags = options.tags || exports.tags,
231 openTag = tags[0],
232 closeTag = tags[tags.length - 1];
233
234 var code = [
235 'var buffer = "";', // output buffer
236 "\nvar line = 1;", // keep track of source line number
237 "\ntry {",
238 '\nbuffer += "'
239 ];
240
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?
244
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);
251 }
252 } else {
253 spaces = [];
254 }
255
256 hasTag = false;
257 nonSpace = false;
258 };
259
260 var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
261
262 var setTags = function (source) {
263 tags = trim(source).split(/\s+/);
264 nextOpenTag = tags[0];
265 nextCloseTag = tags[tags.length - 1];
266 };
267
268 var includePartial = function (source) {
269 code.push(
270 '";',
271 updateLine,
272 '\nvar partial = partials["' + trim(source) + '"];',
273 '\nif (partial) {',
274 '\n buffer += render(partial,stack[stack.length - 1],partials);',
275 '\n}',
276 '\nbuffer += "'
277 );
278 };
279
280 var openSection = function (source, inverted) {
281 var name = trim(source);
282
283 if (name === "") {
284 throw debug(new Error("Section name may not be empty"), template, line, options.file);
285 }
286
287 sectionStack.push({name: name, inverted: inverted});
288
289 code.push(
290 '";',
291 updateLine,
292 '\nvar name = "' + name + '";',
293 '\nvar callback = (function () {',
294 '\n return function () {',
295 '\n var buffer = "";',
296 '\nbuffer += "'
297 );
298 };
299
300 var openInvertedSection = function (source) {
301 openSection(source, true);
302 };
303
304 var closeSection = function (source) {
305 var name = trim(source);
306 var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
307
308 if (!openName || name != openName) {
309 throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
310 }
311
312 var section = sectionStack.pop();
313
314 code.push(
315 '";',
316 '\n return buffer;',
317 '\n };',
318 '\n})();'
319 );
320
321 if (section.inverted) {
322 code.push("\nbuffer += renderSection(name,stack,callback,true);");
323 } else {
324 code.push("\nbuffer += renderSection(name,stack,callback);");
325 }
326
327 code.push('\nbuffer += "');
328 };
329
330 var sendPlain = function (source) {
331 code.push(
332 '";',
333 updateLine,
334 '\nbuffer += lookup("' + trim(source) + '",stack,"");',
335 '\nbuffer += "'
336 );
337 };
338
339 var sendEscaped = function (source) {
340 code.push(
341 '";',
342 updateLine,
343 '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
344 '\nbuffer += "'
345 );
346 };
347
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) {
351 i += openTag.length;
352 c = template.substr(i, 1);
353 updateLine = '\nline = ' + line + ';';
354 nextOpenTag = openTag;
355 nextCloseTag = closeTag;
356 hasTag = true;
357
358 switch (c) {
359 case "!": // comment
360 i++;
361 callback = null;
362 break;
363 case "=": // change open/close tags, e.g. {{=<% %>=}}
364 i++;
365 closeTag = "=" + closeTag;
366 callback = setTags;
367 break;
368 case ">": // include partial
369 i++;
370 callback = includePartial;
371 break;
372 case "#": // start section
373 i++;
374 callback = openSection;
375 break;
376 case "^": // start inverted section
377 i++;
378 callback = openInvertedSection;
379 break;
380 case "/": // end section
381 i++;
382 callback = closeSection;
383 break;
384 case "{": // plain variable
385 closeTag = "}" + closeTag;
386 // fall through
387 case "&": // plain variable
388 i++;
389 nonSpace = true;
390 callback = sendPlain;
391 break;
392 default: // escaped variable
393 nonSpace = true;
394 callback = sendEscaped;
395 }
396
397 var end = template.indexOf(closeTag, i);
398
399 if (end === -1) {
400 throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
401 }
402
403 var source = template.substring(i, end);
404
405 if (callback) {
406 callback(source);
407 }
408
409 // Maintain line count for \n in source.
410 var n = 0;
411 while (~(n = source.indexOf("\n", n))) {
412 line++;
413 n++;
414 }
415
416 i = end + closeTag.length - 1;
417 openTag = nextOpenTag;
418 closeTag = nextCloseTag;
419 } else {
420 c = template.substr(i, 1);
421
422 switch (c) {
423 case '"':
424 case "\\":
425 nonSpace = true;
426 code.push("\\" + c);
427 break;
428 case "\r":
429 // Ignore carriage returns.
430 break;
431 case "\n":
432 spaces.push(code.length);
433 code.push("\\n");
434 stripSpace(); // Check for whitespace on the current line.
435 line++;
436 break;
437 default:
438 if (isWhitespace(c)) {
439 spaces.push(code.length);
440 } else {
441 nonSpace = true;
442 }
443
444 code.push(c);
445 }
446 }
447 }
448
449 if (sectionStack.length != 0) {
450 throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
451 }
452
453 // Clean up any whitespace from a closing {{tag}} that was at the end
454 // of the template without a trailing \n.
455 stripSpace();
456
457 code.push(
458 '";',
459 "\nreturn buffer;",
460 "\n} catch (e) { throw {error: e, line: line}; }"
461 );
462
463 // Ignore `buffer += "";` statements.
464 var body = code.join("").replace(/buffer \+= "";\n/g, "");
465
466 if (options.debug) {
467 if (typeof console != "undefined" && console.log) {
468 console.log(body);
469 } else if (typeof print === "function") {
470 print(body);
471 }
472 }
473
474 return body;
475 }
476
477 /**
478 * Used by `compile` to generate a reusable function for the given `template`.
479 */
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);
484
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 || {};
490
491 var stack = [view]; // context stack
492
493 try {
494 return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
495 } catch (e) {
496 throw debug(e.error, template, e.line, options.file);
497 }
498 };
499 }
500
501 // Cache of pre-compiled templates.
502 var _cache = {};
503
504 /**
505 * Clear the cache of compiled templates.
506 */
507 function clearCache() {
508 _cache = {};
509 }
510
511 /**
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:
515 *
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
519 */
520 function compile(template, options) {
521 options = options || {};
522
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);
527 }
528
529 return _cache[template];
530 }
531
532 return _compile(template, options);
533 }
534
535 /**
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
539 * compiled function.
540 */
541 function render(template, view, partials) {
542 return compile(template)(view, partials);
543 }
544
545 })(Mustache);
546
547 $.mustache = function (template, view, partials) {
548 return Mustache.render(template, view, partials);
549 };
550
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();
556 });
557 };
558
559 })(jQuery);