mindreframer-riemann-dash 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +52 -0
  4. data/LICENSE +21 -0
  5. data/README.markdown +52 -0
  6. data/Rakefile.rb +11 -0
  7. data/bin/riemann-dash +7 -0
  8. data/example/config.rb +17 -0
  9. data/lib/riemann/dash.rb +5 -0
  10. data/lib/riemann/dash/app.rb +32 -0
  11. data/lib/riemann/dash/config.rb +154 -0
  12. data/lib/riemann/dash/controller/css.rb +5 -0
  13. data/lib/riemann/dash/controller/index.rb +20 -0
  14. data/lib/riemann/dash/public/clock.js +45 -0
  15. data/lib/riemann/dash/public/dash.js +287 -0
  16. data/lib/riemann/dash/public/format.js +24 -0
  17. data/lib/riemann/dash/public/jquery-1.7.2.min.js +4 -0
  18. data/lib/riemann/dash/public/jquery-ui-1.9.0.custom.min.js +6 -0
  19. data/lib/riemann/dash/public/jquery.json-2.2.min.js +31 -0
  20. data/lib/riemann/dash/public/jquery.quickfit.js +144 -0
  21. data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +26 -0
  22. data/lib/riemann/dash/public/keys.js +46 -0
  23. data/lib/riemann/dash/public/mustache.js +597 -0
  24. data/lib/riemann/dash/public/persistence.js +30 -0
  25. data/lib/riemann/dash/public/profile.js +33 -0
  26. data/lib/riemann/dash/public/subs.js +164 -0
  27. data/lib/riemann/dash/public/toastr.css +174 -0
  28. data/lib/riemann/dash/public/toastr.js +207 -0
  29. data/lib/riemann/dash/public/toolbar.js +217 -0
  30. data/lib/riemann/dash/public/underscore-min.js +5 -0
  31. data/lib/riemann/dash/public/util.js +34 -0
  32. data/lib/riemann/dash/public/vendor/smoothie.js +374 -0
  33. data/lib/riemann/dash/public/view.js +704 -0
  34. data/lib/riemann/dash/public/views/gauge.js +76 -0
  35. data/lib/riemann/dash/public/views/grid.js +279 -0
  36. data/lib/riemann/dash/public/views/help.js +28 -0
  37. data/lib/riemann/dash/public/views/timeseries.js +107 -0
  38. data/lib/riemann/dash/public/views/title.js +35 -0
  39. data/lib/riemann/dash/public/x.png +0 -0
  40. data/lib/riemann/dash/rack/static.rb +16 -0
  41. data/lib/riemann/dash/version.rb +4 -0
  42. data/lib/riemann/dash/views/css.scss +393 -0
  43. data/lib/riemann/dash/views/index.erubis +203 -0
  44. data/lib/riemann/dash/views/layout.erubis +21 -0
  45. data/riemann-dash.gemspec +28 -0
  46. data/sh/c +1 -0
  47. data/sh/env.rb +2 -0
  48. data/sh/test +1 -0
  49. data/test/config_test.rb +106 -0
  50. data/test/fixtures/config/basic_config.rb +2 -0
  51. data/test/fixtures/config/ws_config.rb +1 -0
  52. data/test/fixtures/ws_config/dummy_config.json +1 -0
  53. data/test/fixtures/ws_config/pretty_printed_config.json +6 -0
  54. data/test/test_helper.rb +10 -0
  55. metadata +202 -0
