web/res/js/mustache.js
changeset 940 843f96e4eebd
parent 598 d366aa22bd79
child 1304 10974bff4dae
equal deleted inserted replaced
939:1648489454d7 940:843f96e4eebd
     1 /*
     1 /*!
     2   mustache.js — Logic-less templates in JavaScript
     2  * mustache.js - Logic-less {{mustache}} templates with JavaScript
     3 
     3  * http://github.com/janl/mustache.js
     4   See http://mustache.github.com/ for more info.
     4  */
     5 */
     5 
     6 
     6 /*global define: false*/
     7 var Mustache = function () {
     7 
     8   var _toString = Object.prototype.toString;
     8 (function (root, factory) {
     9 
     9   if (typeof exports === "object" && exports) {
    10   Array.isArray = Array.isArray || function (obj) {
    10     factory(exports); // CommonJS
    11     return _toString.call(obj) == "[object Array]";
       
    12   }
       
    13 
       
    14   var _trim = String.prototype.trim, trim;
       
    15 
       
    16   if (_trim) {
       
    17     trim = function (text) {
       
    18       return text == null ? "" : _trim.call(text);
       
    19     }
       
    20   } else {
    11   } else {
    21     var trimLeft, trimRight;
    12     var mustache = {};
    22 
    13     factory(mustache);
    23     // IE doesn't match non-breaking spaces with \s.
    14     if (typeof define === "function" && define.amd) {
    24     if ((/\S/).test("\xA0")) {
    15       define(mustache); // AMD
    25       trimLeft = /^[\s\xA0]+/;
       
    26       trimRight = /[\s\xA0]+$/;
       
    27     } else {
    16     } else {
    28       trimLeft = /^\s+/;
    17       root.Mustache = mustache; // <script>
    29       trimRight = /\s+$/;
    18     }
    30     }
    19   }
    31 
    20 }(this, function (mustache) {
    32     trim = function (text) {
    21 
    33       return text == null ? "" :
    22   var whiteRe = /\s*/;
    34         text.toString().replace(trimLeft, "").replace(trimRight, "");
    23   var spaceRe = /\s+/;
    35     }
    24   var nonSpaceRe = /\S/;
    36   }
    25   var eqRe = /\s*=/;
    37 
    26   var curlyRe = /\s*\}/;
    38   var escapeMap = {
    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 = {
    39     "&": "&amp;",
    50     "&": "&amp;",
    40     "<": "&lt;",
    51     "<": "&lt;",
    41     ">": "&gt;",
    52     ">": "&gt;",
    42     '"': '&quot;',
    53     '"': '&quot;',
    43     "'": '&#39;'
    54     "'": '&#39;',
    44   };
    55     "/": '&#x2F;'
    45 
    56   };
    46   function escapeHTML(string) {
    57 
    47     return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
    58   function escapeHtml(string) {
    48       return escapeMap[s] || s;
    59     return String(string).replace(/[&<>"'\/]/g, function (s) {
       
    60       return entityMap[s];
    49     });
    61     });
    50   }
    62   }
    51 
    63 
    52   var regexCache = {};
    64   function Scanner(string) {
    53   var Renderer = function () {};
    65     this.string = string;
    54 
    66     this.tail = string;
    55   Renderer.prototype = {
    67     this.pos = 0;
    56     otag: "{{",
    68   }
    57     ctag: "}}",
    69 
    58     pragmas: {},
    70   /**
    59     buffer: [],
    71    * Returns `true` if the tail is empty (end of string).
    60     pragmas_implemented: {
    72    */
    61       "IMPLICIT-ITERATOR": true
    73   Scanner.prototype.eos = function () {
    62     },
    74     return this.tail === "";
    63     context: {},
    75   };
    64 
    76 
    65     render: function (template, context, partials, in_recursion) {
    77   /**
    66       // reset buffer & set context
    78    * Tries to match the given regular expression at the current position.
    67       if (!in_recursion) {
    79    * Returns the matched text if it can match, the empty string otherwise.
    68         this.context = context;
    80    */
    69         this.buffer = []; // TODO: make this non-lazy
    81   Scanner.prototype.scan = function (re) {
    70       }
    82     var match = this.tail.match(re);
    71 
    83 
    72       // fail fast
    84     if (match && match.index === 0) {
    73       if (!this.includes("", template)) {
    85       this.tail = this.tail.substring(match[0].length);
    74         if (in_recursion) {
    86       this.pos += match[0].length;
    75           return template;
    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;
    76         } else {
   206         } else {
    77           this.send(template);
   207           for (var name in partials) {
    78           return;
   208             self.compilePartial(name, partials[name]);
    79         }
   209           }
    80       }
   210         }
    81 
   211       }
    82       // get the pragmas together
   212 
    83       template = this.render_pragmas(template);
   213       return renderTokens(tokens, self, Context.make(view), template);
    84 
   214     };
    85       // render the template
   215   };
    86       var html = this.render_section(template, context, partials);
   216 
    87 
   217   Writer.prototype.render = function (template, view, partials) {
    88       // render_section did not find any sections, we still need to render the tags
   218     return this.compile(template)(view, partials);
    89       if (html === false) {
   219   };
    90         html = this.render_tags(template, context, partials, in_recursion);
   220 
    91       }
   221   /**
    92 
   222    * Low-level function that renders the given `tokens` using the given `writer`
    93       if (in_recursion) {
   223    * and `context`. The `template` string is only needed for templates that use
    94         return html;
   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         }
    95       } else {
   383       } else {
    96         this.sendLines(html);
   384         spaces = [];
    97       }
   385       }
    98     },
   386 
    99 
   387       hasTag = false;
   100     /*
   388       nonSpace = false;
   101       Sends parsed lines
   389     }
   102     */
   390 
   103     send: function (line) {
   391     var start, type, value, chr, token;
   104       if (line !== "") {
   392     while (!scanner.eos()) {
   105         this.buffer.push(line);
   393       start = scanner.pos;
   106       }
   394 
   107     },
   395       // Match any text between tags.
   108 
   396       value = scanner.scanUntil(tagRes[0]);
   109     sendLines: function (text) {
   397       if (value) {
   110       if (text) {
   398         for (var i = 0, len = value.length; i < len; ++i) {
   111         var lines = text.split("\n");
   399           chr = value.charAt(i);
   112         for (var i = 0; i < lines.length; i++) {
   400 
   113           this.send(lines[i]);
   401           if (isWhitespace(chr)) {
   114         }
   402             spaces.push(tokens.length);
   115       }
       
   116     },
       
   117 
       
   118     /*
       
   119       Looks for %PRAGMAS
       
   120     */
       
   121     render_pragmas: function (template) {
       
   122       // no pragmas
       
   123       if (!this.includes("%", template)) {
       
   124         return template;
       
   125       }
       
   126 
       
   127       var that = this;
       
   128       var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) {
       
   129         return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
       
   130       });
       
   131 
       
   132       return template.replace(regex, function (match, pragma, options) {
       
   133         if (!that.pragmas_implemented[pragma]) {
       
   134           throw({message:
       
   135             "This implementation of mustache doesn't understand the '" +
       
   136             pragma + "' pragma"});
       
   137         }
       
   138         that.pragmas[pragma] = {};
       
   139         if (options) {
       
   140           var opts = options.split("=");
       
   141           that.pragmas[pragma][opts[0]] = opts[1];
       
   142         }
       
   143         return "";
       
   144         // ignore unknown pragmas silently
       
   145       });
       
   146     },
       
   147 
       
   148     /*
       
   149       Tries to find a partial in the curent scope and render it
       
   150     */
       
   151     render_partial: function (name, context, partials) {
       
   152       name = trim(name);
       
   153       if (!partials || partials[name] === undefined) {
       
   154         throw({message: "unknown_partial '" + name + "'"});
       
   155       }
       
   156       if (!context || typeof context[name] != "object") {
       
   157         return this.render(partials[name], context, partials, true);
       
   158       }
       
   159       return this.render(partials[name], context[name], partials, true);
       
   160     },
       
   161 
       
   162     /*
       
   163       Renders inverted (^) and normal (#) sections
       
   164     */
       
   165     render_section: function (template, context, partials) {
       
   166       if (!this.includes("#", template) && !this.includes("^", template)) {
       
   167         // did not render anything, there were no sections
       
   168         return false;
       
   169       }
       
   170 
       
   171       var that = this;
       
   172 
       
   173       var regex = this.getCachedRegex("render_section", function (otag, ctag) {
       
   174         // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
       
   175         return new RegExp(
       
   176           "^([\\s\\S]*?)" +         // all the crap at the beginning that is not {{*}} ($1)
       
   177 
       
   178           otag +                    // {{
       
   179           "(\\^|\\#)\\s*(.+)\\s*" + //  #foo (# == $2, foo == $3)
       
   180           ctag +                    // }}
       
   181 
       
   182           "\n*([\\s\\S]*?)" +       // between the tag ($2). leading newlines are dropped
       
   183 
       
   184           otag +                    // {{
       
   185           "\\/\\s*\\3\\s*" +        //  /foo (backreference to the opening tag).
       
   186           ctag +                    // }}
       
   187 
       
   188           "\\s*([\\s\\S]*)$",       // everything else in the string ($4). leading whitespace is dropped.
       
   189 
       
   190         "g");
       
   191       });
       
   192 
       
   193 
       
   194       // for each {{#foo}}{{/foo}} section do...
       
   195       return template.replace(regex, function (match, before, type, name, content, after) {
       
   196         // before contains only tags, no sections
       
   197         var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
       
   198 
       
   199         // after may contain both sections and tags, so use full rendering function
       
   200             renderedAfter = after ? that.render(after, context, partials, true) : "",
       
   201 
       
   202         // will be computed below
       
   203             renderedContent,
       
   204 
       
   205             value = that.find(name, context);
       
   206 
       
   207         if (type === "^") { // inverted section
       
   208           if (!value || Array.isArray(value) && value.length === 0) {
       
   209             // false or empty list, render it
       
   210             renderedContent = that.render(content, context, partials, true);
       
   211           } else {
   403           } else {
   212             renderedContent = "";
   404             nonSpace = true;
   213           }
   405           }
   214         } else if (type === "#") { // normal section
   406 
   215           if (Array.isArray(value)) { // Enumerable, Let's loop!
   407           tokens.push(['text', chr, start, start + 1]);
   216             renderedContent = that.map(value, function (row) {
   408           start += 1;
   217               return that.render(content, that.create_context(row), partials, true);
   409 
   218             }).join("");
   410           // Check for whitespace on the current line.
   219           } else if (that.is_object(value)) { // Object, Use it as subcontext!
   411           if (chr == '\n') stripSpace();
   220             renderedContent = that.render(content, that.create_context(value),
   412         }
   221               partials, true);
   413       }
   222           } else if (typeof value == "function") {
   414 
   223             // higher order section
   415       // Match the opening tag.
   224             renderedContent = value.call(context, content, function (text) {
   416       if (!scanner.scan(tagRes[0])) break;
   225               return that.render(text, context, partials, true);
   417       hasTag = true;
   226             });
   418 
   227           } else if (value) { // boolean section
   419       // Get the tag type.
   228             renderedContent = that.render(content, context, partials, true);
   420       type = scanner.scan(tagRe) || 'name';
   229           } else {
   421       scanner.scan(whiteRe);
   230             renderedContent = "";
   422 
   231           }
   423       // Get the tag value.
   232         }
   424       if (type === '=') {
   233 
   425         value = scanner.scanUntil(eqRe);
   234         return renderedBefore + renderedContent + renderedAfter;
   426         scanner.scan(eqRe);
   235       });
   427         scanner.scanUntil(tagRes[1]);
   236     },
   428       } else if (type === '{') {
   237 
   429         value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
   238     /*
   430         scanner.scan(curlyRe);
   239       Replace {{foo}} and friends with values from our view
   431         scanner.scanUntil(tagRes[1]);
   240     */
   432         type = '&';
   241     render_tags: function (template, context, partials, in_recursion) {
       
   242       // tit for tat
       
   243       var that = this;
       
   244 
       
   245       var new_regex = function () {
       
   246         return that.getCachedRegex("render_tags", function (otag, ctag) {
       
   247           return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g");
       
   248         });
       
   249       };
       
   250 
       
   251       var regex = new_regex();
       
   252       var tag_replace_callback = function (match, operator, name) {
       
   253         switch(operator) {
       
   254         case "!": // ignore comments
       
   255           return "";
       
   256         case "=": // set new delimiters, rebuild the replace regexp
       
   257           that.set_delimiters(name);
       
   258           regex = new_regex();
       
   259           return "";
       
   260         case ">": // render partial
       
   261           return that.render_partial(name, context, partials);
       
   262         case "{": // the triple mustache is unescaped
       
   263         case "&": // & operator is an alternative unescape method
       
   264           return that.find(name, context);
       
   265         default: // escape the value
       
   266           return escapeHTML(that.find(name, context));
       
   267         }
       
   268       };
       
   269       var lines = template.split("\n");
       
   270       for(var i = 0; i < lines.length; i++) {
       
   271         lines[i] = lines[i].replace(regex, tag_replace_callback, this);
       
   272         if (!in_recursion) {
       
   273           this.send(lines[i]);
       
   274         }
       
   275       }
       
   276 
       
   277       if (in_recursion) {
       
   278         return lines.join("\n");
       
   279       }
       
   280     },
       
   281 
       
   282     set_delimiters: function (delimiters) {
       
   283       var dels = delimiters.split(" ");
       
   284       this.otag = this.escape_regex(dels[0]);
       
   285       this.ctag = this.escape_regex(dels[1]);
       
   286     },
       
   287 
       
   288     escape_regex: function (text) {
       
   289       // thank you Simon Willison
       
   290       if (!arguments.callee.sRE) {
       
   291         var specials = [
       
   292           '/', '.', '*', '+', '?', '|',
       
   293           '(', ')', '[', ']', '{', '}', '\\'
       
   294         ];
       
   295         arguments.callee.sRE = new RegExp(
       
   296           '(\\' + specials.join('|\\') + ')', 'g'
       
   297         );
       
   298       }
       
   299       return text.replace(arguments.callee.sRE, '\\$1');
       
   300     },
       
   301 
       
   302     /*
       
   303       find `name` in current `context`. That is find me a value
       
   304       from the view object
       
   305     */
       
   306     find: function (name, context) {
       
   307       name = trim(name);
       
   308 
       
   309       // Checks whether a value is thruthy or false or 0
       
   310       function is_kinda_truthy(bool) {
       
   311         return bool === false || bool === 0 || bool;
       
   312       }
       
   313 
       
   314       var value;
       
   315 
       
   316       // check for dot notation eg. foo.bar
       
   317       if (name.match(/([a-z_]+)\./ig)) {
       
   318         var childValue = this.walk_context(name, context);
       
   319         if (is_kinda_truthy(childValue)) {
       
   320           value = childValue;
       
   321         }
       
   322       } else {
   433       } else {
   323         if (is_kinda_truthy(context[name])) {
   434         value = scanner.scanUntil(tagRes[1]);
   324           value = context[name];
   435       }
   325         } else if (is_kinda_truthy(this.context[name])) {
   436 
   326           value = this.context[name];
   437       // Match the closing tag.
   327         }
   438       if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos);
   328       }
   439 
   329 
   440       token = [type, value, start, scanner.pos];
   330       if (typeof value == "function") {
   441       tokens.push(token);
   331         return value.apply(context);
   442 
   332       }
   443       if (type === '#' || type === '^') {
   333       if (value !== undefined) {
   444         sections.push(token);
   334         return value;
   445       } else if (type === '/') {
   335       }
   446         // Check section nesting.
   336       // silently ignore unkown variables
   447         if (sections.length === 0) throw new Error('Unopened section "' + value + '" at ' + start);
   337       return "";
   448         var openSection = sections.pop();
   338     },
   449         if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
   339 
   450       } else if (type === 'name' || type === '{' || type === '&') {
   340     walk_context: function (name, context) {
   451         nonSpace = true;
   341       var path = name.split('.');
   452       } else if (type === '=') {
   342       // if the var doesn't exist in current context, check the top level context
   453         // Set the tags for the next time around.
   343       var value_context = (context[path[0]] != undefined) ? context : this.context;
   454         tags = value.split(spaceRe);
   344       var value = value_context[path.shift()];
   455         if (tags.length !== 2) throw new Error('Invalid tags at ' + start + ': ' + tags.join(', '));
   345       while (value != undefined && path.length > 0) {
   456         tagRes = escapeTags(tags);
   346         value_context = value;
   457       }
   347         value = value[path.shift()];
   458     }
   348       }
   459 
   349       // if the value is a function, call it, binding the correct context
   460     // Make sure there are no open sections when we're done.
   350       if (typeof value == "function") {
   461     var openSection = sections.pop();
   351         return value.apply(value_context);
   462     if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
   352       }
   463 
   353       return value;
   464     tokens = squashTokens(tokens);
   354     },
   465 
   355 
   466     return nestTokens(tokens);
   356     // Utility methods
   467   }
   357 
   468 
   358     /* includes tag */
   469   mustache.name = "mustache.js";
   359     includes: function (needle, haystack) {
   470   mustache.version = "0.7.2";
   360       return haystack.indexOf(this.otag + needle) != -1;
   471   mustache.tags = ["{{", "}}"];
   361     },
   472 
   362 
   473   mustache.Scanner = Scanner;
   363     // by @langalex, support for arrays of strings
   474   mustache.Context = Context;
   364     create_context: function (_context) {
   475   mustache.Writer = Writer;
   365       if (this.is_object(_context)) {
   476 
   366         return _context;
   477   mustache.parse = parseTemplate;
   367       } else {
   478 
   368         var iterator = ".";
   479   // Export the escaping function so that the user may override it.
   369         if (this.pragmas["IMPLICIT-ITERATOR"]) {
   480   // See https://github.com/janl/mustache.js/issues/244
   370           iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
   481   mustache.escape = escapeHtml;
   371         }
   482 
   372         var ctx = {};
   483   // All Mustache.* functions use this writer.
   373         ctx[iterator] = _context;
   484   var defaultWriter = new Writer();
   374         return ctx;
   485 
   375       }
   486   /**
   376     },
   487    * Clears all cached templates and partials in the default writer.
   377 
   488    */
   378     is_object: function (a) {
   489   mustache.clearCache = function () {
   379       return a && typeof a == "object";
   490     return defaultWriter.clearCache();
   380     },
   491   };
   381 
   492 
   382     /*
   493   /**
   383       Why, why, why? Because IE. Cry, cry cry.
   494    * Compiles the given `template` to a reusable function using the default
   384     */
   495    * writer.
   385     map: function (array, fn) {
   496    */
   386       if (typeof array.map == "function") {
   497   mustache.compile = function (template, tags) {
   387         return array.map(fn);
   498     return defaultWriter.compile(template, tags);
   388       } else {
   499   };
   389         var r = [];
   500 
   390         var l = array.length;
   501   /**
   391         for(var i = 0; i < l; i++) {
   502    * Compiles the partial with the given `name` and `template` to a reusable
   392           r.push(fn(array[i]));
   503    * function using the default writer.
   393         }
   504    */
   394         return r;
   505   mustache.compilePartial = function (name, template, tags) {
   395       }
   506     return defaultWriter.compilePartial(name, template, tags);
   396     },
   507   };
   397 
   508 
   398     getCachedRegex: function (name, generator) {
   509   /**
   399       var byOtag = regexCache[this.otag];
   510    * Compiles the given array of tokens (the output of a parse) to a reusable
   400       if (!byOtag) {
   511    * function using the default writer.
   401         byOtag = regexCache[this.otag] = {};
   512    */
   402       }
   513   mustache.compileTokens = function (tokens, template) {
   403 
   514     return defaultWriter.compileTokens(tokens, template);
   404       var byCtag = byOtag[this.ctag];
   515   };
   405       if (!byCtag) {
   516 
   406         byCtag = byOtag[this.ctag] = {};
   517   /**
   407       }
   518    * Renders the `template` with the given `view` and `partials` using the
   408 
   519    * default writer.
   409       var regex = byCtag[name];
   520    */
   410       if (!regex) {
   521   mustache.render = function (template, view, partials) {
   411         regex = byCtag[name] = generator(this.otag, this.ctag);
   522     return defaultWriter.render(template, view, partials);
   412       }
   523   };
   413 
   524 
   414       return regex;
   525   // This is here for backwards compatibility with 0.4.x.
   415     }
   526   mustache.to_html = function (template, view, partials, send) {
   416   };
   527     var result = mustache.render(template, view, partials);
   417 
   528 
   418   return({
   529     if (typeof send === "function") {
   419     name: "mustache.js",
   530       send(result);
   420     version: "0.5.0-dev",
   531     } else {
   421 
   532       return result;
   422     /*
   533     }
   423       Turns a template and view into HTML
   534   };
   424     */
   535 
   425     to_html: function (template, view, partials, send_fun) {
   536 }));
   426       var renderer = new Renderer();
       
   427       if (send_fun) {
       
   428         renderer.send = send_fun;
       
   429       }
       
   430       renderer.render(template, view || {}, partials);
       
   431       if (!send_fun) {
       
   432         return renderer.buffer.join("\n");
       
   433       }
       
   434     }
       
   435   });
       
   436 }();