nutcracker-web 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +63 -0
  4. data/README.md +56 -0
  5. data/Rakefile +26 -0
  6. data/assets/javascripts/application.js +10 -0
  7. data/assets/javascripts/collections/clusters.js.coffee +4 -0
  8. data/assets/javascripts/collections/nodes.js.coffee +19 -0
  9. data/assets/javascripts/models/cluster.js.coffee +16 -0
  10. data/assets/javascripts/models/node.js.coffee +14 -0
  11. data/assets/javascripts/models/overview.js.coffee +27 -0
  12. data/assets/javascripts/nutcracker.js.coffee +20 -0
  13. data/assets/javascripts/routers/nutcracker_router.js.coffee +29 -0
  14. data/assets/javascripts/utils/region_manager.js.coffee +12 -0
  15. data/assets/javascripts/utils/underscore_ext.js.coffee +6 -0
  16. data/assets/javascripts/vendor/backbone.js +1571 -0
  17. data/assets/javascripts/vendor/bootstrap.js +2276 -0
  18. data/assets/javascripts/vendor/google_chart.js.coffee +124 -0
  19. data/assets/javascripts/vendor/humanize.js +459 -0
  20. data/assets/javascripts/vendor/jquery.js +8842 -0
  21. data/assets/javascripts/vendor/underscore.js +1227 -0
  22. data/assets/javascripts/views/cluster.js.coffee +58 -0
  23. data/assets/javascripts/views/config.js.coffee +6 -0
  24. data/assets/javascripts/views/footer.js.coffee +8 -0
  25. data/assets/javascripts/views/navbar.js.coffee +28 -0
  26. data/assets/javascripts/views/node.js.coffee +54 -0
  27. data/assets/javascripts/views/overview.js.coffee +6 -0
  28. data/assets/stylesheets/application.css +24 -0
  29. data/assets/stylesheets/bootstrap-responsive.css +1109 -0
  30. data/assets/stylesheets/bootstrap.css +6158 -0
  31. data/assets/templates/cluster.jst.eco +87 -0
  32. data/assets/templates/config.jst.eco +15 -0
  33. data/assets/templates/footer.jst.eco +1 -0
  34. data/assets/templates/navbar.jst.eco +55 -0
  35. data/assets/templates/node.jst.eco +73 -0
  36. data/assets/templates/overview.jst.eco +32 -0
  37. data/lib/nutcracker/web/app.rb +51 -0
  38. data/lib/nutcracker/web/version.rb +5 -0
  39. data/lib/nutcracker/web.rb +29 -0
  40. metadata +122 -0
