Add scripts to create myops-getqueryview:
[myops.git] / web / query / vendor / couchapp / lib / mustache.js
1 /*
2  * CommonJS-compatible mustache.js module
3  *
4  * See http://github.com/janl/mustache.js for more info.
5  */
6
7 /*
8   mustache.js — Logic-less templates in JavaScript
9
10   See http://mustache.github.com/ for more info.
11 */
12
13 var Mustache = function() {
14   var Renderer = function() {};
15
16   Renderer.prototype = {
17     otag: "{{",
18     ctag: "}}",
19     pragmas: {},
20     buffer: [],
21     pragmas_implemented: {
22       "IMPLICIT-ITERATOR": true
23     },
24     context: {},
25
26     render: function(template, context, partials, in_recursion) {
27       // reset buffer & set context
28       if(!in_recursion) {
29         this.context = context;
30         this.buffer = []; // TODO: make this non-lazy
31       }
32
33       // fail fast
34       if(!this.includes("", template)) {
35         if(in_recursion) {
36           return template;
37         } else {
38           this.send(template);
39           return;
40         }
41       }
42
43       template = this.render_pragmas(template);
44       var html = this.render_section(template, context, partials);
45       if(in_recursion) {
46         return this.render_tags(html, context, partials, in_recursion);
47       }
48
49       this.render_tags(html, context, partials, in_recursion);
50     },
51
52     /*
53       Sends parsed lines
54     */
55     send: function(line) {
56       if(line != "") {
57         this.buffer.push(line);
58       }
59     },
60
61     /*
62       Looks for %PRAGMAS
63     */
64     render_pragmas: function(template) {
65       // no pragmas
66       if(!this.includes("%", template)) {
67         return template;
68       }
69
70       var that = this;
71       var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
72             this.ctag);
73       return template.replace(regex, function(match, pragma, options) {
74         if(!that.pragmas_implemented[pragma]) {
75           throw({message: 
76             "This implementation of mustache doesn't understand the '" +
77             pragma + "' pragma"});
78         }
79         that.pragmas[pragma] = {};
80         if(options) {
81           var opts = options.split("=");
82           that.pragmas[pragma][opts[0]] = opts[1];
83         }
84         return "";
85         // ignore unknown pragmas silently
86       });
87     },
88
89     /*
90       Tries to find a partial in the curent scope and render it
91     */
92     render_partial: function(name, context, partials) {
93       name = this.trim(name);
94       if(!partials || partials[name] === undefined) {
95         throw({message: "unknown_partial '" + name + "'"});
96       }
97       if(typeof(context[name]) != "object") {
98         return this.render(partials[name], context, partials, true);
99       }
100       return this.render(partials[name], context[name], partials, true);
101     },
102
103     /*
104       Renders inverted (^) and normal (#) sections
105     */
106     render_section: function(template, context, partials) {
107       if(!this.includes("#", template) && !this.includes("^", template)) {
108         return template;
109       }
110
111       var that = this;
112       // CSW - Added "+?" so it finds the tighest bound, not the widest
113       var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
114               "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
115               "\\s*", "mg");
116
117       // for each {{#foo}}{{/foo}} section do...
118       return template.replace(regex, function(match, type, name, content) {
119         var value = that.find(name, context);
120         if(type == "^") { // inverted section
121           if(!value || that.is_array(value) && value.length === 0) {
122             // false or empty list, render it
123             return that.render(content, context, partials, true);
124           } else {
125             return "";
126           }
127         } else if(type == "#") { // normal section
128           if(that.is_array(value)) { // Enumerable, Let's loop!
129             return that.map(value, function(row) {
130               return that.render(content, that.create_context(row),
131                 partials, true);
132             }).join("");
133           } else if(that.is_object(value)) { // Object, Use it as subcontext!
134             return that.render(content, that.create_context(value),
135               partials, true);
136           } else if(typeof value === "function") {
137             // higher order section
138             return value.call(context, content, function(text) {
139               return that.render(text, context, partials, true);
140             });
141           } else if(value) { // boolean section
142             return that.render(content, context, partials, true);
143           } else {
144             return "";
145           }
146         }
147       });
148     },
149
150     /*
151       Replace {{foo}} and friends with values from our view
152     */
153     render_tags: function(template, context, partials, in_recursion) {
154       // tit for tat
155       var that = this;
156
157       var new_regex = function() {
158         return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
159           that.ctag + "+", "g");
160       };
161
162       var regex = new_regex();
163       var tag_replace_callback = function(match, operator, name) {
164         switch(operator) {
165         case "!": // ignore comments
166           return "";
167         case "=": // set new delimiters, rebuild the replace regexp
168           that.set_delimiters(name);
169           regex = new_regex();
170           return "";
171         case ">": // render partial
172           return that.render_partial(name, context, partials);
173         case "{": // the triple mustache is unescaped
174           return that.find(name, context);
175         default: // escape the value
176           return that.escape(that.find(name, context));
177         }
178       };
179       var lines = template.split("\n");
180       for(var i = 0; i < lines.length; i++) {
181         lines[i] = lines[i].replace(regex, tag_replace_callback, this);
182         if(!in_recursion) {
183           this.send(lines[i]);
184         }
185       }
186
187       if(in_recursion) {
188         return lines.join("\n");
189       }
190     },
191
192     set_delimiters: function(delimiters) {
193       var dels = delimiters.split(" ");
194       this.otag = this.escape_regex(dels[0]);
195       this.ctag = this.escape_regex(dels[1]);
196     },
197
198     escape_regex: function(text) {
199       // thank you Simon Willison
200       if(!arguments.callee.sRE) {
201         var specials = [
202           '/', '.', '*', '+', '?', '|',
203           '(', ')', '[', ']', '{', '}', '\\'
204         ];
205         arguments.callee.sRE = new RegExp(
206           '(\\' + specials.join('|\\') + ')', 'g'
207         );
208       }
209       return text.replace(arguments.callee.sRE, '\\$1');
210     },
211
212     /*
213       find `name` in current `context`. That is find me a value
214       from the view object
215     */
216     find: function(name, context) {
217       name = this.trim(name);
218
219       // Checks whether a value is thruthy or false or 0
220       function is_kinda_truthy(bool) {
221         return bool === false || bool === 0 || bool;
222       }
223
224       var value;
225       if(is_kinda_truthy(context[name])) {
226         value = context[name];
227       } else if(is_kinda_truthy(this.context[name])) {
228         value = this.context[name];
229       }
230
231       if(typeof value === "function") {
232         return value.apply(context);
233       }
234       if(value !== undefined) {
235         return value;
236       }
237       // silently ignore unkown variables
238       return "";
239     },
240
241     // Utility methods
242
243     /* includes tag */
244     includes: function(needle, haystack) {
245       return haystack.indexOf(this.otag + needle) != -1;
246     },
247
248     /*
249       Does away with nasty characters
250     */
251     escape: function(s) {
252       s = String(s === null ? "" : s);
253       return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
254         switch(s) {
255         case "&": return "&amp;";
256         case "\\": return "\\\\";
257         case '"': return '\"';
258         case "<": return "&lt;";
259         case ">": return "&gt;";
260         default: return s;
261         }
262       });
263     },
264
265     // by @langalex, support for arrays of strings
266     create_context: function(_context) {
267       if(this.is_object(_context)) {
268         return _context;
269       } else {
270         var iterator = ".";
271         if(this.pragmas["IMPLICIT-ITERATOR"]) {
272           iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
273         }
274         var ctx = {};
275         ctx[iterator] = _context;
276         return ctx;
277       }
278     },
279
280     is_object: function(a) {
281       return a && typeof a == "object";
282     },
283
284     is_array: function(a) {
285       return Object.prototype.toString.call(a) === '[object Array]';
286     },
287
288     /*
289       Gets rid of leading and trailing whitespace
290     */
291     trim: function(s) {
292       return s.replace(/^\s*|\s*$/g, "");
293     },
294
295     /*
296       Why, why, why? Because IE. Cry, cry cry.
297     */
298     map: function(array, fn) {
299       if (typeof array.map == "function") {
300         return array.map(fn);
301       } else {
302         var r = [];
303         var l = array.length;
304         for(var i = 0; i < l; i++) {
305           r.push(fn(array[i]));
306         }
307         return r;
308       }
309     }
310   };
311
312   return({
313     name: "mustache.js",
314     version: "0.3.1-dev",
315
316     /*
317       Turns a template and view into HTML
318     */
319     to_html: function(template, view, partials, send_fun) {
320       var renderer = new Renderer();
321       if(send_fun) {
322         renderer.send = send_fun;
323       }
324       renderer.render(template, view, partials);
325       if(!send_fun) {
326         return renderer.buffer.join("\n");
327       }
328     },
329     escape : function(text) {
330       return new Renderer().escape(text);
331     }
332   });
333 }();
334
335 exports.name = Mustache.name;
336 exports.version = Mustache.version;
337
338 exports.to_html = Mustache.to_html;
339 exports.escape = Mustache.escape;