MyAccount: Generate keys+Upload keys Ok [users needs to have account on myslice to...
[myslice.git] / third-party / mustache / mustache.js
1 /*!
2  * mustache.js - Logic-less {{mustache}} templates with JavaScript
3  * http://github.com/janl/mustache.js
4  */
5
6 /*global define: false*/
7
8 (function (root, factory) {
9   if (typeof exports === "object" && exports) {
10     factory(exports); // CommonJS
11   } else {
12     var mustache = {};
13     factory(mustache);
14     if (typeof define === "function" && define.amd) {
15       define(mustache); // AMD
16     } else {
17       root.Mustache = mustache; // <script>
18     }
19   }
20 }(this, function (mustache) {
21
22   var whiteRe = /\s*/;
23   var spaceRe = /\s+/;
24   var nonSpaceRe = /\S/;
25   var eqRe = /\s*=/;
26   var curlyRe = /\s*\}/;
27   var tagRe = /#|\^|\/|>|\{|&|=|!/;
28
29   // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
30   // See https://github.com/janl/mustache.js/issues/189
31   var RegExp_test = RegExp.prototype.test;
32   function testRegExp(re, string) {
33     return RegExp_test.call(re, string);
34   }
35
36   function isWhitespace(string) {
37     return !testRegExp(nonSpaceRe, string);
38   }
39
40   var Object_toString = Object.prototype.toString;
41   var isArray = Array.isArray || function (obj) {
42     return Object_toString.call(obj) === '[object Array]';
43   };
44
45   function escapeRegExp(string) {
46     return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
47   }
48
49   var entityMap = {
50     "&": "&amp;",
51     "<": "&lt;",
52     ">": "&gt;",
53     '"': '&quot;',
54     "'": '&#39;',
55     "/": '&#x2F;'
56   };
57
58   function escapeHtml(string) {
59     return String(string).replace(/[&<>"'\/]/g, function (s) {
60       return entityMap[s];
61     });
62   }
63
64   function Scanner(string) {
65     this.string = string;
66     this.tail = string;
67     this.pos = 0;
68   }
69
70   /**
71    * Returns `true` if the tail is empty (end of string).
72    */
73   Scanner.prototype.eos = function () {
74     return this.tail === "";
75   };
76
77   /**
78    * Tries to match the given regular expression at the current position.
79    * Returns the matched text if it can match, the empty string otherwise.
80    */
81   Scanner.prototype.scan = function (re) {
82     var match = this.tail.match(re);
83
84     if (match && match.index === 0) {
85       this.tail = this.tail.substring(match[0].length);
86       this.pos += match[0].length;
87       return match[0];
88     }
89
90     return "";
91   };
92
93   /**
94    * Skips all text until the given regular expression can be matched. Returns
95    * the skipped string, which is the entire tail if no match can be made.
96    */
97   Scanner.prototype.scanUntil = function (re) {
98     var match, pos = this.tail.search(re);
99
100     switch (pos) {
101     case -1:
102       match = this.tail;
103       this.pos += this.tail.length;
104       this.tail = "";
105       break;
106     case 0:
107       match = "";
108       break;
109     default:
110       match = this.tail.substring(0, pos);
111       this.tail = this.tail.substring(pos);
112       this.pos += pos;
113     }
114
115     return match;
116   };
117
118   function Context(view, parent) {
119     this.view = view || {};
120     this.parent = parent;
121     this._cache = {};
122   }
123
124   Context.make = function (view) {
125     return (view instanceof Context) ? view : new Context(view);
126   };
127
128   Context.prototype.push = function (view) {
129     return new Context(view, this);
130   };
131
132   Context.prototype.lookup = function (name) {
133     var value = this._cache[name];
134
135     if (!value) {
136       if (name == '.') {
137         value = this.view;
138       } else {
139         var context = this;
140
141         while (context) {
142           if (name.indexOf('.') > 0) {
143             value = context.view;
144             var names = name.split('.'), i = 0;
145             while (value && i < names.length) {
146               value = value[names[i++]];
147             }
148           } else {
149             value = context.view[name];
150           }
151
152           if (value != null) break;
153
154           context = context.parent;
155         }
156       }
157
158       this._cache[name] = value;
159     }
160
161     if (typeof value === 'function') value = value.call(this.view);
162
163     return value;
164   };
165
166   function Writer() {
167     this.clearCache();
168   }
169
170   Writer.prototype.clearCache = function () {
171     this._cache = {};
172     this._partialCache = {};
173   };
174
175   Writer.prototype.compile = function (template, tags) {
176     var fn = this._cache[template];
177
178     if (!fn) {
179       var tokens = mustache.parse(template, tags);
180       fn = this._cache[template] = this.compileTokens(tokens, template);
181     }
182
183     return fn;
184   };
185
186   Writer.prototype.compilePartial = function (name, template, tags) {
187     var fn = this.compile(template, tags);
188     this._partialCache[name] = fn;
189     return fn;
190   };
191
192   Writer.prototype.getPartial = function (name) {
193     if (!(name in this._partialCache) && this._loadPartial) {
194       this.compilePartial(name, this._loadPartial(name));
195     }
196
197     return this._partialCache[name];
198   };
199
200   Writer.prototype.compileTokens = function (tokens, template) {
201     var self = this;
202     return function (view, partials) {
203       if (partials) {
204         if (typeof partials === 'function') {
205           self._loadPartial = partials;
206         } else {
207           for (var name in partials) {
208             self.compilePartial(name, partials[name]);
209           }
210         }
211       }
212
213       return renderTokens(tokens, self, Context.make(view), template);
214     };
215   };
216
217   Writer.prototype.render = function (template, view, partials) {
218     return this.compile(template)(view, partials);
219   };
220
221   /**
222    * Low-level function that renders the given `tokens` using the given `writer`
223    * and `context`. The `template` string is only needed for templates that use
224    * higher-order sections to extract the portion of the original template that
225    * was contained in that section.
226    */
227   function renderTokens(tokens, writer, context, template) {
228     var buffer = '';
229
230     var token, tokenValue, value;
231     for (var i = 0, len = tokens.length; i < len; ++i) {
232       token = tokens[i];
233       tokenValue = token[1];
234
235       switch (token[0]) {
236       case '#':
237         value = context.lookup(tokenValue);
238
239         if (typeof value === 'object') {
240           if (isArray(value)) {
241             for (var j = 0, jlen = value.length; j < jlen; ++j) {
242               buffer += renderTokens(token[4], writer, context.push(value[j]), template);
243             }
244           } else if (value) {
245             buffer += renderTokens(token[4], writer, context.push(value), template);
246           }
247         } else if (typeof value === 'function') {
248           var text = template == null ? null : template.slice(token[3], token[5]);
249           value = value.call(context.view, text, function (template) {
250             return writer.render(template, context);
251           });
252           if (value != null) buffer += value;
253         } else if (value) {
254           buffer += renderTokens(token[4], writer, context, template);
255         }
256
257         break;
258       case '^':
259         value = context.lookup(tokenValue);
260
261         // Use JavaScript's definition of falsy. Include empty arrays.
262         // See https://github.com/janl/mustache.js/issues/186
263         if (!value || (isArray(value) && value.length === 0)) {
264           buffer += renderTokens(token[4], writer, context, template);
265         }
266
267         break;
268       case '>':
269         value = writer.getPartial(tokenValue);
270         if (typeof value === 'function') buffer += value(context);
271         break;
272       case '&':
273         value = context.lookup(tokenValue);
274         if (value != null) buffer += value;
275         break;
276       case 'name':
277         value = context.lookup(tokenValue);
278         if (value != null) buffer += mustache.escape(value);
279         break;
280       case 'text':
281         buffer += tokenValue;
282         break;
283       }
284     }
285
286     return buffer;
287   }
288
289   /**
290    * Forms the given array of `tokens` into a nested tree structure where
291    * tokens that represent a section have two additional items: 1) an array of
292    * all tokens that appear in that section and 2) the index in the original
293    * template that represents the end of that section.
294    */
295   function nestTokens(tokens) {
296     var tree = [];
297     var collector = tree;
298     var sections = [];
299
300     var token;
301     for (var i = 0, len = tokens.length; i < len; ++i) {
302       token = tokens[i];
303       switch (token[0]) {
304       case '#':
305       case '^':
306         sections.push(token);
307         collector.push(token);
308         collector = token[4] = [];
309         break;
310       case '/':
311         var section = sections.pop();
312         section[5] = token[2];
313         collector = sections.length > 0 ? sections[sections.length - 1][4] : tree;
314         break;
315       default:
316         collector.push(token);
317       }
318     }
319
320     return tree;
321   }
322
323   /**
324    * Combines the values of consecutive text tokens in the given `tokens` array
325    * to a single token.
326    */
327   function squashTokens(tokens) {
328     var squashedTokens = [];
329
330     var token, lastToken;
331     for (var i = 0, len = tokens.length; i < len; ++i) {
332       token = tokens[i];
333       if (token) {
334         if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
335           lastToken[1] += token[1];
336           lastToken[3] = token[3];
337         } else {
338           lastToken = token;
339           squashedTokens.push(token);
340         }
341       }
342     }
343
344     return squashedTokens;
345   }
346
347   function escapeTags(tags) {
348     return [
349       new RegExp(escapeRegExp(tags[0]) + "\\s*"),
350       new RegExp("\\s*" + escapeRegExp(tags[1]))
351     ];
352   }
353
354   /**
355    * Breaks up the given `template` string into a tree of token objects. If
356    * `tags` is given here it must be an array with two string values: the
357    * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
358    * course, the default is to use mustaches (i.e. Mustache.tags).
359    */
360   function parseTemplate(template, tags) {
361     template = template || '';
362     tags = tags || mustache.tags;
363
364     if (typeof tags === 'string') tags = tags.split(spaceRe);
365     if (tags.length !== 2) throw new Error('Invalid tags: ' + tags.join(', '));
366
367     var tagRes = escapeTags(tags);
368     var scanner = new Scanner(template);
369
370     var sections = [];     // Stack to hold section tokens
371     var tokens = [];       // Buffer to hold the tokens
372     var spaces = [];       // Indices of whitespace tokens on the current line
373     var hasTag = false;    // Is there a {{tag}} on the current line?
374     var nonSpace = false;  // Is there a non-space char on the current line?
375
376     // Strips all whitespace tokens array for the current line
377     // if there was a {{#tag}} on it and otherwise only space.
378     function stripSpace() {
379       if (hasTag && !nonSpace) {
380         while (spaces.length) {
381           delete tokens[spaces.pop()];
382         }
383       } else {
384         spaces = [];
385       }
386
387       hasTag = false;
388       nonSpace = false;
389     }
390
391     var start, type, value, chr, token;
392     while (!scanner.eos()) {
393       start = scanner.pos;
394
395       // Match any text between tags.
396       value = scanner.scanUntil(tagRes[0]);
397       if (value) {
398         for (var i = 0, len = value.length; i < len; ++i) {
399           chr = value.charAt(i);
400
401           if (isWhitespace(chr)) {
402             spaces.push(tokens.length);
403           } else {
404             nonSpace = true;
405           }
406
407           tokens.push(['text', chr, start, start + 1]);
408           start += 1;
409
410           // Check for whitespace on the current line.
411           if (chr == '\n') stripSpace();
412         }
413       }
414
415       // Match the opening tag.
416       if (!scanner.scan(tagRes[0])) break;
417       hasTag = true;
418
419       // Get the tag type.
420       type = scanner.scan(tagRe) || 'name';
421       scanner.scan(whiteRe);
422
423       // Get the tag value.
424       if (type === '=') {
425         value = scanner.scanUntil(eqRe);
426         scanner.scan(eqRe);
427         scanner.scanUntil(tagRes[1]);
428       } else if (type === '{') {
429         value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
430         scanner.scan(curlyRe);
431         scanner.scanUntil(tagRes[1]);
432         type = '&';
433       } else {
434         value = scanner.scanUntil(tagRes[1]);
435       }
436
437       // Match the closing tag.
438       if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos);
439
440       token = [type, value, start, scanner.pos];
441       tokens.push(token);
442
443       if (type === '#' || type === '^') {
444         sections.push(token);
445       } else if (type === '/') {
446         // Check section nesting.
447         if (sections.length === 0) throw new Error('Unopened section "' + value + '" at ' + start);
448         var openSection = sections.pop();
449         if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
450       } else if (type === 'name' || type === '{' || type === '&') {
451         nonSpace = true;
452       } else if (type === '=') {
453         // Set the tags for the next time around.
454         tags = value.split(spaceRe);
455         if (tags.length !== 2) throw new Error('Invalid tags at ' + start + ': ' + tags.join(', '));
456         tagRes = escapeTags(tags);
457       }
458     }
459
460     // Make sure there are no open sections when we're done.
461     var openSection = sections.pop();
462     if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
463
464     tokens = squashTokens(tokens);
465
466     return nestTokens(tokens);
467   }
468
469   mustache.name = "mustache.js";
470   mustache.version = "0.7.2";
471   mustache.tags = ["{{", "}}"];
472
473   mustache.Scanner = Scanner;
474   mustache.Context = Context;
475   mustache.Writer = Writer;
476
477   mustache.parse = parseTemplate;
478
479   // Export the escaping function so that the user may override it.
480   // See https://github.com/janl/mustache.js/issues/244
481   mustache.escape = escapeHtml;
482
483   // All Mustache.* functions use this writer.
484   var defaultWriter = new Writer();
485
486   /**
487    * Clears all cached templates and partials in the default writer.
488    */
489   mustache.clearCache = function () {
490     return defaultWriter.clearCache();
491   };
492
493   /**
494    * Compiles the given `template` to a reusable function using the default
495    * writer.
496    */
497   mustache.compile = function (template, tags) {
498     return defaultWriter.compile(template, tags);
499   };
500
501   /**
502    * Compiles the partial with the given `name` and `template` to a reusable
503    * function using the default writer.
504    */
505   mustache.compilePartial = function (name, template, tags) {
506     return defaultWriter.compilePartial(name, template, tags);
507   };
508
509   /**
510    * Compiles the given array of tokens (the output of a parse) to a reusable
511    * function using the default writer.
512    */
513   mustache.compileTokens = function (tokens, template) {
514     return defaultWriter.compileTokens(tokens, template);
515   };
516
517   /**
518    * Renders the `template` with the given `view` and `partials` using the
519    * default writer.
520    */
521   mustache.render = function (template, view, partials) {
522     return defaultWriter.render(template, view, partials);
523   };
524
525   // This is here for backwards compatibility with 0.4.x.
526   mustache.to_html = function (template, view, partials, send) {
527     var result = mustache.render(template, view, partials);
528
529     if (typeof send === "function") {
530       send(result);
531     } else {
532       return result;
533     }
534   };
535
536 }));