@@ -0,0 +1,124 @@
1
+ class Backbone.GoogleChart extends Backbone.View
2
+ ###
3
+ # Initialize a new GoogleChart object
4
+ #
5
+ # Example:
6
+ # lineChart = new Backbone.GoogleChart({
7
+ # chartType: 'ColumnChart',
8
+ # dataTable: [['Germany', 'USA', 'Brazil', 'Canada', 'France', 'RU'],
9
+ # [700, 300, 400, 500, 600, 800]],
10
+ # options: {'title': 'Countries'},
11
+ # });
12
+ #
13
+ # $('body').append( lineChart.render().el );
14
+ #
15
+ # For the complete list of options please checkout
16
+ # https://developers.google.com/chart/interactive/docs/reference#chartwrapperobject
17
+ #
18
+ ###
19
+ initialize: ( options ) ->
20
+ chartOptions = _.extend({},options)
21
+ ['el','id','attributes','className','tagName'].map (key)=>
22
+ delete chartOptions[key]
23
+
24
+ google.load 'visualization', '1', packages: ['corechart'], callback: =>
25
+ @onGoogleLoad "loaded"
26
+
27
+ @onGoogleLoad =>
28
+ @google = google.visualization
29
+ if chartOptions.dataTable instanceof Array
30
+ chartOptions.dataTable = @google.arrayToDataTable chartOptions.dataTable
31
+ (chartOptions.beforeDraw||->) @, chartOptions
32
+
33
+ if formatter = chartOptions.formatter
34
+ [0..chartOptions.dataTable.getNumberOfRows()-1].map (index)=>
35
+ formatter.columns.map (column)=>
36
+ chartOptions.dataTable.setFormattedValue(
37
+ index, column, formatter.callback(
38
+ chartOptions.dataTable.getValue index, column
39
+ )
40
+ )
41
+
42
+ @wrapper = new @google.ChartWrapper chartOptions
43
+ ['ready','select', 'error'].map @listen
44
+
45
+ ###
46
+ # Execute a callback once google visualization fully loaded
47
+ ###
48
+ onGoogleLoad: ( callback )=>
49
+ if callback == "loaded"
50
+ @googleLoaded = true
51
+ _( @onGoogleLoadItems ).map (fn)-> fn()
52
+ else
53
+ if @googleLoaded
54
+ callback()
55
+ else
56
+ (@onGoogleLoadItems ||= []).push callback
57
+
58
+ ###
59
+ # Execute a callback once a given element ID appears in DOM ( mini livequery ).
60
+ #
61
+ # We need it because GoogleChart object only draw itself on DOM elements
62
+ # so we first need to wait for our element to be added to the DOM before
63
+ # we call GoogleChart.draw().
64
+ #
65
+ # Usage:
66
+ # Backbone.GoogleChart.watch("#myid", function() { console.log("I'm in") });
67
+ # $("body").append("<div id='myid'></div>"); // 'I"m in' should be printed to console
68
+ #
69
+ ###
70
+ @watch: ( id, fn ) =>
71
+ (@_list ||= {})[id] = fn
72
+ return if @_watching
73
+ @_watching = true
74
+
75
+ timeout = 10
76
+ (func = =>
77
+ _(@_list).map ( fn, id ) =>
78
+ fn() || delete @_list[id] if $(id)[0]
79
+ if _(@_list).isEmpty()
80
+ @_watching = false
81
+ else
82
+ setTimeout(func, timeout+=10)
83
+ )()
84
+
85
+ ###
86
+ # Returns the wrapping element id
87
+ # if no id was specified on initialization a random one will be returned
88
+ ###
89
+ id: =>
90
+ @el?.id or @randomID()
91
+
92
+ ###
93
+ # "Instruct" the current graph instance to draw itself once its visiable on DOM
94
+ # return the current instance
95
+ ###
96
+ render: =>
97
+ @onGoogleLoad =>
98
+ @constructor.watch "##{@el.id}", =>
99
+ @wrapper.draw @el.id
100
+ this
101
+
102
+ ###
103
+ # Register for ChartWrapper events
104
+ # For the complete event list please look at the events section under
105
+ # https://developers.google.com/chart/interactive/docs/reference#chartwrapperobject
106
+ #
107
+ # By default the ready, select and error events are register automatically on initialization
108
+ # so instead of calling this function directly consider this:
109
+ # graph = new Backbone.GoogleChart({chartOptions: options});
110
+ # graph.on("select",function(graph) { console.log("Someone click on me!") })
111
+ # graph.on("error",function(graph) { console.log("Oops") })
112
+ # graph.on("ready",function(graph) { console.log("I'm ready!") })
113
+ #
114
+ ###
115
+ listen: ( event ) =>
116
+ @google.events.addListener @wrapper, event, =>
117
+ @trigger event, @wrapper.getChart()
118
+
119
+ ###
120
+ # Generate a random ID, gc_XXX
121
+ ###
122
+ randomID: ->
123
+ _.uniqueId "gc_"
124
+
@@ -0,0 +1,459 @@
1
+ // https://github.com/taijinlee/humanize
2
+ (function() {
3
+
4
+ // Baseline setup
5
+ // --------------
6
+
7
+ // Establish the root object, `window` in the browser, or `global` on the server.
8
+ var root = this;
9
+
10
+ // Save the previous value of the `humanize` variable.
11
+ var previousHumanize = root.humanize;
12
+
13
+ var humanize = {};
14
+
15
+ if (typeof exports !== 'undefined') {
16
+ if (typeof module !== 'undefined' && module.exports) {
17
+ exports = module.exports = humanize;
18
+ }
19
+ exports.humanize = humanize;
20
+ } else {
21
+ if (typeof define === 'function' && define.amd) {
22
+ define('humanize', function() {
23
+ return humanize;
24
+ });
25
+ }
26
+ root.humanize = humanize;
27
+ }
28
+
29
+ humanize.noConflict = function() {
30
+ root.humanize = previousHumanize;
31
+ return this;
32
+ };
33
+
34
+ humanize.pad = function(str, count, padChar, type) {
35
+ str += '';
36
+ if (!padChar) {
37
+ padChar = ' ';
38
+ } else if (padChar.length > 1) {
39
+ padChar = padChar.charAt(0);
40
+ }
41
+ type = (type === undefined) ? 'left' : 'right';
42
+
43
+ if (type === 'right') {
44
+ while (str.length < count) {
45
+ str = str + padChar;
46
+ }
47
+ } else {
48
+ // default to left
49
+ while (str.length < count) {
50
+ str = padChar + str;
51
+ }
52
+ }
53
+
54
+ return str;
55
+ };
56
+
57
+ // gets current unix time
58
+ humanize.time = function() {
59
+ return new Date().getTime() / 1000;
60
+ };
61
+
62
+ /**
63
+ * PHP-inspired date
64
+ */
65
+
66
+ /* jan feb mar apr may jun jul aug sep oct nov dec */
67
+ var dayTableCommon = [ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];
68
+ var dayTableLeap = [ 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 ];
69
+ // var mtable_common[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
70
+ // static int ml_table_leap[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
71
+
72
+
73
+ humanize.date = function(format, timestamp) {
74
+ var jsdate = ((timestamp === undefined) ? new Date() : // Not provided
75
+ (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
76
+ new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
77
+ );
78
+
79
+ var formatChr = /\\?([a-z])/gi;
80
+ var formatChrCb = function (t, s) {
81
+ return f[t] ? f[t]() : s;
82
+ };
83
+
84
+ var shortDayTxt = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
85
+ var monthTxt = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
86
+
87
+ var f = {
88
+ /* Day */
89
+ // Day of month w/leading 0; 01..31
90
+ d: function () { return humanize.pad(f.j(), 2, '0'); },
91
+
92
+ // Shorthand day name; Mon..Sun
93
+ D: function () { return f.l().slice(0, 3); },
94
+
95
+ // Day of month; 1..31
96
+ j: function () { return jsdate.getDate(); },
97
+
98
+ // Full day name; Monday..Sunday
99
+ l: function () { return shortDayTxt[f.w()]; },
100
+
101
+ // ISO-8601 day of week; 1[Mon]..7[Sun]
102
+ N: function () { return f.w() || 7; },
103
+
104
+ // Ordinal suffix for day of month; st, nd, rd, th
105
+ S: function () {
106
+ var j = f.j();
107
+ return j > 4 && j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th';
108
+ },
109
+
110
+ // Day of week; 0[Sun]..6[Sat]
111
+ w: function () { return jsdate.getDay(); },
112
+
113
+ // Day of year; 0..365
114
+ z: function () {
115
+ return (f.L() ? dayTableLeap[f.n()] : dayTableCommon[f.n()]) + f.j() - 1;
116
+ },
117
+
118
+ /* Week */
119
+ // ISO-8601 week number
120
+ W: function () {
121
+ // days between midweek of this week and jan 4
122
+ // (f.z() - f.N() + 1 + 3.5) - 3
123
+ var midWeekDaysFromJan4 = f.z() - f.N() + 1.5;
124
+ // 1 + number of weeks + rounded week
125
+ return humanize.pad(1 + Math.floor(Math.abs(midWeekDaysFromJan4) / 7) + (midWeekDaysFromJan4 % 7 > 3.5 ? 1 : 0), 2, '0');
126
+ },
127
+
128
+ /* Month */
129
+ // Full month name; January..December
130
+ F: function () { return monthTxt[jsdate.getMonth()]; },
131
+
132
+ // Month w/leading 0; 01..12
133
+ m: function () { return humanize.pad(f.n(), 2, '0'); },
134
+
135
+ // Shorthand month name; Jan..Dec
136
+ M: function () { return f.F().slice(0, 3); },
137
+
138
+ // Month; 1..12
139
+ n: function () { return jsdate.getMonth() + 1; },
140
+
141
+ // Days in month; 28..31
142
+ t: function () { return (new Date(f.Y(), f.n(), 0)).getDate(); },
143
+
144
+ /* Year */
145
+ // Is leap year?; 0 or 1
146
+ L: function () { return new Date(f.Y(), 1, 29).getMonth() === 1 ? 1 : 0; },
147
+
148
+ // ISO-8601 year
149
+ o: function () {
150
+ var n = f.n();
151
+ var W = f.W();
152
+ return f.Y() + (n === 12 && W < 9 ? -1 : n === 1 && W > 9);
153
+ },
154
+
155
+ // Full year; e.g. 1980..2010
156
+ Y: function () { return jsdate.getFullYear(); },
157
+
158
+ // Last two digits of year; 00..99
159
+ y: function () { return (String(f.Y())).slice(-2); },
160
+
161
+ /* Time */
162
+ // am or pm
163
+ a: function () { return jsdate.getHours() > 11 ? 'pm' : 'am'; },
164
+
165
+ // AM or PM
166
+ A: function () { return f.a().toUpperCase(); },
167
+
168
+ // Swatch Internet time; 000..999
169
+ B: function () {
170
+ var unixTime = jsdate.getTime() / 1000;
171
+ var secondsPassedToday = unixTime % 86400 + 3600; // since it's based off of UTC+1
172
+ if (secondsPassedToday < 0) { secondsPassedToday += 86400; }
173
+ var beats = ((secondsPassedToday) / 86.4) % 1000;
174
+ if (unixTime < 0) {
175
+ return Math.ceil(beats);
176
+ }
177
+ return Math.floor(beats);
178
+ },
179
+
180
+ // 12-Hours; 1..12
181
+ g: function () { return f.G() % 12 || 12; },
182
+
183
+ // 24-Hours; 0..23
184
+ G: function () { return jsdate.getHours(); },
185
+
186
+ // 12-Hours w/leading 0; 01..12
187
+ h: function () { return humanize.pad(f.g(), 2, '0'); },
188
+
189
+ // 24-Hours w/leading 0; 00..23
190
+ H: function () { return humanize.pad(f.G(), 2, '0'); },
191
+
192
+ // Minutes w/leading 0; 00..59
193
+ i: function () { return humanize.pad(jsdate.getMinutes(), 2, '0'); },
194
+
195
+ // Seconds w/leading 0; 00..59
196
+ s: function () { return humanize.pad(jsdate.getSeconds(), 2, '0'); },
197
+
198
+ // Microseconds; 000000-999000
199
+ u: function () { return humanize.pad(jsdate.getMilliseconds() * 1000, 6, '0'); },
200
+
201
+ // Whether or not the date is in daylight savings time
202
+ /*
203
+ I: function () {
204
+ // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
205
+ // If they are not equal, then DST is observed.
206
+ var Y = f.Y();
207
+ return 0 + ((new Date(Y, 0) - Date.UTC(Y, 0)) !== (new Date(Y, 6) - Date.UTC(Y, 6)));
208
+ },
209
+ */
210
+
211
+ // Difference to GMT in hour format; e.g. +0200
212
+ O: function () {
213
+ var tzo = jsdate.getTimezoneOffset();
214
+ var tzoNum = Math.abs(tzo);
215
+ return (tzo > 0 ? '-' : '+') + humanize.pad(Math.floor(tzoNum / 60) * 100 + tzoNum % 60, 4, '0');
216
+ },
217
+
218
+ // Difference to GMT w/colon; e.g. +02:00
219
+ P: function () {
220
+ var O = f.O();
221
+ return (O.substr(0, 3) + ':' + O.substr(3, 2));
222
+ },
223
+
224
+ // Timezone offset in seconds (-43200..50400)
225
+ Z: function () { return -jsdate.getTimezoneOffset() * 60; },
226
+
227
+ // Full Date/Time, ISO-8601 date
228
+ c: function () { return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); },
229
+
230
+ // RFC 2822
231
+ r: function () { return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); },
232
+
233
+ // Seconds since UNIX epoch
234
+ U: function () { return jsdate.getTime() / 1000 || 0; }
235
+ };
236
+
237
+ return format.replace(formatChr, formatChrCb);
238
+ };
239
+
240
+
241
+ /**
242
+ * format number by adding thousands separaters and significant digits while rounding
243
+ */
244
+ humanize.numberFormat = function(number, decimals, decPoint, thousandsSep) {
245
+ decimals = isNaN(decimals) ? 2 : Math.abs(decimals);
246
+ decPoint = (decPoint === undefined) ? '.' : decPoint;
247
+ thousandsSep = (thousandsSep === undefined) ? ',' : thousandsSep;
248
+
249
+ var sign = number < 0 ? '-' : '';
250
+ number = Math.abs(+number || 0);
251
+
252
+ var intPart = parseInt(number.toFixed(decimals), 10) + '';
253
+ var j = intPart.length > 3 ? intPart.length % 3 : 0;
254
+
255
+ return sign + (j ? intPart.substr(0, j) + thousandsSep : '') + intPart.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep) + (decimals ? decPoint + Math.abs(number - intPart).toFixed(decimals).slice(2) : '');
256
+ };
257
+
258
+
259
+ /**
260
+ * For dates that are the current day or within one day, return 'today', 'tomorrow' or 'yesterday', as appropriate.
261
+ * Otherwise, format the date using the passed in format string.
262
+ *
263
+ * Examples (when 'today' is 17 Feb 2007):
264
+ * 16 Feb 2007 becomes yesterday.
265
+ * 17 Feb 2007 becomes today.
266
+ * 18 Feb 2007 becomes tomorrow.
267
+ * Any other day is formatted according to given argument or the DATE_FORMAT setting if no argument is given.
268
+ */
269
+ humanize.naturalDay = function(timestamp, format) {
270
+ timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
271
+ format = (format === undefined) ? 'Y-m-d' : format;
272
+
273
+ var oneDay = 86400;
274
+ var d = new Date();
275
+ var today = (new Date(d.getFullYear(), d.getMonth(), d.getDate())).getTime() / 1000;
276
+
277
+ if (timestamp < today && timestamp >= today - oneDay) {
278
+ return 'yesterday';
279
+ } else if (timestamp >= today && timestamp < today + oneDay) {
280
+ return 'today';
281
+ } else if (timestamp >= today + oneDay && timestamp < today + 2 * oneDay) {
282
+ return 'tomorrow';
283
+ }
284
+
285
+ return humanize.date(format, timestamp);
286
+ };
287
+
288
+ /**
289
+ * returns a string representing how many seconds, minutes or hours ago it was or will be in the future
290
+ * Will always return a relative time, most granular of seconds to least granular of years. See unit tests for more details
291
+ */
292
+ humanize.relativeTime = function(timestamp) {
293
+ timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
294
+
295
+ var currTime = humanize.time();
296
+ var timeDiff = currTime - timestamp;
297
+
298
+ // within 2 seconds
299
+ if (timeDiff < 2 && timeDiff > -2) {
300
+ return (timeDiff >= 0 ? 'just ' : '') + 'now';
301
+ }
302
+
303
+ // within a minute
304
+ if (timeDiff < 60 && timeDiff > -60) {
305
+ return (timeDiff >= 0 ? Math.floor(timeDiff) + ' seconds ago' : 'in ' + Math.floor(-timeDiff) + ' seconds');
306
+ }
307
+
308
+ // within 2 minutes
309
+ if (timeDiff < 120 && timeDiff > -120) {
310
+ return (timeDiff >= 0 ? 'about a minute ago' : 'in about a minute');
311
+ }
312
+
313
+ // within an hour
314
+ if (timeDiff < 3600 && timeDiff > -3600) {
315
+ return (timeDiff >= 0 ? Math.floor(timeDiff / 60) + ' minutes ago' : 'in ' + Math.floor(-timeDiff / 60) + ' minutes');
316
+ }
317
+
318
+ // within 2 hours
319
+ if (timeDiff < 7200 && timeDiff > -7200) {
320
+ return (timeDiff >= 0 ? 'about an hour ago' : 'in about an hour');
321
+ }
322
+
323
+ // within 24 hours
324
+ if (timeDiff < 86400 && timeDiff > -86400) {
325
+ return (timeDiff >= 0 ? Math.floor(timeDiff / 3600) + ' hours ago' : 'in ' + Math.floor(-timeDiff / 3600) + ' hours');
326
+ }
327
+
328
+ // within 2 days
329
+ var days2 = 2 * 86400;
330
+ if (timeDiff < days2 && timeDiff > -days2) {
331
+ return (timeDiff >= 0 ? '1 day ago' : 'in 1 day');
332
+ }
333
+
334
+ // within 29 days
335
+ var days29 = 29 * 86400;
336
+ if (timeDiff < days29 && timeDiff > -days29) {
337
+ return (timeDiff >= 0 ? Math.floor(timeDiff / 86400) + ' days ago' : 'in ' + Math.floor(-timeDiff / 86400) + ' days');
338
+ }
339
+
340
+ // within 60 days
341
+ var days60 = 60 * 86400;
342
+ if (timeDiff < days60 && timeDiff > -days60) {
343
+ return (timeDiff >= 0 ? 'about a month ago' : 'in about a month');
344
+ }
345
+
346
+ var currTimeYears = parseInt(humanize.date('Y', currTime), 10);
347
+ var timestampYears = parseInt(humanize.date('Y', timestamp), 10);
348
+ var currTimeMonths = currTimeYears * 12 + parseInt(humanize.date('n', currTime), 10);
349
+ var timestampMonths = timestampYears * 12 + parseInt(humanize.date('n', timestamp), 10);
350
+
351
+ // within a year
352
+ var monthDiff = currTimeMonths - timestampMonths;
353
+ if (monthDiff < 12 && monthDiff > -12) {
354
+ return (monthDiff >= 0 ? monthDiff + ' months ago' : 'in ' + (-monthDiff) + ' months');
355
+ }
356
+
357
+ var yearDiff = currTimeYears - timestampYears;
358
+ if (yearDiff < 2 && yearDiff > -2) {
359
+ return (yearDiff >= 0 ? 'a year ago' : 'in a year');
360
+ }
361
+
362
+ return (yearDiff >= 0 ? yearDiff + ' years ago' : 'in ' + (-yearDiff) + ' years');
363
+ };
364
+
365
+ /**
366
+ * Converts an integer to its ordinal as a string.
367
+ *
368
+ * 1 becomes 1st
369
+ * 2 becomes 2nd
370
+ * 3 becomes 3rd etc
371
+ */
372
+ humanize.ordinal = function(number) {
373
+ number = parseInt(number, 10);
374
+ number = isNaN(number) ? 0 : number;
375
+ var sign = number < 0 ? '-' : '';
376
+ number = Math.abs(number);
377
+ var tens = number % 100;
378
+
379
+ return sign + number + (tens > 4 && tens < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[number % 10] || 'th');
380
+ };
381
+
382
+ /**
383
+ * Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc).
384
+ *
385
+ * For example:
386
+ * If value is 123456789, the output would be 117.7 MB.
387
+ */
388
+ humanize.filesize = function(filesize, kilo, decimals, decPoint, thousandsSep) {
389
+ kilo = (kilo === undefined) ? 1024 : kilo;
390
+ decimals = isNaN(decimals) ? 2 : Math.abs(decimals);
391
+ decPoint = (decPoint === undefined) ? '.' : decPoint;
392
+ thousandsSep = (thousandsSep === undefined) ? ',' : thousandsSep;
393
+ if (filesize <= 0) { return '0 bytes'; }
394
+
395
+ var thresholds = [1];
396
+ var units = ['bytes', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb'];
397
+ if (filesize < kilo) { return humanize.numberFormat(filesize, 0) + ' ' + units[0]; }
398
+
399
+ for (var i = 1; i < units.length; i++) {
400
+ thresholds[i] = thresholds[i-1] * kilo;
401
+ if (filesize < thresholds[i]) {
402
+ return humanize.numberFormat(filesize / thresholds[i-1], decimals, decPoint, thousandsSep) + ' ' + units[i-1];
403
+ }
404
+ }
405
+
406
+ // use the last unit if we drop out to here
407
+ return humanize.numberFormat(filesize / thresholds[units.length - 1], decimals, decPoint, thousandsSep) + ' ' + units[units.length - 1];
408
+ };
409
+
410
+ /**
411
+ * Replaces line breaks in plain text with appropriate HTML
412
+ * A single newline becomes an HTML line break (<br />) and a new line followed by a blank line becomes a paragraph break (</p>).
413
+ *
414
+ * For example:
415
+ * If value is Joel\nis a\n\nslug, the output will be <p>Joel<br />is a</p><p>slug</p>
416
+ */
417
+ humanize.linebreaks = function(str) {
418
+ // remove beginning and ending newlines
419
+ str = str.replace(/^([\n|\r]*)/, '');
420
+ str = str.replace(/([\n|\r]*)$/, '');
421
+
422
+ // normalize all to \n
423
+ str = str.replace(/(\r\n|\n|\r)/g, "\n");
424
+
425
+ // any consecutive new lines more than 2 gets turned into p tags
426
+ str = str.replace(/(\n{2,})/g, '</p><p>');
427
+
428
+ // any that are singletons get turned into br
429
+ str = str.replace(/\n/g, '<br />');
430
+ return '<p>' + str + '</p>';
431
+ };
432
+
433
+ /**
434
+ * Converts all newlines in a piece of plain text to HTML line breaks (<br />).
435
+ */
436
+ humanize.nl2br = function(str) {
437
+ return str.replace(/(\r\n|\n|\r)/g, '<br />');
438
+ };
439
+
440
+ /**
441
+ * Truncates a string if it is longer than the specified number of characters.
442
+ * Truncated strings will end with a translatable ellipsis sequence ('…').
443
+ */
444
+ humanize.truncatechars = function(string, length) {
445
+ if (string.length <= length) { return string; }
446
+ return string.substr(0, length) + '…';
447
+ };
448
+
449
+ /**
450
+ * Truncates a string after a certain number of words.
451
+ * Newlines within the string will be removed.
452
+ */
453
+ humanize.truncatewords = function(string, numWords) {
454
+ var words = string.split(' ');
455
+ if (words.length < numWords) { return string; }
456
+ return words.slice(0, numWords).join(' ') + '…';
457
+ };
458
+
459
+ }).call(this);