riemann-dash 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +1 -1
- data/lib/riemann/dash.rb +5 -2
- data/lib/riemann/dash/controller/websockets.rb +54 -0
- data/lib/riemann/dash/helper/renderer.rb +1 -1
- data/lib/riemann/dash/public/clock.js +45 -0
- data/lib/riemann/dash/public/dash.js +127 -0
- data/lib/riemann/dash/public/format.js +24 -0
- data/lib/riemann/dash/public/gauge.js +76 -0
- data/lib/riemann/dash/public/help.js +27 -0
- data/lib/riemann/dash/public/jquery-1.7.2.min.js +4 -0
- data/lib/riemann/dash/public/jquery.json-2.2.min.js +31 -0
- data/lib/riemann/dash/public/jquery.quickfit.js +144 -0
- data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +26 -0
- data/lib/riemann/dash/public/keys.js +45 -0
- data/lib/riemann/dash/public/mustache.js +597 -0
- data/lib/riemann/dash/public/persistence.js +30 -0
- data/lib/riemann/dash/public/subs.js +28 -0
- data/lib/riemann/dash/public/title.js +33 -0
- data/lib/riemann/dash/public/view.js +684 -0
- data/lib/riemann/dash/public/x.png +0 -0
- data/lib/riemann/dash/rack/static.rb +3 -3
- data/lib/riemann/dash/version.rb +1 -1
- data/lib/riemann/dash/views/css.scss +172 -9
- data/lib/riemann/dash/views/websockets.erubis +196 -0
- metadata +22 -4
@@ -0,0 +1,144 @@
|
|
1
|
+
|
2
|
+
(function($) {
|
3
|
+
var Quickfit, QuickfitHelper, defaults, pluginName;
|
4
|
+
pluginName = 'quickfit';
|
5
|
+
defaults = {
|
6
|
+
min: 8,
|
7
|
+
max: 12,
|
8
|
+
tolerance: 0.02,
|
9
|
+
truncate: false,
|
10
|
+
width: null,
|
11
|
+
height: null,
|
12
|
+
sample_number_of_letters: 10,
|
13
|
+
sample_font_size: 12,
|
14
|
+
font_height_scale: 0.85
|
15
|
+
};
|
16
|
+
QuickfitHelper = (function() {
|
17
|
+
var shared_instance;
|
18
|
+
|
19
|
+
shared_instance = null;
|
20
|
+
|
21
|
+
QuickfitHelper.instance = function(options) {
|
22
|
+
if (!shared_instance) shared_instance = new QuickfitHelper(options);
|
23
|
+
return shared_instance;
|
24
|
+
};
|
25
|
+
|
26
|
+
function QuickfitHelper(options) {
|
27
|
+
this.options = options;
|
28
|
+
this.item = $('<span id="meassure"></span>');
|
29
|
+
this.item.css({
|
30
|
+
position: 'absolute',
|
31
|
+
left: '-1000px',
|
32
|
+
top: '-1000px',
|
33
|
+
'font-size': "" + this.options.sample_font_size + "px"
|
34
|
+
});
|
35
|
+
$('body').append(this.item);
|
36
|
+
this.meassures = {};
|
37
|
+
}
|
38
|
+
|
39
|
+
QuickfitHelper.prototype.get_meassure = function(letter) {
|
40
|
+
var current_meassure;
|
41
|
+
current_meassure = this.meassures[letter];
|
42
|
+
if (current_meassure === void 0) {
|
43
|
+
current_meassure = this.set_meassure(letter);
|
44
|
+
}
|
45
|
+
return current_meassure;
|
46
|
+
};
|
47
|
+
|
48
|
+
QuickfitHelper.prototype.set_meassure = function(letter) {
|
49
|
+
var current_meassure, index, sample_letter, text, _ref;
|
50
|
+
text = '';
|
51
|
+
sample_letter = letter === ' ' ? ' ' : letter;
|
52
|
+
for (index = 0, _ref = this.options.sample_number_of_letters - 1; 0 <= _ref ? index <= _ref : index >= _ref; 0 <= _ref ? index++ : index--) {
|
53
|
+
text += sample_letter;
|
54
|
+
}
|
55
|
+
this.item.html(text);
|
56
|
+
current_meassure = this.item.width() / this.options.sample_number_of_letters / this.options.sample_font_size;
|
57
|
+
this.meassures[letter] = current_meassure;
|
58
|
+
return current_meassure;
|
59
|
+
};
|
60
|
+
|
61
|
+
return QuickfitHelper;
|
62
|
+
|
63
|
+
})();
|
64
|
+
Quickfit = (function() {
|
65
|
+
|
66
|
+
function Quickfit(element, options) {
|
67
|
+
this.element = element;
|
68
|
+
this.options = $.extend({}, defaults, options);
|
69
|
+
this.element = $(this.element);
|
70
|
+
this._defaults = defaults;
|
71
|
+
this._name = pluginName;
|
72
|
+
this.quickfit_helper = QuickfitHelper.instance(this.options);
|
73
|
+
}
|
74
|
+
|
75
|
+
Quickfit.prototype.fit = function() {
|
76
|
+
var element_width;
|
77
|
+
if (!this.options.width) {
|
78
|
+
element_width = this.element.width();
|
79
|
+
this.options.width = element_width - this.options.tolerance * element_width;
|
80
|
+
}
|
81
|
+
if (!this.options.height) {
|
82
|
+
this.options.height = this.element.height();
|
83
|
+
}
|
84
|
+
if (this.text = this.element.attr('data-quickfit')) {
|
85
|
+
this.previously_truncated = true;
|
86
|
+
} else {
|
87
|
+
this.text = this.element.html();
|
88
|
+
}
|
89
|
+
this.calculate_font_size();
|
90
|
+
if (this.options.truncate) this.truncate();
|
91
|
+
return this.element.css('font-size', "" + this.font_size + "px");
|
92
|
+
};
|
93
|
+
|
94
|
+
Quickfit.prototype.calculate_font_size = function() {
|
95
|
+
var letter, text_width, _i, _len, _ref;
|
96
|
+
text_width = 0;
|
97
|
+
_ref = this.text;
|
98
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
99
|
+
letter = _ref[_i];
|
100
|
+
text_width += this.quickfit_helper.get_meassure(letter);
|
101
|
+
}
|
102
|
+
var vertical_font_size = this.options.height * this.options.font_height_scale;
|
103
|
+
this.target_font_size = parseInt(this.options.width / text_width);
|
104
|
+
return this.font_size = Math.max(
|
105
|
+
this.options.min,
|
106
|
+
Math.min(
|
107
|
+
vertical_font_size,
|
108
|
+
this.options.max,
|
109
|
+
this.target_font_size));
|
110
|
+
};
|
111
|
+
|
112
|
+
Quickfit.prototype.truncate = function() {
|
113
|
+
var index, last_letter, letter, set_text, text_width;
|
114
|
+
if (this.font_size > this.target_font_size) {
|
115
|
+
set_text = '';
|
116
|
+
text_width = 3 * this.quickfit_helper.get_meassure('.') * this.font_size;
|
117
|
+
index = 0;
|
118
|
+
while (text_width < this.options.width && index < this.text.length) {
|
119
|
+
letter = this.text[index++];
|
120
|
+
if (last_letter) set_text += last_letter;
|
121
|
+
text_width += this.font_size * this.quickfit_helper.get_meassure(letter);
|
122
|
+
last_letter = letter;
|
123
|
+
}
|
124
|
+
if (set_text.length + 1 === this.text.length) {
|
125
|
+
set_text = this.text;
|
126
|
+
} else {
|
127
|
+
set_text += '...';
|
128
|
+
}
|
129
|
+
this.text_was_truncated = true;
|
130
|
+
return this.element.attr('data-quickfit', this.text).html(set_text);
|
131
|
+
} else {
|
132
|
+
if (this.previously_truncated) return this.element.html(this.text);
|
133
|
+
}
|
134
|
+
};
|
135
|
+
|
136
|
+
return Quickfit;
|
137
|
+
|
138
|
+
})();
|
139
|
+
return $.fn.quickfit = function(options) {
|
140
|
+
return this.each(function() {
|
141
|
+
return new Quickfit(this, options).fit();
|
142
|
+
});
|
143
|
+
};
|
144
|
+
})(jQuery, window);
|
@@ -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,45 @@
|
|
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
|
+
}
|
36
|
+
});
|
37
|
+
|
38
|
+
return {
|
39
|
+
active: function() { return active; },
|
40
|
+
bindings: function() { return bindings; },
|
41
|
+
bind: bind,
|
42
|
+
enable: enable,
|
43
|
+
disable: disable
|
44
|
+
}
|
45
|
+
})();
|
@@ -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
|
+
"&": "&",
|
72
|
+
"<": "<",
|
73
|
+
">": ">",
|
74
|
+
'"': '"',
|
75
|
+
"'": ''',
|
76
|
+
"/": '/'
|
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);
|