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