source: trip-planner-front/node_modules/ora/index.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 8.4 KB
Line 
1'use strict';
2const readline = require('readline');
3const chalk = require('chalk');
4const cliCursor = require('cli-cursor');
5const cliSpinners = require('cli-spinners');
6const logSymbols = require('log-symbols');
7const stripAnsi = require('strip-ansi');
8const wcwidth = require('wcwidth');
9const isInteractive = require('is-interactive');
10const isUnicodeSupported = require('is-unicode-supported');
11const {BufferListStream} = require('bl');
12
13const TEXT = Symbol('text');
14const PREFIX_TEXT = Symbol('prefixText');
15const ASCII_ETX_CODE = 0x03; // Ctrl+C emits this code
16
17class StdinDiscarder {
18 constructor() {
19 this.requests = 0;
20
21 this.mutedStream = new BufferListStream();
22 this.mutedStream.pipe(process.stdout);
23
24 const self = this; // eslint-disable-line unicorn/no-this-assignment
25 this.ourEmit = function (event, data, ...args) {
26 const {stdin} = process;
27 if (self.requests > 0 || stdin.emit === self.ourEmit) {
28 if (event === 'keypress') { // Fixes readline behavior
29 return;
30 }
31
32 if (event === 'data' && data.includes(ASCII_ETX_CODE)) {
33 process.emit('SIGINT');
34 }
35
36 Reflect.apply(self.oldEmit, this, [event, data, ...args]);
37 } else {
38 Reflect.apply(process.stdin.emit, this, [event, data, ...args]);
39 }
40 };
41 }
42
43 start() {
44 this.requests++;
45
46 if (this.requests === 1) {
47 this.realStart();
48 }
49 }
50
51 stop() {
52 if (this.requests <= 0) {
53 throw new Error('`stop` called more times than `start`');
54 }
55
56 this.requests--;
57
58 if (this.requests === 0) {
59 this.realStop();
60 }
61 }
62
63 realStart() {
64 // No known way to make it work reliably on Windows
65 if (process.platform === 'win32') {
66 return;
67 }
68
69 this.rl = readline.createInterface({
70 input: process.stdin,
71 output: this.mutedStream
72 });
73
74 this.rl.on('SIGINT', () => {
75 if (process.listenerCount('SIGINT') === 0) {
76 process.emit('SIGINT');
77 } else {
78 this.rl.close();
79 process.kill(process.pid, 'SIGINT');
80 }
81 });
82 }
83
84 realStop() {
85 if (process.platform === 'win32') {
86 return;
87 }
88
89 this.rl.close();
90 this.rl = undefined;
91 }
92}
93
94let stdinDiscarder;
95
96class Ora {
97 constructor(options) {
98 if (!stdinDiscarder) {
99 stdinDiscarder = new StdinDiscarder();
100 }
101
102 if (typeof options === 'string') {
103 options = {
104 text: options
105 };
106 }
107
108 this.options = {
109 text: '',
110 color: 'cyan',
111 stream: process.stderr,
112 discardStdin: true,
113 ...options
114 };
115
116 this.spinner = this.options.spinner;
117
118 this.color = this.options.color;
119 this.hideCursor = this.options.hideCursor !== false;
120 this.interval = this.options.interval || this.spinner.interval || 100;
121 this.stream = this.options.stream;
122 this.id = undefined;
123 this.isEnabled = typeof this.options.isEnabled === 'boolean' ? this.options.isEnabled : isInteractive({stream: this.stream});
124 this.isSilent = typeof this.options.isSilent === 'boolean' ? this.options.isSilent : false;
125
126 // Set *after* `this.stream`
127 this.text = this.options.text;
128 this.prefixText = this.options.prefixText;
129 this.linesToClear = 0;
130 this.indent = this.options.indent;
131 this.discardStdin = this.options.discardStdin;
132 this.isDiscardingStdin = false;
133 }
134
135 get indent() {
136 return this._indent;
137 }
138
139 set indent(indent = 0) {
140 if (!(indent >= 0 && Number.isInteger(indent))) {
141 throw new Error('The `indent` option must be an integer from 0 and up');
142 }
143
144 this._indent = indent;
145 }
146
147 _updateInterval(interval) {
148 if (interval !== undefined) {
149 this.interval = interval;
150 }
151 }
152
153 get spinner() {
154 return this._spinner;
155 }
156
157 set spinner(spinner) {
158 this.frameIndex = 0;
159
160 if (typeof spinner === 'object') {
161 if (spinner.frames === undefined) {
162 throw new Error('The given spinner must have a `frames` property');
163 }
164
165 this._spinner = spinner;
166 } else if (!isUnicodeSupported()) {
167 this._spinner = cliSpinners.line;
168 } else if (spinner === undefined) {
169 // Set default spinner
170 this._spinner = cliSpinners.dots;
171 } else if (spinner !== 'default' && cliSpinners[spinner]) {
172 this._spinner = cliSpinners[spinner];
173 } else {
174 throw new Error(`There is no built-in spinner named '${spinner}'. See https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json for a full list.`);
175 }
176
177 this._updateInterval(this._spinner.interval);
178 }
179
180 get text() {
181 return this[TEXT];
182 }
183
184 set text(value) {
185 this[TEXT] = value;
186 this.updateLineCount();
187 }
188
189 get prefixText() {
190 return this[PREFIX_TEXT];
191 }
192
193 set prefixText(value) {
194 this[PREFIX_TEXT] = value;
195 this.updateLineCount();
196 }
197
198 get isSpinning() {
199 return this.id !== undefined;
200 }
201
202 getFullPrefixText(prefixText = this[PREFIX_TEXT], postfix = ' ') {
203 if (typeof prefixText === 'string') {
204 return prefixText + postfix;
205 }
206
207 if (typeof prefixText === 'function') {
208 return prefixText() + postfix;
209 }
210
211 return '';
212 }
213
214 updateLineCount() {
215 const columns = this.stream.columns || 80;
216 const fullPrefixText = this.getFullPrefixText(this.prefixText, '-');
217 this.lineCount = 0;
218 for (const line of stripAnsi(fullPrefixText + '--' + this[TEXT]).split('\n')) {
219 this.lineCount += Math.max(1, Math.ceil(wcwidth(line) / columns));
220 }
221 }
222
223 get isEnabled() {
224 return this._isEnabled && !this.isSilent;
225 }
226
227 set isEnabled(value) {
228 if (typeof value !== 'boolean') {
229 throw new TypeError('The `isEnabled` option must be a boolean');
230 }
231
232 this._isEnabled = value;
233 }
234
235 get isSilent() {
236 return this._isSilent;
237 }
238
239 set isSilent(value) {
240 if (typeof value !== 'boolean') {
241 throw new TypeError('The `isSilent` option must be a boolean');
242 }
243
244 this._isSilent = value;
245 }
246
247 frame() {
248 const {frames} = this.spinner;
249 let frame = frames[this.frameIndex];
250
251 if (this.color) {
252 frame = chalk[this.color](frame);
253 }
254
255 this.frameIndex = ++this.frameIndex % frames.length;
256 const fullPrefixText = (typeof this.prefixText === 'string' && this.prefixText !== '') ? this.prefixText + ' ' : '';
257 const fullText = typeof this.text === 'string' ? ' ' + this.text : '';
258
259 return fullPrefixText + frame + fullText;
260 }
261
262 clear() {
263 if (!this.isEnabled || !this.stream.isTTY) {
264 return this;
265 }
266
267 for (let i = 0; i < this.linesToClear; i++) {
268 if (i > 0) {
269 this.stream.moveCursor(0, -1);
270 }
271
272 this.stream.clearLine();
273 this.stream.cursorTo(this.indent);
274 }
275
276 this.linesToClear = 0;
277
278 return this;
279 }
280
281 render() {
282 if (this.isSilent) {
283 return this;
284 }
285
286 this.clear();
287 this.stream.write(this.frame());
288 this.linesToClear = this.lineCount;
289
290 return this;
291 }
292
293 start(text) {
294 if (text) {
295 this.text = text;
296 }
297
298 if (this.isSilent) {
299 return this;
300 }
301
302 if (!this.isEnabled) {
303 if (this.text) {
304 this.stream.write(`- ${this.text}\n`);
305 }
306
307 return this;
308 }
309
310 if (this.isSpinning) {
311 return this;
312 }
313
314 if (this.hideCursor) {
315 cliCursor.hide(this.stream);
316 }
317
318 if (this.discardStdin && process.stdin.isTTY) {
319 this.isDiscardingStdin = true;
320 stdinDiscarder.start();
321 }
322
323 this.render();
324 this.id = setInterval(this.render.bind(this), this.interval);
325
326 return this;
327 }
328
329 stop() {
330 if (!this.isEnabled) {
331 return this;
332 }
333
334 clearInterval(this.id);
335 this.id = undefined;
336 this.frameIndex = 0;
337 this.clear();
338 if (this.hideCursor) {
339 cliCursor.show(this.stream);
340 }
341
342 if (this.discardStdin && process.stdin.isTTY && this.isDiscardingStdin) {
343 stdinDiscarder.stop();
344 this.isDiscardingStdin = false;
345 }
346
347 return this;
348 }
349
350 succeed(text) {
351 return this.stopAndPersist({symbol: logSymbols.success, text});
352 }
353
354 fail(text) {
355 return this.stopAndPersist({symbol: logSymbols.error, text});
356 }
357
358 warn(text) {
359 return this.stopAndPersist({symbol: logSymbols.warning, text});
360 }
361
362 info(text) {
363 return this.stopAndPersist({symbol: logSymbols.info, text});
364 }
365
366 stopAndPersist(options = {}) {
367 if (this.isSilent) {
368 return this;
369 }
370
371 const prefixText = options.prefixText || this.prefixText;
372 const text = options.text || this.text;
373 const fullText = (typeof text === 'string') ? ' ' + text : '';
374
375 this.stop();
376 this.stream.write(`${this.getFullPrefixText(prefixText, ' ')}${options.symbol || ' '}${fullText}\n`);
377
378 return this;
379 }
380}
381
382const oraFactory = function (options) {
383 return new Ora(options);
384};
385
386module.exports = oraFactory;
387
388module.exports.promise = (action, options) => {
389 // eslint-disable-next-line promise/prefer-await-to-then
390 if (typeof action.then !== 'function') {
391 throw new TypeError('Parameter `action` must be a Promise');
392 }
393
394 const spinner = new Ora(options);
395 spinner.start();
396
397 (async () => {
398 try {
399 await action;
400 spinner.succeed();
401 } catch {
402 spinner.fail();
403 }
404 })();
405
406 return spinner;
407};
Note: See TracBrowser for help on using the repository browser.