UNPKG

9.77 kBJavaScriptView Raw
1var clc = require('cli-color'),
2 ansiTrim = require('cli-color/trim'),
3 sprintf = require('util').format;
4
5var sparklineSymbols = [
6 '\u2581',
7 '\u2582',
8 '\u2583',
9 '\u2584',
10 '\u2585',
11 '\u2586',
12 '\u2587',
13 '\u2588'
14];
15
16// Tiny helper function used for making default values prettier.
17function pick(value, defaultValue) {
18 return (typeof value == 'undefined' ? defaultValue : value);
19}
20
21var helpers = {
22
23 // Make a console spinner.
24 // Code based on code from Mocha by Visionmedia/Tj
25 // https://github.com/visionmedia/mocha/blob/master/bin/_mocha
26 Spinner: function (message, style) {
27 var spinnerMessage = message;
28 var spinnerStyle = style;
29
30 this.start = function () {
31 var self = this;
32 var spinner = spinnerStyle;
33
34 if (!spinner || spinner.length === 0) {
35 spinner = 'win32' == process.platform ? ['|','/','-','\\'] : ['◜','◠','◝','◞','◡','◟'];
36 }
37
38 function play(arr, interval) {
39 var len = arr.length, i = 0;
40 interval = interval || 100;
41
42 var drawTick = function () {
43 var str = arr[i++ % len];
44 process.stdout.write('\u001b[0G' + str + '\u001b[90m' + spinnerMessage + '\u001b[0m');
45 };
46
47 self.timer = setInterval(drawTick, interval);
48 }
49
50 var frames = spinner.map(function(c) {
51 return sprintf(' \u001b[96m%s ', c);
52 });
53
54 play(frames, 70);
55 };
56
57 this.message = function (message) {
58 spinnerMessage = message;
59 };
60
61 this.stop = function () {
62 process.stdout.write('\u001b[0G\u001b[2K');
63 clearInterval(this.timer);
64 };
65 },
66
67 // Make an ascii horizontal gauge
68 Gauge: function (value, maxValue, width, dangerZone, suffix) {
69 if (maxValue === 0) {
70 return '[]';
71 } else {
72 var barLength = value ? Math.ceil(value / maxValue * width) : 1;
73 if (barLength > width)
74 barLength = width;
75
76 var barColor = clc.green;
77 if (value > dangerZone)
78 barColor = clc.red;
79
80 return '['+
81 barColor(Array(barLength).join("|")) + // The filled portion
82 Array(width + 1 - barLength).join("-") + // The empty portion
83 '] ' + clc.blackBright(suffix);
84 }
85 },
86
87 // Make a progress bar
88 Progress: function (width) {
89 var currentValue = 0;
90 var maxValue = 0;
91 var self = this;
92
93 this.update = function (currentValue, maxValue) {
94 if(maxValue === 0) {
95 return '[]';
96 }
97
98 // if no maxValue assigned, treat as percentage
99 if (typeof maxValue === 'undefined')
100 maxValue = 1
101
102 // maintain correct barLength when value is 0
103 var barLength = currentValue ? Math.ceil(currentValue / maxValue * width) : 1;
104
105 // prevent barLength from overflowing column width
106 if (barLength > width)
107 barLength = width;
108
109 // prevent value from being greater than maxValue
110 if (currentValue > maxValue)
111 currentValue = maxValue
112
113 return '['+
114 clc.green(Array(barLength).join("|")) + //The filled portion
115 Array(width + 1 - barLength).join("-") + //The empty portion
116 '] ' + clc.blackBright(Math.ceil(currentValue / maxValue * 100) + '%');
117 };
118 },
119
120 // Make a unicode sparkline chart
121 Sparkline: function (points, suffix) {
122 if(typeof suffix == 'undefined')
123 suffix = '';
124
125 var max = Math.max.apply(Math, points);
126
127 var scaledSequence = points.map(function (thisPoint) {
128 if(max === 0)
129 return [0, 0];
130 else if(thisPoint === 0)
131 return [0, 0];
132 else
133 return [
134 Math.ceil(thisPoint / max * (sparklineSymbols.length-1)),
135 thisPoint
136 ];
137 });
138
139 var sparklineGraph = '';
140 var alreadyDrawnMax = false;
141 scaledSequence.forEach(function (symbolNumber) {
142 if(symbolNumber[1] == max & !alreadyDrawnMax)
143 {
144 sparklineGraph += clc.green(sparklineSymbols[symbolNumber[0]]);
145 alreadyDrawnMax = true;
146 }
147 else
148 sparklineGraph += sparklineSymbols[symbolNumber[0]];
149 });
150
151 return sparklineGraph + ' ' + clc.blackBright(points[points.length-1] + suffix + ' (') + clc.green(max + suffix) + clc.blackBright(')');
152 },
153
154 // Interface for storing multiple lines and then outputting them all at once.
155 LineBuffer: function (userOptions) {
156 var self = this;
157 self.lines = [];
158
159 //Merge the user defined settings (if there are any) with the default settings.
160 var defaultOptions = {
161 x: 0,
162 y: 0,
163 width: 'console',
164 height: 'console',
165 scroll: 0
166 };
167
168 if(typeof userOptions == 'undefined')
169 self.userOptions = defaultOptions;
170 else
171 {
172 self.userOptions = {
173 x: pick(userOptions.x, defaultOptions.x),
174 y: pick(userOptions.y, defaultOptions.y),
175 width: pick(userOptions.width, defaultOptions.width),
176 height: pick(userOptions.height, defaultOptions.height),
177 scroll: pick(userOptions.scroll, defaultOptions.scroll)
178 };
179 }
180
181 this.height = function ()
182 {
183 if(self.userOptions.height == 'console')
184 return process.stdout.rows;
185 else
186 return self.userOptions.height;
187 };
188
189 this.width = function ()
190 {
191 if(self.userOptions.width == 'console')
192 return process.stdout.columns;
193 else
194 return self.userOptions.width;
195 };
196
197 // Push a line of content into the buffer.
198 this.addLine = function (lineObject) {
199 self.lines.push(lineObject);
200 return self;
201 };
202
203 // See if the buffer has enough content to fill the vertical space, if not fill the vertical space
204 // with the designated fill line.
205 this.fill = function (fillLine) {
206 var fillHeight = self.height()-self.lines.length;
207 if(fillHeight > 0)
208 {
209 for(var i = 0; i < fillHeight; i++)
210 {
211 self.addLine(fillLine);
212 }
213 }
214 return self;
215 };
216
217 // Output a buffer full of lines.
218 this.output = function () {
219 // First grab a subset of the lines depending on the scroll location and the height of the buffer.
220 var outputLines;
221 var sliceEnd;
222 var outputHeight = self.height();
223 if(self.userOptions.scroll > self.lines.length)
224 return;
225
226 if(self.lines.length - self.userOptions.scroll > outputHeight)
227 outputLines = self.lines.slice(self.userOptions.scroll, self.userOptions.scroll + outputHeight);
228 else
229 outputLines = self.lines.slice(self.userOptions.scroll);
230
231 // First move the cursor to the location where we want the buffer to draw.
232 var currentY = self.userOptions.y;
233 outputLines.forEach(function (line) {
234 process.stdout.write(clc.moveTo(self.userOptions.x, currentY));
235 line.output();
236 currentY++;
237 });
238 };
239 },
240
241 // Create a new table object to output
242 Table: function () {
243 var self = this;
244 var tableContent = '';
245
246 // Adds a new table row to the output
247 self.tr = function () {
248 return self;
249 };
250
251 // Adds a new table cell to the output
252 self.td = function (cellContent, cellWidth) {
253 return self;
254 };
255
256 // Draw this table to the screen
257 self.output = function () {
258 return self;
259 };
260
261 return self;
262 },
263
264 // Chainable wrapper for line content
265 Line: function (defaultBuffer) {
266 var lineContent = "";
267 var self = this;
268 self.defaultBuffer = defaultBuffer;
269
270 // Put text in the line
271 this.text = function (text, styles) {
272 for(var styleNumber in styles)
273 {
274 text = styles[styleNumber](text);
275 }
276 lineContent += text;
277 return self;
278 };
279
280 // Put padding in the line.
281 this.padding = function (width, styles) {
282 var padding = Array(width+1).join(" ");
283 for(var styleNumber in styles)
284 {
285 padding = styles[styleNumber](padding);
286 }
287 lineContent += padding;
288 return self;
289 };
290
291 // Put padding in the line.
292 this.column = function (text, columnWidth, textStyles) {
293 var textWidth = ansiTrim(text).length;
294
295 if(textWidth > columnWidth)
296 {
297 self.text(text.slice(0, columnWidth), textStyles);
298 }
299 else if(textWidth < columnWidth)
300 {
301 self.text(text, textStyles)
302 .padding(columnWidth - textWidth);
303 }
304 else
305 {
306 self.text(text, textStyles);
307 }
308 return self;
309 };
310
311 // Fill the rest of the width of the line with space.
312 this.fill = function (styles) {
313 var fillWidth = process.stdout.columns-ansiTrim(lineContent).length;
314 if(fillWidth > 0)
315 self.padding(fillWidth, styles);
316 return self;
317 };
318
319 // Store a line in a line buffer to be output later.
320 this.store = function (buffer) {
321 if(typeof buffer == 'undefined')
322 {
323 if(typeof self.defaultBuffer == 'undefined')
324 process.stderr.write('Attempt to store a line in a line buffer, without providing a line buffer to store that line in.');
325 else
326 self.defaultBuffer.addLine(self);
327 }
328 else
329 {
330 buffer.addLine(self);
331 }
332 return self;
333 };
334
335 // Output a line directly to the screen.
336 this.output = function () {
337 process.stdout.write(lineContent+"\n");
338 return self;
339 };
340
341 // Return the contents
342 this.contents = function () {
343 return lineContent;
344 };
345 },
346
347 // Effectively clear a screen.
348 // Code based on code from Clear by bahamas10/node-clear
349 // https://github.com/bahamas10/node-clear
350 Clear: function(clear) {
351 if (clear !== false) {
352 // Ansi code for clearing screen
353 process.stdout.write('\033[2J');
354 }
355 // if false, don't clear screen, rather move cursor to top left
356 process.stdout.write('\033[0;0f');
357 }
358};
359
360module.exports = helpers;