1 | 'use strict';
|
2 |
|
3 | const ask = require('ask-nicely');
|
4 | const os = require('os');
|
5 | const util = require('util');
|
6 |
|
7 | const Table = require('cli-table');
|
8 | const stripAnsi = require('strip-ansi');
|
9 |
|
10 | const primitives = require('./primitives');
|
11 | const extras = require('./extras');
|
12 |
|
13 | const DEFAULT_CONFIG = {
|
14 | defaultWidth: extras.defaultWidth,
|
15 | indentation: ' ',
|
16 | defaultIndentation: 1,
|
17 | tableCharacters: extras.expandedTable,
|
18 | spinnerFactory: extras.spriteSpinner,
|
19 | spinnerInterval: 200,
|
20 | stdout: process.stdout
|
21 | };
|
22 |
|
23 | const DESTROYERS = Symbol();
|
24 | const LOG = Symbol();
|
25 | const RAW = Symbol();
|
26 | const WIDTH = Symbol();
|
27 |
|
28 | class ErrorWithSolution extends ask.InputError {
|
29 | |
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | constructor(message, solution, innerError, code) {
|
38 | super(message, solution, code);
|
39 | this.innerError = innerError;
|
40 |
|
41 |
|
42 | this.constructor = ErrorWithSolution;
|
43 | this.__proto__ = ErrorWithSolution.prototype;
|
44 | }
|
45 | }
|
46 |
|
47 | class ErrorWithInnerError extends ErrorWithSolution {
|
48 | |
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | constructor(message, innerError, code) {
|
56 | super(message, undefined, innerError, code);
|
57 |
|
58 |
|
59 | this.constructor = ErrorWithInnerError;
|
60 | this.__proto__ = ErrorWithInnerError.prototype;
|
61 | }
|
62 | }
|
63 |
|
64 | class FdtResponse {
|
65 | |
66 |
|
67 |
|
68 |
|
69 |
|
70 | constructor(colors, config) {
|
71 |
|
72 | this.colors = Object.assign({}, extras.defaultTheme, colors);
|
73 | Object.keys(this.colors).forEach(colorName => {
|
74 | if (!this.colors[colorName]) this.colors[colorName] = ['dim'];
|
75 | else if (!Array.isArray(this.colors[colorName]))
|
76 | this.colors[colorName] = [this.colors[colorName]];
|
77 | });
|
78 |
|
79 |
|
80 | this.config = Object.assign({}, DEFAULT_CONFIG, config);
|
81 |
|
82 |
|
83 | this.needsClearing = false;
|
84 |
|
85 |
|
86 | this.indentationLevel = this.config.defaultIndentation;
|
87 |
|
88 |
|
89 | this[WIDTH] = primitives.getTerminalWidth() || this.config.defaultWidth;
|
90 |
|
91 |
|
92 | this[DESTROYERS] = [];
|
93 |
|
94 | this.ErrorWithInnerError = ErrorWithInnerError;
|
95 | this.ErrorWithSolution = ErrorWithSolution;
|
96 | this.InputError = ask.InputError;
|
97 | }
|
98 |
|
99 | clearIfNeeded() {
|
100 | if (this.needsClearing && typeof this.config.stdout.clearLine === 'function') {
|
101 | this.config.stdout.clearLine();
|
102 | this.config.stdout.cursorTo(0);
|
103 | this.needsClearing = false;
|
104 | }
|
105 | }
|
106 |
|
107 | [RAW](data) {
|
108 | this.config.stdout.write(data);
|
109 | }
|
110 |
|
111 | [LOG](string, formattingOptions, skipLineBreak) {
|
112 | this.clearIfNeeded();
|
113 |
|
114 | this[RAW](
|
115 | primitives.indentString(
|
116 | primitives.formatString(string, formattingOptions),
|
117 | primitives.getLeftIndentationString(this.config.indentation, this.indentationLevel),
|
118 | this.config.indentation,
|
119 | this[WIDTH]
|
120 | ) + (skipLineBreak ? '' : os.EOL)
|
121 | );
|
122 | }
|
123 |
|
124 | getWidth() {
|
125 | return this[WIDTH];
|
126 | }
|
127 |
|
128 | setWrapping(width) {
|
129 | if (width === true) this[WIDTH] = primitives.getTerminalWidth();
|
130 | else if (width) this[WIDTH] = parseInt(width, 10);
|
131 | else this[WIDTH] = Infinity;
|
132 | }
|
133 |
|
134 | indent() {
|
135 | ++this.indentationLevel;
|
136 | }
|
137 | outdent() {
|
138 | this.indentationLevel = Math.max(this.config.defaultIndentation, this.indentationLevel - 1);
|
139 | }
|
140 |
|
141 | |
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 | log(data, skipLineBreak) {
|
148 | this[LOG](data, this.colors.log, skipLineBreak);
|
149 | }
|
150 |
|
151 | |
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | success(data, skipLineBreak) {
|
158 | this[LOG](data, this.colors.success, skipLineBreak);
|
159 | }
|
160 |
|
161 | |
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | caption(data, skipLineBreak) {
|
168 | this.clearIfNeeded();
|
169 | this.break();
|
170 | this[LOG](data, this.colors.caption, skipLineBreak);
|
171 | }
|
172 |
|
173 | |
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | notice(data, skipLineBreak) {
|
180 | this[LOG](data, this.colors.notice, skipLineBreak);
|
181 | }
|
182 |
|
183 | |
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 | error(data, skipLineBreak) {
|
190 | this[LOG](data, this.colors.error, skipLineBreak);
|
191 | }
|
192 |
|
193 | |
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | debug(data, skipLineBreak) {
|
200 | this[LOG](
|
201 | data && typeof data === 'object'
|
202 | ? util.inspect(data, { depth: 3, colors: false })
|
203 | : data,
|
204 | this.colors.debug,
|
205 | skipLineBreak
|
206 | );
|
207 | }
|
208 |
|
209 | definition(key, value, formattingName, skipLineBreak) {
|
210 | this[LOG](key, this.colors.definitionKey);
|
211 |
|
212 | this[RAW](
|
213 | primitives.indentString(
|
214 | primitives.formatString(
|
215 | value,
|
216 | formattingName ? this.colors[formattingName] : this.colors.definitionValue
|
217 | ),
|
218 | primitives.getLeftIndentationString(
|
219 | this.config.indentation,
|
220 | this.indentationLevel + 1
|
221 | ),
|
222 | this.config.indentation,
|
223 | this[WIDTH]
|
224 | ) + (skipLineBreak ? '' : os.EOL)
|
225 | );
|
226 | }
|
227 |
|
228 | property(key, value, keySize, formattingName, skipLineBreak) {
|
229 | keySize = keySize || 0;
|
230 | const keyString = primitives.indentString(
|
231 | primitives.formatString(
|
232 | primitives.padString(key, keySize),
|
233 | this.colors.propertyKey
|
234 | ),
|
235 | primitives.getLeftIndentationString(this.config.indentation, this.indentationLevel),
|
236 | this.config.indentation,
|
237 | this[WIDTH]
|
238 | ),
|
239 | seperatorString = '';
|
240 |
|
241 | this.clearIfNeeded();
|
242 | this[RAW](
|
243 | primitives
|
244 | .indentString(
|
245 | primitives.formatString(
|
246 | value,
|
247 | formattingName ? this.colors[formattingName] : this.colors.propertyValue
|
248 | ),
|
249 | seperatorString,
|
250 | seperatorString,
|
251 | this[WIDTH] -
|
252 | (1 + this.indentationLevel) * this.config.indentation.length -
|
253 | seperatorString.length -
|
254 | keySize
|
255 | )
|
256 | .split('\n')
|
257 | .map(
|
258 | (line, i) =>
|
259 | (i === 0
|
260 | ? keyString
|
261 | : primitives.fillString(
|
262 | keySize +
|
263 | (this.indentationLevel + 1) *
|
264 | this.config.indentation.length +
|
265 | 1
|
266 | )) + line
|
267 | )
|
268 | .join('\n') + (skipLineBreak ? '' : os.EOL)
|
269 | );
|
270 | }
|
271 |
|
272 | properties(obj, formattingName) {
|
273 | let maxLength = 0;
|
274 | if (Array.isArray(obj)) {
|
275 | obj.forEach(k => {
|
276 | maxLength = Math.max((k[0] || '').length, maxLength);
|
277 | });
|
278 | obj.forEach(k => {
|
279 | this.property(k[0], k[1], maxLength, k[2] || formattingName);
|
280 | });
|
281 | } else {
|
282 | Object.keys(obj).forEach(k => {
|
283 | maxLength = Math.max(k.length, maxLength);
|
284 | });
|
285 | Object.keys(obj).forEach(k => {
|
286 | this.property(k, obj[k], maxLength, formattingName);
|
287 | });
|
288 | }
|
289 | }
|
290 |
|
291 | |
292 |
|
293 |
|
294 |
|
295 |
|
296 | raw(data) {
|
297 | this[RAW](data);
|
298 | }
|
299 |
|
300 | |
301 |
|
302 |
|
303 | break() {
|
304 | this[RAW](os.EOL);
|
305 | }
|
306 |
|
307 | |
308 |
|
309 |
|
310 | destroyAllSpinners() {
|
311 | this[DESTROYERS].forEach(fn => fn());
|
312 | }
|
313 |
|
314 | |
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 | spinner(message, skipLineBreak) {
|
323 | const startTime = new Date().getTime(),
|
324 | hasClearLine = typeof this.config.stdout.clearLine === 'function';
|
325 |
|
326 | if (!hasClearLine) {
|
327 |
|
328 |
|
329 | const destroySpinnerWithoutClearLine = () => {
|
330 | const ms = new Date().getTime() - startTime;
|
331 | this[LOG](`${message} (${ms}ms)`, this.colors.spinnerDone, skipLineBreak);
|
332 |
|
333 | this[DESTROYERS].splice(
|
334 | this[DESTROYERS].indexOf(destroySpinnerWithoutClearLine),
|
335 | 1
|
336 | );
|
337 | };
|
338 |
|
339 | this[DESTROYERS].push(destroySpinnerWithoutClearLine);
|
340 |
|
341 | return destroySpinnerWithoutClearLine;
|
342 | }
|
343 |
|
344 | const formattedMessageWithAnsi = primitives
|
345 | .indentString(
|
346 | primitives.formatString(message, this.colors.spinnerSpinning),
|
347 | primitives.getLeftIndentationString(
|
348 | this.config.indentation,
|
349 | this.indentationLevel
|
350 | ),
|
351 | this.config.indentation,
|
352 | this.getWidth()
|
353 | )
|
354 | .replace(new RegExp(`${this.config.indentation}$`), ''),
|
355 | formattedMessageWithoutAnsi = stripAnsi(formattedMessageWithAnsi),
|
356 | drawSpinner = this.config.spinnerFactory(
|
357 | this,
|
358 | message,
|
359 | formattedMessageWithoutAnsi,
|
360 | formattedMessageWithAnsi
|
361 | ),
|
362 | interval = setInterval(() => {
|
363 | if (!this.needsClearing) {
|
364 |
|
365 | this[RAW](formattedMessageWithAnsi);
|
366 | }
|
367 | drawSpinner(null, !this.needsClearing);
|
368 | this.needsClearing = true;
|
369 | }, this.config.spinnerInterval),
|
370 | destroySpinner = () => {
|
371 | const ms = new Date().getTime() - startTime;
|
372 |
|
373 | if (!this.needsClearing) {
|
374 |
|
375 | this[RAW](formattedMessageWithAnsi);
|
376 | }
|
377 |
|
378 | drawSpinner(`(${ms}ms)`, !this.needsClearing);
|
379 | this[RAW](os.EOL);
|
380 |
|
381 | clearInterval(interval);
|
382 | this[DESTROYERS].splice(this[DESTROYERS].indexOf(destroySpinner), 1);
|
383 | };
|
384 |
|
385 | this[DESTROYERS].push(destroySpinner);
|
386 |
|
387 | this[RAW](formattedMessageWithAnsi);
|
388 | this.needsClearing = true;
|
389 | drawSpinner();
|
390 |
|
391 | return destroySpinner;
|
392 | }
|
393 |
|
394 | table(columnNames, content, expanded) {
|
395 | const columnSizes = [],
|
396 | totalWidth = Math.min(
|
397 | this[WIDTH] - (this.indentationLevel + 1) * this.config.indentation.length,
|
398 | 800
|
399 | ),
|
400 | columnSeperator = ' ';
|
401 |
|
402 | content = content.map(row =>
|
403 | row.map((cell, colIndex) => {
|
404 | cell = cell + '';
|
405 | if (!columnSizes[colIndex]) columnSizes[colIndex] = columnNames[colIndex].length;
|
406 |
|
407 | let cellLength = cell.length;
|
408 | if (cell.includes('\n'))
|
409 | cellLength = cell
|
410 | .split('\n')
|
411 | .reduce((max, line) => Math.max(max, line.length), 0);
|
412 | if (cellLength > columnSizes[colIndex]) columnSizes[colIndex] = cellLength;
|
413 |
|
414 | return cell.trim();
|
415 | })
|
416 | );
|
417 |
|
418 | const totalContentAvailableWidth = totalWidth - columnNames.length * columnSeperator.length,
|
419 | totalContentNativeWidth = columnSizes.reduce((total, size) => total + size, 0),
|
420 | contentRelativeSizes =
|
421 | totalContentNativeWidth <= totalContentAvailableWidth
|
422 | ? columnSizes
|
423 | : columnSizes.map(size =>
|
424 | Math.ceil(totalContentAvailableWidth * (size / totalContentNativeWidth))
|
425 | ),
|
426 | table = new Table({
|
427 | head: columnNames || [],
|
428 | colWidths: contentRelativeSizes,
|
429 | chars: !expanded ? extras.compactTable : this.config.tableCharacters,
|
430 | style: {
|
431 | 'padding-left': 0,
|
432 | 'padding-right': 0,
|
433 | 'compact': !expanded,
|
434 | 'head': this.colors.tableHeader || [],
|
435 | 'border': this.colors.debug || []
|
436 | }
|
437 | });
|
438 |
|
439 | content.forEach(cont =>
|
440 | table.push(
|
441 | cont.map((c, i) => {
|
442 | return c.length > contentRelativeSizes[i]
|
443 | ? primitives.wrap(c, contentRelativeSizes[i])
|
444 | : c;
|
445 | })
|
446 | )
|
447 | );
|
448 |
|
449 | this.clearIfNeeded();
|
450 | table
|
451 | .toString()
|
452 | .split('\n')
|
453 | .map(
|
454 | line =>
|
455 | primitives.getLeftIndentationString(
|
456 | this.config.indentation,
|
457 | this.indentationLevel
|
458 | ) + line
|
459 | )
|
460 | .forEach(line => {
|
461 | this[RAW](line + os.EOL);
|
462 | });
|
463 | }
|
464 |
|
465 | list(listItems, bulletCharacter) {
|
466 | listItems.forEach((listItem, i) => {
|
467 | this.listItem(listItem, bulletCharacter ? bulletCharacter : i + 1 + '.');
|
468 | });
|
469 | }
|
470 |
|
471 | listItem(value, bulletCharacter, skipLineBreak) {
|
472 | this[LOG](
|
473 | primitives.formatString(bulletCharacter, this.colors.listItemBullet) +
|
474 | ' ' +
|
475 | primitives.formatString(value, this.colors.listItemValue, skipLineBreak)
|
476 | );
|
477 | }
|
478 | }
|
479 |
|
480 | module.exports = FdtResponse;
|