@@ -0,0 +1,26 @@
1
+ /*
2
+ * SimpleModal 1.4.3 - jQuery Plugin
3
+ * http://simplemodal.com/
4
+ * Copyright (c) 2012 Eric Martin
5
+ * Licensed under MIT and GPL
6
+ * Date: Sat, Sep 8 2012 07:52:31 -0700
7
+ */
8
+ (function(b){"function"===typeof define&&define.amd?define(["jquery"],b):b(jQuery)})(function(b){var j=[],l=b(document),m=b.browser.msie&&6===parseInt(b.browser.version)&&"object"!==typeof window.XMLHttpRequest,o=b.browser.msie&&7===parseInt(b.browser.version),n=null,k=b(window),h=[];b.modal=function(a,d){return b.modal.impl.init(a,d)};b.modal.close=function(){b.modal.impl.close()};b.modal.focus=function(a){b.modal.impl.focus(a)};b.modal.setContainerDimensions=function(){b.modal.impl.setContainerDimensions()};
9
+ b.modal.setPosition=function(){b.modal.impl.setPosition()};b.modal.update=function(a,d){b.modal.impl.update(a,d)};b.fn.modal=function(a){return b.modal.impl.init(this,a)};b.modal.defaults={appendTo:"body",focus:!0,opacity:50,overlayId:"simplemodal-overlay",overlayCss:{},containerId:"simplemodal-container",containerCss:{},dataId:"simplemodal-data",dataCss:{},minHeight:null,minWidth:null,maxHeight:null,maxWidth:null,autoResize:!1,autoPosition:!0,zIndex:1E3,close:!0,closeHTML:'<a class="modalCloseImg" title="Close"></a>',
10
+ closeClass:"simplemodal-close",escClose:!0,overlayClose:!1,fixed:!0,position:null,persist:!1,modal:!0,onOpen:null,onShow:null,onClose:null};b.modal.impl={d:{},init:function(a,d){if(this.d.data)return!1;n=b.browser.msie&&!b.support.boxModel;this.o=b.extend({},b.modal.defaults,d);this.zIndex=this.o.zIndex;this.occb=!1;if("object"===typeof a){if(a=a instanceof b?a:b(a),this.d.placeholder=!1,0<a.parent().parent().size()&&(a.before(b("<span></span>").attr("id","simplemodal-placeholder").css({display:"none"})),
11
+ this.d.placeholder=!0,this.display=a.css("display"),!this.o.persist))this.d.orig=a.clone(!0)}else if("string"===typeof a||"number"===typeof a)a=b("<div></div>").html(a);else return alert("SimpleModal Error: Unsupported data type: "+typeof a),this;this.create(a);this.open();b.isFunction(this.o.onShow)&&this.o.onShow.apply(this,[this.d]);return this},create:function(a){this.getDimensions();if(this.o.modal&&m)this.d.iframe=b('<iframe src="javascript:false;"></iframe>').css(b.extend(this.o.iframeCss,
12
+ {display:"none",opacity:0,position:"fixed",height:h[0],width:h[1],zIndex:this.o.zIndex,top:0,left:0})).appendTo(this.o.appendTo);this.d.overlay=b("<div></div>").attr("id",this.o.overlayId).addClass("simplemodal-overlay").css(b.extend(this.o.overlayCss,{display:"none",opacity:this.o.opacity/100,height:this.o.modal?j[0]:0,width:this.o.modal?j[1]:0,position:"fixed",left:0,top:0,zIndex:this.o.zIndex+1})).appendTo(this.o.appendTo);this.d.container=b("<div></div>").attr("id",this.o.containerId).addClass("simplemodal-container").css(b.extend({position:this.o.fixed?
13
+ "fixed":"absolute"},this.o.containerCss,{display:"none",zIndex:this.o.zIndex+2})).append(this.o.close&&this.o.closeHTML?b(this.o.closeHTML).addClass(this.o.closeClass):"").appendTo(this.o.appendTo);this.d.wrap=b("<div></div>").attr("tabIndex",-1).addClass("simplemodal-wrap").css({height:"100%",outline:0,width:"100%"}).appendTo(this.d.container);this.d.data=a.attr("id",a.attr("id")||this.o.dataId).addClass("simplemodal-data").css(b.extend(this.o.dataCss,{display:"none"})).appendTo("body");this.setContainerDimensions();
14
+ this.d.data.appendTo(this.d.wrap);(m||n)&&this.fixIE()},bindEvents:function(){var a=this;b("."+a.o.closeClass).bind("click.simplemodal",function(b){b.preventDefault();a.close()});a.o.modal&&a.o.close&&a.o.overlayClose&&a.d.overlay.bind("click.simplemodal",function(b){b.preventDefault();a.close()});l.bind("keydown.simplemodal",function(b){a.o.modal&&9===b.keyCode?a.watchTab(b):a.o.close&&a.o.escClose&&27===b.keyCode&&(b.preventDefault(),a.close())});k.bind("resize.simplemodal orientationchange.simplemodal",
15
+ function(){a.getDimensions();a.o.autoResize?a.setContainerDimensions():a.o.autoPosition&&a.setPosition();m||n?a.fixIE():a.o.modal&&(a.d.iframe&&a.d.iframe.css({height:h[0],width:h[1]}),a.d.overlay.css({height:j[0],width:j[1]}))})},unbindEvents:function(){b("."+this.o.closeClass).unbind("click.simplemodal");l.unbind("keydown.simplemodal");k.unbind(".simplemodal");this.d.overlay.unbind("click.simplemodal")},fixIE:function(){var a=this.o.position;b.each([this.d.iframe||null,!this.o.modal?null:this.d.overlay,
16
+ "fixed"===this.d.container.css("position")?this.d.container:null],function(b,f){if(f){var g=f[0].style;g.position="absolute";if(2>b)g.removeExpression("height"),g.removeExpression("width"),g.setExpression("height",'document.body.scrollHeight > document.body.clientHeight ? document.body.scrollHeight : document.body.clientHeight + "px"'),g.setExpression("width",'document.body.scrollWidth > document.body.clientWidth ? document.body.scrollWidth : document.body.clientWidth + "px"');else{var c,e;a&&a.constructor===
17
+ Array?(c=a[0]?"number"===typeof a[0]?a[0].toString():a[0].replace(/px/,""):f.css("top").replace(/px/,""),c=-1===c.indexOf("%")?c+' + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"':parseInt(c.replace(/%/,""))+' * ((document.documentElement.clientHeight || document.body.clientHeight) / 100) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"',a[1]&&(e="number"===typeof a[1]?
18
+ a[1].toString():a[1].replace(/px/,""),e=-1===e.indexOf("%")?e+' + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"':parseInt(e.replace(/%/,""))+' * ((document.documentElement.clientWidth || document.body.clientWidth) / 100) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"')):(c='(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"',
19
+ e='(document.documentElement.clientWidth || document.body.clientWidth) / 2 - (this.offsetWidth / 2) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"');g.removeExpression("top");g.removeExpression("left");g.setExpression("top",c);g.setExpression("left",e)}}})},focus:function(a){var d=this,a=a&&-1!==b.inArray(a,["first","last"])?a:"first",f=b(":input:enabled:visible:"+a,d.d.wrap);setTimeout(function(){0<f.length?f.focus():d.d.wrap.focus()},
20
+ 10)},getDimensions:function(){var a="undefined"===typeof window.innerHeight?k.height():window.innerHeight;j=[l.height(),l.width()];h=[a,k.width()]},getVal:function(a,b){return a?"number"===typeof a?a:"auto"===a?0:0<a.indexOf("%")?parseInt(a.replace(/%/,""))/100*("h"===b?h[0]:h[1]):parseInt(a.replace(/px/,"")):null},update:function(a,b){if(!this.d.data)return!1;this.d.origHeight=this.getVal(a,"h");this.d.origWidth=this.getVal(b,"w");this.d.data.hide();a&&this.d.container.css("height",a);b&&this.d.container.css("width",
21
+ b);this.setContainerDimensions();this.d.data.show();this.o.focus&&this.focus();this.unbindEvents();this.bindEvents()},setContainerDimensions:function(){var a=m||o,d=this.d.origHeight?this.d.origHeight:b.browser.opera?this.d.container.height():this.getVal(a?this.d.container[0].currentStyle.height:this.d.container.css("height"),"h"),a=this.d.origWidth?this.d.origWidth:b.browser.opera?this.d.container.width():this.getVal(a?this.d.container[0].currentStyle.width:this.d.container.css("width"),"w"),f=this.d.data.outerHeight(!0),
22
+ g=this.d.data.outerWidth(!0);this.d.origHeight=this.d.origHeight||d;this.d.origWidth=this.d.origWidth||a;var c=this.o.maxHeight?this.getVal(this.o.maxHeight,"h"):null,e=this.o.maxWidth?this.getVal(this.o.maxWidth,"w"):null,c=c&&c<h[0]?c:h[0],e=e&&e<h[1]?e:h[1],i=this.o.minHeight?this.getVal(this.o.minHeight,"h"):"auto",d=d?this.o.autoResize&&d>c?c:d<i?i:d:f?f>c?c:this.o.minHeight&&"auto"!==i&&f<i?i:f:i,c=this.o.minWidth?this.getVal(this.o.minWidth,"w"):"auto",a=a?this.o.autoResize&&a>e?e:a<c?c:a:
23
+ g?g>e?e:this.o.minWidth&&"auto"!==c&&g<c?c:g:c;this.d.container.css({height:d,width:a});this.d.wrap.css({overflow:f>d||g>a?"auto":"visible"});this.o.autoPosition&&this.setPosition()},setPosition:function(){var a,b;a=h[0]/2-this.d.container.outerHeight(!0)/2;b=h[1]/2-this.d.container.outerWidth(!0)/2;var f="fixed"!==this.d.container.css("position")?k.scrollTop():0;this.o.position&&"[object Array]"===Object.prototype.toString.call(this.o.position)?(a=f+(this.o.position[0]||a),b=this.o.position[1]||
24
+ b):a=f+a;this.d.container.css({left:b,top:a})},watchTab:function(a){if(0<b(a.target).parents(".simplemodal-container").length){if(this.inputs=b(":input:enabled:visible:first, :input:enabled:visible:last",this.d.data[0]),!a.shiftKey&&a.target===this.inputs[this.inputs.length-1]||a.shiftKey&&a.target===this.inputs[0]||0===this.inputs.length)a.preventDefault(),this.focus(a.shiftKey?"last":"first")}else a.preventDefault(),this.focus()},open:function(){this.d.iframe&&this.d.iframe.show();b.isFunction(this.o.onOpen)?
25
+ this.o.onOpen.apply(this,[this.d]):(this.d.overlay.show(),this.d.container.show(),this.d.data.show());this.o.focus&&this.focus();this.bindEvents()},close:function(){if(!this.d.data)return!1;this.unbindEvents();if(b.isFunction(this.o.onClose)&&!this.occb)this.occb=!0,this.o.onClose.apply(this,[this.d]);else{if(this.d.placeholder){var a=b("#simplemodal-placeholder");this.o.persist?a.replaceWith(this.d.data.removeClass("simplemodal-data").css("display",this.display)):(this.d.data.hide().remove(),a.replaceWith(this.d.orig))}else this.d.data.hide().remove();
26
+ this.d.container.hide().remove();this.d.overlay.hide();this.d.iframe&&this.d.iframe.hide().remove();this.d.overlay.remove();this.d={}}}}});
@@ -0,0 +1,46 @@
1
+ var keys = (function() {
2
+ var active = true;
3
+
4
+ var bindings = {};
5
+
6
+ // Disable bindings.
7
+ var disable = function() {
8
+ active = false;
9
+ }
10
+
11
+ // Enable bindings.
12
+ var enable = function() {
13
+ active = true;
14
+ }
15
+
16
+ // Bind a key.
17
+ var bind = function(code, fn) {
18
+ if (bindings[code] === undefined) {
19
+ bindings[code] = [];
20
+ }
21
+ bindings[code].push(fn);
22
+ }
23
+
24
+ // React to key presses.
25
+ $(document).bind('keydown', function(ev) {
26
+ if (active === false) {
27
+ return;
28
+ }
29
+
30
+ console.log(ev.which);
31
+
32
+ var fns = bindings[ev.which];
33
+ if (fns !== undefined) {
34
+ fns.forEach(function(fn) { fn(ev); });
35
+ // ev.preventDefault();
36
+ }
37
+ });
38
+
39
+ return {
40
+ active: function() { return active; },
41
+ bindings: function() { return bindings; },
42
+ bind: bind,
43
+ enable: enable,
44
+ disable: disable
45
+ }
46
+ })();
@@ -0,0 +1,597 @@
1
+ /*!
2
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
3
+ * http://github.com/janl/mustache.js
4
+ */
5
+ var Mustache = (typeof module !== "undefined" && module.exports) || {};
6
+
7
+ (function (exports) {
8
+
9
+ exports.name = "mustache.js";
10
+ exports.version = "0.5.1-dev";
11
+ exports.tags = ["{{", "}}"];
12
+
13
+ exports.parse = parse;
14
+ exports.clearCache = clearCache;
15
+ exports.compile = compile;
16
+ exports.compilePartial = compilePartial;
17
+ exports.render = render;
18
+
19
+ exports.Scanner = Scanner;
20
+ exports.Context = Context;
21
+ exports.Renderer = Renderer;
22
+
23
+ // This is here for backwards compatibility with 0.4.x.
24
+ exports.to_html = function (template, view, partials, send) {
25
+ var result = render(template, view, partials);
26
+
27
+ if (typeof send === "function") {
28
+ send(result);
29
+ } else {
30
+ return result;
31
+ }
32
+ };
33
+
34
+ var whiteRe = /\s*/;
35
+ var spaceRe = /\s+/;
36
+ var nonSpaceRe = /\S/;
37
+ var eqRe = /\s*=/;
38
+ var curlyRe = /\s*\}/;
39
+ var tagRe = /#|\^|\/|>|\{|&|=|!/;
40
+
41
+ // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
42
+ // See https://github.com/janl/mustache.js/issues/189
43
+ function testRe(re, string) {
44
+ return RegExp.prototype.test.call(re, string);
45
+ }
46
+
47
+ function isWhitespace(string) {
48
+ return !testRe(nonSpaceRe, string);
49
+ }
50
+
51
+ var isArray = Array.isArray || function (obj) {
52
+ return Object.prototype.toString.call(obj) === "[object Array]";
53
+ };
54
+
55
+ // OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
56
+ var jsCharsRe = /[\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\xFF\u2028\u2029]/gm;
57
+
58
+ function quote(text) {
59
+ var escaped = text.replace(jsCharsRe, function (c) {
60
+ return "\\u" + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
61
+ });
62
+
63
+ return '"' + escaped + '"';
64
+ }
65
+
66
+ function escapeRe(string) {
67
+ return string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
68
+ }
69
+
70
+ var entityMap = {
71
+ "&": "&amp;",
72
+ "<": "&lt;",
73
+ ">": "&gt;",
74
+ '"': '&quot;',
75
+ "'": '&#39;',
76
+ "/": '&#x2F;'
77
+ };
78
+
79
+ function escapeHtml(string) {
80
+ return String(string).replace(/[&<>"'\/]/g, function (s) {
81
+ return entityMap[s];
82
+ });
83
+ }
84
+
85
+ // Export these utility functions.
86
+ exports.isWhitespace = isWhitespace;
87
+ exports.isArray = isArray;
88
+ exports.quote = quote;
89
+ exports.escapeRe = escapeRe;
90
+ exports.escapeHtml = escapeHtml;
91
+
92
+ function Scanner(string) {
93
+ this.string = string;
94
+ this.tail = string;
95
+ this.pos = 0;
96
+ }
97
+
98
+ /**
99
+ * Returns `true` if the tail is empty (end of string).
100
+ */
101
+ Scanner.prototype.eos = function () {
102
+ return this.tail === "";
103
+ };
104
+
105
+ /**
106
+ * Tries to match the given regular expression at the current position.
107
+ * Returns the matched text if it can match, `null` otherwise.
108
+ */
109
+ Scanner.prototype.scan = function (re) {
110
+ var match = this.tail.match(re);
111
+
112
+ if (match && match.index === 0) {
113
+ this.tail = this.tail.substring(match[0].length);
114
+ this.pos += match[0].length;
115
+ return match[0];
116
+ }
117
+
118
+ return null;
119
+ };
120
+
121
+ /**
122
+ * Skips all text until the given regular expression can be matched. Returns
123
+ * the skipped string, which is the entire tail of this scanner if no match
124
+ * can be made.
125
+ */
126
+ Scanner.prototype.scanUntil = function (re) {
127
+ var match, pos = this.tail.search(re);
128
+
129
+ switch (pos) {
130
+ case -1:
131
+ match = this.tail;
132
+ this.pos += this.tail.length;
133
+ this.tail = "";
134
+ break;
135
+ case 0:
136
+ match = null;
137
+ break;
138
+ default:
139
+ match = this.tail.substring(0, pos);
140
+ this.tail = this.tail.substring(pos);
141
+ this.pos += pos;
142
+ }
143
+
144
+ return match;
145
+ };
146
+
147
+ function Context(view, parent) {
148
+ this.view = view;
149
+ this.parent = parent;
150
+ this.clearCache();
151
+ }
152
+
153
+ Context.make = function (view) {
154
+ return (view instanceof Context) ? view : new Context(view);
155
+ };
156
+
157
+ Context.prototype.clearCache = function () {
158
+ this._cache = {};
159
+ };
160
+
161
+ Context.prototype.push = function (view) {
162
+ return new Context(view, this);
163
+ };
164
+
165
+ Context.prototype.lookup = function (name) {
166
+ var value = this._cache[name];
167
+
168
+ if (!value) {
169
+ if (name === ".") {
170
+ value = this.view;
171
+ } else {
172
+ var context = this;
173
+
174
+ while (context) {
175
+ if (name.indexOf(".") > 0) {
176
+ var names = name.split("."), i = 0;
177
+
178
+ value = context.view;
179
+
180
+ while (value && i < names.length) {
181
+ value = value[names[i++]];
182
+ }
183
+ } else {
184
+ value = context.view[name];
185
+ }
186
+
187
+ if (value != null) {
188
+ break;
189
+ }
190
+
191
+ context = context.parent;
192
+ }
193
+ }
194
+
195
+ this._cache[name] = value;
196
+ }
197
+
198
+ if (typeof value === "function") {
199
+ value = value.call(this.view);
200
+ }
201
+
202
+ return value;
203
+ };
204
+
205
+ function Renderer() {
206
+ this.clearCache();
207
+ }
208
+
209
+ Renderer.prototype.clearCache = function () {
210
+ this._cache = {};
211
+ this._partialCache = {};
212
+ };
213
+
214
+ Renderer.prototype.compile = function (tokens, tags) {
215
+ var fn = compileTokens(tokens),
216
+ self = this;
217
+
218
+ return function (view) {
219
+ return fn(Context.make(view), self);
220
+ };
221
+ };
222
+
223
+ Renderer.prototype.compilePartial = function (name, tokens, tags) {
224
+ this._partialCache[name] = this.compile(tokens, tags);
225
+ return this._partialCache[name];
226
+ };
227
+
228
+ Renderer.prototype.render = function (template, view) {
229
+ var fn = this._cache[template];
230
+
231
+ if (!fn) {
232
+ fn = this.compile(template);
233
+ this._cache[template] = fn;
234
+ }
235
+
236
+ return fn(view);
237
+ };
238
+
239
+ Renderer.prototype._section = function (name, context, callback) {
240
+ var value = context.lookup(name);
241
+
242
+ switch (typeof value) {
243
+ case "object":
244
+ if (isArray(value)) {
245
+ var buffer = "";
246
+ for (var i = 0, len = value.length; i < len; ++i) {
247
+ buffer += callback(context.push(value[i]), this);
248
+ }
249
+ return buffer;
250
+ } else {
251
+ return callback(context.push(value), this);
252
+ }
253
+ break;
254
+ case "function":
255
+ var sectionText = callback(context, this), self = this;
256
+ var scopedRender = function (template) {
257
+ return self.render(template, context);
258
+ };
259
+ return value.call(context.view, sectionText, scopedRender) || "";
260
+ break;
261
+ default:
262
+ if (value) {
263
+ return callback(context, this);
264
+ }
265
+ }
266
+
267
+ return "";
268
+ };
269
+
270
+ Renderer.prototype._inverted = function (name, context, callback) {
271
+ var value = context.lookup(name);
272
+
273
+ // From the spec: inverted sections may render text once based on the
274
+ // inverse value of the key. That is, they will be rendered if the key
275
+ // doesn't exist, is false, or is an empty list.
276
+ if (value == null || value === false || (isArray(value) && value.length === 0)) {
277
+ return callback(context, this);
278
+ }
279
+
280
+ return "";
281
+ };
282
+
283
+ Renderer.prototype._partial = function (name, context) {
284
+ var fn = this._partialCache[name];
285
+
286
+ if (fn) {
287
+ return fn(context, this);
288
+ }
289
+
290
+ return "";
291
+ };
292
+
293
+ Renderer.prototype._name = function (name, context, escape) {
294
+ var value = context.lookup(name);
295
+
296
+ if (typeof value === "function") {
297
+ value = value.call(context.view);
298
+ }
299
+
300
+ var string = (value == null) ? "" : String(value);
301
+
302
+ if (escape) {
303
+ return escapeHtml(string);
304
+ }
305
+
306
+ return string;
307
+ };
308
+
309
+ /**
310
+ * Low-level function that compiles the given `tokens` into a
311
+ * function that accepts two arguments: a Context and a
312
+ * Renderer. Returns the body of the function as a string if
313
+ * `returnBody` is true.
314
+ */
315
+ function compileTokens(tokens, returnBody) {
316
+ if (typeof tokens === "string") {
317
+ tokens = parse(tokens);
318
+ }
319
+
320
+ var body = ['""'];
321
+ var token, method, escape;
322
+
323
+ for (var i = 0, len = tokens.length; i < len; ++i) {
324
+ token = tokens[i];
325
+
326
+ switch (token.type) {
327
+ case "#":
328
+ case "^":
329
+ method = (token.type === "#") ? "_section" : "_inverted";
330
+ body.push("r." + method + "(" + quote(token.value) + ", c, function (c, r) {\n" +
331
+ " " + compileTokens(token.tokens, true) + "\n" +
332
+ "})");
333
+ break;
334
+ case "{":
335
+ case "&":
336
+ case "name":
337
+ escape = token.type === "name" ? "true" : "false";
338
+ body.push("r._name(" + quote(token.value) + ", c, " + escape + ")");
339
+ break;
340
+ case ">":
341
+ body.push("r._partial(" + quote(token.value) + ", c)");
342
+ break;
343
+ case "text":
344
+ body.push(quote(token.value));
345
+ break;
346
+ }
347
+ }
348
+
349
+ // Convert to a string body.
350
+ body = "return " + body.join(" + ") + ";";
351
+
352
+ // Good for debugging.
353
+ // console.log(body);
354
+
355
+ if (returnBody) {
356
+ return body;
357
+ }
358
+
359
+ // For great evil!
360
+ return new Function("c, r", body);
361
+ }
362
+
363
+ function escapeTags(tags) {
364
+ if (tags.length === 2) {
365
+ return [
366
+ new RegExp(escapeRe(tags[0]) + "\\s*"),
367
+ new RegExp("\\s*" + escapeRe(tags[1]))
368
+ ];
369
+ }
370
+
371
+ throw new Error("Invalid tags: " + tags.join(" "));
372
+ }
373
+
374
+ /**
375
+ * Forms the given linear array of `tokens` into a nested tree structure
376
+ * where tokens that represent a section have a "tokens" array property
377
+ * that contains all tokens that are in that section.
378
+ */
379
+ function nestTokens(tokens) {
380
+ var tree = [];
381
+ var collector = tree;
382
+ var sections = [];
383
+ var token, section;
384
+
385
+ for (var i = 0; i < tokens.length; ++i) {
386
+ token = tokens[i];
387
+
388
+ switch (token.type) {
389
+ case "#":
390
+ case "^":
391
+ token.tokens = [];
392
+ sections.push(token);
393
+ collector.push(token);
394
+ collector = token.tokens;
395
+ break;
396
+ case "/":
397
+ if (sections.length === 0) {
398
+ throw new Error("Unopened section: " + token.value);
399
+ }
400
+
401
+ section = sections.pop();
402
+
403
+ if (section.value !== token.value) {
404
+ throw new Error("Unclosed section: " + section.value);
405
+ }
406
+
407
+ if (sections.length > 0) {
408
+ collector = sections[sections.length - 1].tokens;
409
+ } else {
410
+ collector = tree;
411
+ }
412
+ break;
413
+ default:
414
+ collector.push(token);
415
+ }
416
+ }
417
+
418
+ // Make sure there were no open sections when we're done.
419
+ section = sections.pop();
420
+
421
+ if (section) {
422
+ throw new Error("Unclosed section: " + section.value);
423
+ }
424
+
425
+ return tree;
426
+ }
427
+
428
+ /**
429
+ * Combines the values of consecutive text tokens in the given `tokens` array
430
+ * to a single token.
431
+ */
432
+ function squashTokens(tokens) {
433
+ var lastToken;
434
+
435
+ for (var i = 0; i < tokens.length; ++i) {
436
+ token = tokens[i];
437
+
438
+ if (lastToken && lastToken.type === "text" && token.type === "text") {
439
+ lastToken.value += token.value;
440
+ tokens.splice(i--, 1); // Remove this token from the array.
441
+ } else {
442
+ lastToken = token;
443
+ }
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Breaks up the given `template` string into a tree of token objects. If
449
+ * `tags` is given here it must be an array with two string values: the
450
+ * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
451
+ * course, the default is to use mustaches (i.e. Mustache.tags).
452
+ */
453
+ function parse(template, tags) {
454
+ tags = tags || exports.tags;
455
+ tagRes = escapeTags(tags);
456
+
457
+ var scanner = new Scanner(template);
458
+
459
+ var tokens = [], // Buffer to hold the tokens
460
+ spaces = [], // Indices of whitespace tokens on the current line
461
+ hasTag = false, // Is there a {{tag}} on the current line?
462
+ nonSpace = false; // Is there a non-space char on the current line?
463
+
464
+ // Strips all whitespace tokens array for the current line
465
+ // if there was a {{#tag}} on it and otherwise only space.
466
+ var stripSpace = function () {
467
+ if (hasTag && !nonSpace) {
468
+ while (spaces.length) {
469
+ tokens.splice(spaces.pop(), 1);
470
+ }
471
+ } else {
472
+ spaces = [];
473
+ }
474
+
475
+ hasTag = false;
476
+ nonSpace = false;
477
+ };
478
+
479
+ var type, value, chr;
480
+
481
+ while (!scanner.eos()) {
482
+ value = scanner.scanUntil(tagRes[0]);
483
+
484
+ if (value) {
485
+ for (var i = 0, len = value.length; i < len; ++i) {
486
+ chr = value[i];
487
+
488
+ if (isWhitespace(chr)) {
489
+ spaces.push(tokens.length);
490
+ } else {
491
+ nonSpace = true;
492
+ }
493
+
494
+ tokens.push({type: "text", value: chr});
495
+
496
+ if (chr === "\n") {
497
+ stripSpace(); // Check for whitespace on the current line.
498
+ }
499
+ }
500
+ }
501
+
502
+ // Match the opening tag.
503
+ if (!scanner.scan(tagRes[0])) {
504
+ break;
505
+ }
506
+
507
+ hasTag = true;
508
+ type = scanner.scan(tagRe) || "name";
509
+
510
+ // Skip any whitespace between tag and value.
511
+ scanner.scan(whiteRe);
512
+
513
+ // Extract the tag value.
514
+ if (type === "=") {
515
+ value = scanner.scanUntil(eqRe);
516
+ scanner.scan(eqRe);
517
+ scanner.scanUntil(tagRes[1]);
518
+ } else if (type === "{") {
519
+ var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1]));
520
+ value = scanner.scanUntil(closeRe);
521
+ scanner.scan(curlyRe);
522
+ scanner.scanUntil(tagRes[1]);
523
+ } else {
524
+ value = scanner.scanUntil(tagRes[1]);
525
+ }
526
+
527
+ // Match the closing tag.
528
+ if (!scanner.scan(tagRes[1])) {
529
+ throw new Error("Unclosed tag at " + scanner.pos);
530
+ }
531
+
532
+ tokens.push({type: type, value: value});
533
+
534
+ if (type === "name" || type === "{" || type === "&") {
535
+ nonSpace = true;
536
+ }
537
+
538
+ // Set the tags for the next time around.
539
+ if (type === "=") {
540
+ tags = value.split(spaceRe);
541
+ tagRes = escapeTags(tags);
542
+ }
543
+ }
544
+
545
+ squashTokens(tokens);
546
+
547
+ return nestTokens(tokens);
548
+ }
549
+
550
+ // The high-level clearCache, compile, compilePartial, and render functions
551
+ // use this default renderer.
552
+ var _renderer = new Renderer;
553
+
554
+ /**
555
+ * Clears all cached templates and partials.
556
+ */
557
+ function clearCache() {
558
+ _renderer.clearCache();
559
+ }
560
+
561
+ /**
562
+ * High-level API for compiling the given `tokens` down to a reusable
563
+ * function. If `tokens` is a string it will be parsed using the given `tags`
564
+ * before it is compiled.
565
+ */
566
+ function compile(tokens, tags) {
567
+ return _renderer.compile(tokens, tags);
568
+ }
569
+
570
+ /**
571
+ * High-level API for compiling the `tokens` for the partial with the given
572
+ * `name` down to a reusable function. If `tokens` is a string it will be
573
+ * parsed using the given `tags` before it is compiled.
574
+ */
575
+ function compilePartial(name, tokens, tags) {
576
+ return _renderer.compilePartial(name, tokens, tags);
577
+ }
578
+
579
+ /**
580
+ * High-level API for rendering the `template` using the given `view`. The
581
+ * optional `partials` object may be given here for convenience, but note that
582
+ * it will cause all partials to be re-compiled, thus hurting performance. Of
583
+ * course, this only matters if you're going to render the same template more
584
+ * than once. If so, it is best to call `compilePartial` before calling this
585
+ * function and to leave the `partials` argument blank.
586
+ */
587
+ function render(template, view, partials) {
588
+ if (partials) {
589
+ for (var name in partials) {
590
+ compilePartial(name, partials[name]);
591
+ }
592
+ }
593
+
594
+ return _renderer.render(template, view);
595
+ }
596
+
597
+ })(Mustache);