console-toolkit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +92 -0
  3. package/package.json +59 -0
  4. package/src/alphanumeric/arrows.js +31 -0
  5. package/src/alphanumeric/fractions.js +82 -0
  6. package/src/alphanumeric/number-formatters.js +131 -0
  7. package/src/alphanumeric/roman.js +86 -0
  8. package/src/alphanumeric/unicode-cultural-numbers.js +68 -0
  9. package/src/alphanumeric/unicode-letters.js +63 -0
  10. package/src/alphanumeric/unicode-numbers.js +75 -0
  11. package/src/alphanumeric/utils.js +44 -0
  12. package/src/ansi/csi.js +67 -0
  13. package/src/ansi/index.js +20 -0
  14. package/src/ansi/sgr-constants.js +99 -0
  15. package/src/ansi/sgr-state.js +398 -0
  16. package/src/ansi/sgr.js +214 -0
  17. package/src/box.js +253 -0
  18. package/src/charts/bars/block-frac-grouped.js +6 -0
  19. package/src/charts/bars/block-frac.js +36 -0
  20. package/src/charts/bars/block-grouped.js +6 -0
  21. package/src/charts/bars/block.js +43 -0
  22. package/src/charts/bars/draw-grouped.js +39 -0
  23. package/src/charts/bars/draw-stacked.js +24 -0
  24. package/src/charts/bars/frac-grouped.js +33 -0
  25. package/src/charts/bars/plain-grouped.js +6 -0
  26. package/src/charts/bars/plain.js +63 -0
  27. package/src/charts/columns/block-frac-grouped.js +6 -0
  28. package/src/charts/columns/block-frac.js +30 -0
  29. package/src/charts/columns/block-grouped.js +6 -0
  30. package/src/charts/columns/block.js +37 -0
  31. package/src/charts/columns/draw-grouped.js +48 -0
  32. package/src/charts/columns/draw-stacked.js +31 -0
  33. package/src/charts/columns/frac-grouped.js +27 -0
  34. package/src/charts/columns/plain-grouped.js +6 -0
  35. package/src/charts/columns/plain.js +39 -0
  36. package/src/charts/themes/default.js +12 -0
  37. package/src/charts/themes/rainbow-reversed.js +5 -0
  38. package/src/charts/themes/rainbow.js +8 -0
  39. package/src/charts/utils.js +75 -0
  40. package/src/draw-block-frac.js +33 -0
  41. package/src/draw-block.js +55 -0
  42. package/src/meta.js +41 -0
  43. package/src/output/show.js +40 -0
  44. package/src/output/updater.js +82 -0
  45. package/src/output/writer.js +131 -0
  46. package/src/panel.js +748 -0
  47. package/src/plot/bitmap.js +108 -0
  48. package/src/plot/draw-line.js +26 -0
  49. package/src/plot/draw-rect.js +216 -0
  50. package/src/plot/index.js +24 -0
  51. package/src/plot/to-quads.js +32 -0
  52. package/src/spinner/index.js +8 -0
  53. package/src/spinner/spin.js +51 -0
  54. package/src/spinner/spinner.js +75 -0
  55. package/src/spinner/spinners.js +65 -0
  56. package/src/strings.js +72 -0
  57. package/src/style.js +620 -0
  58. package/src/symbols.js +131 -0
  59. package/src/table/draw-borders.js +87 -0
  60. package/src/table/index.js +7 -0
  61. package/src/table/table.js +330 -0
  62. package/src/themes/blocks/unicode-half.js +9 -0
  63. package/src/themes/blocks/unicode-thin.js +9 -0
  64. package/src/themes/lines/ascii-compact.js +11 -0
  65. package/src/themes/lines/ascii-dots.js +9 -0
  66. package/src/themes/lines/ascii-girder.js +9 -0
  67. package/src/themes/lines/ascii-github.js +11 -0
  68. package/src/themes/lines/ascii-reddit.js +11 -0
  69. package/src/themes/lines/ascii-rounded.js +11 -0
  70. package/src/themes/lines/ascii.js +11 -0
  71. package/src/themes/lines/unicode-bold.js +15 -0
  72. package/src/themes/lines/unicode-rounded.js +15 -0
  73. package/src/themes/lines/unicode.js +15 -0
  74. package/src/themes/utils.js +38 -0
  75. package/src/turtle/draw-line-art.js +46 -0
  76. package/src/turtle/draw-unicode.js +33 -0
  77. package/src/turtle/index.js +12 -0
  78. package/src/turtle/turtle.js +286 -0
@@ -0,0 +1,108 @@
1
+ import Box from '../box.js';
2
+ import {addAlias} from '../meta.js';
3
+ import {fullBlock} from '../symbols.js';
4
+
5
+ export class Bitmap {
6
+ constructor(width, height, blockWidth = 5, blockHeight = 5) {
7
+ // parameters check
8
+ width = Math.round(width);
9
+ if (isNaN(width) || width <= 0) throw new Error(`Width should be a positive integer instead of "${width}"`);
10
+ height = Math.round(height);
11
+ if (isNaN(width) || height <= 0) throw new Error(`Height should be a positive integer instead of "${height}"`);
12
+ blockWidth = Math.round(blockWidth);
13
+ if (isNaN(width) || blockWidth <= 0)
14
+ throw new Error(`Block width should be a positive integer instead of "${blockWidth}"`);
15
+ blockHeight = Math.round(blockHeight);
16
+ if (isNaN(width) || blockHeight <= 0)
17
+ throw new Error(`Block height should be a positive integer instead of "${blockHeight}"`);
18
+ if (isNaN(width) || blockWidth * blockHeight > 32)
19
+ throw new Error("Multiplication of 'blockWidth' and 'blockHeight' should be equal or less than 32");
20
+
21
+ this.width = width;
22
+ this.height = height;
23
+ this.blockWidth = blockWidth;
24
+ this.blockHeight = blockHeight;
25
+
26
+ // create a bitmap
27
+ this.lineSize = Math.floor(this.width / this.blockWidth);
28
+ if (this.width % this.blockWidth) ++this.lineSize;
29
+ this.lineCount = Math.floor(this.height / this.blockHeight);
30
+ if (this.height % this.blockHeight) ++this.lineCount;
31
+ this.bitmap = new Array(this.lineCount * this.lineSize).fill(0);
32
+ }
33
+
34
+ verifyPos(x, y) {
35
+ if (x < 0 || x >= this.width) throw new Error(`X is out of bounds: "${x}`);
36
+ if (y < 0 || y >= this.height) throw new Error(`Y is out of bounds: "${y}`);
37
+ return this;
38
+ }
39
+
40
+ getWordIndex(x, y) {
41
+ const line = Math.floor(y / this.blockHeight),
42
+ linePos = Math.floor(x / this.blockWidth);
43
+ return line * this.lineSize + linePos;
44
+ }
45
+
46
+ getWordMask(x, y) {
47
+ const shiftX = x % this.blockWidth,
48
+ shiftY = y % this.blockHeight;
49
+ return 1 << (shiftX + this.blockWidth * shiftY);
50
+ }
51
+
52
+ getBit(x, y) {
53
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) return 0;
54
+ const index = this.getWordIndex(x, y),
55
+ mask = this.getWordMask(x, y);
56
+ return this.bitmap[index] & mask;
57
+ }
58
+
59
+ setBit(x, y, value = 1) {
60
+ if (x < 0 || x >= this.width || y < 0 || y >= this.height) return this;
61
+ const index = this.getWordIndex(x, y),
62
+ mask = this.getWordMask(x, y);
63
+ this.bitmap[index] =
64
+ value > 0 ? this.bitmap[index] | mask : value < 0 ? this.bitmap[index] ^ mask : this.bitmap[index] & ~mask;
65
+ return this;
66
+ }
67
+
68
+ clear(value) {
69
+ this.bitmap.fill(value ? ~0 : 0);
70
+ return this;
71
+ }
72
+
73
+ // toBox(one = fullBlock, zero = ' ') {
74
+ // // this algorithm is trivial, and not very efficient
75
+ // const result = [];
76
+ // for (let i = 0; i < this.height; ++i) {
77
+ // let row = '';
78
+ // for (let j = 0; j < this.width; ++j) row += this.getBit(j, i) ? one : zero;
79
+ // result.push(row);
80
+ // }
81
+ // return result;
82
+ // }
83
+
84
+ toBox(one = fullBlock, zero = ' ') {
85
+ const result = [];
86
+ for (let k = 0, kBase = 0; k < this.lineCount; ++k, kBase += this.blockHeight) {
87
+ const iLimit = Math.min(this.blockHeight, this.height - kBase);
88
+ for (let i = 0; i < iLimit; ++i) {
89
+ let row = '';
90
+ for (let j = 0, jBase = 0; j < this.lineSize; ++j, jBase += this.blockWidth) {
91
+ const index = k * this.lineSize + j,
92
+ word = this.bitmap[index],
93
+ mLimit = Math.min(this.blockWidth, this.width - jBase);
94
+ let mask = 1 << (this.blockWidth * i);
95
+ for (let m = 0; m < mLimit; ++m, mask <<= 1) {
96
+ row += word & mask ? one : zero;
97
+ }
98
+ }
99
+ result.push(row);
100
+ }
101
+ }
102
+ return new Box(result, true);
103
+ }
104
+ }
105
+
106
+ addAlias(Bitmap, 'set', 'setBit');
107
+
108
+ export default Bitmap;
@@ -0,0 +1,26 @@
1
+ // Drawing lines using Bresenham's line algorithm
2
+ // The canonic version from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
3
+
4
+ export const drawLine = (bmp, x0, y0, x1, y1, value = 1) => {
5
+ const dx = Math.abs(x1 - x0),
6
+ sx = x0 < x1 ? 1 : -1,
7
+ dy = -Math.abs(y1 - y0),
8
+ sy = y0 < y1 ? 1 : -1;
9
+ for (let error = dx + dy; ; ) {
10
+ bmp.setBit(x0, y0, value);
11
+ if (x0 == x1 && y0 == y1) break;
12
+ const e2 = 2 * error;
13
+ if (e2 >= dy) {
14
+ if (x0 == x1) break;
15
+ error += dy;
16
+ x0 += sx;
17
+ }
18
+ if (e2 <= dx) {
19
+ if (y0 == y1) break;
20
+ error += dx;
21
+ y0 += sy;
22
+ }
23
+ }
24
+ };
25
+
26
+ export default drawLine;
@@ -0,0 +1,216 @@
1
+ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
2
+ if (x1 < x0) [x0, x1] = [x1, x0];
3
+ if (y1 < y0) [y0, y1] = [y1, y0];
4
+
5
+ let indexL = bmp.getWordIndex(x0, y0),
6
+ indexR = bmp.getWordIndex(x1, y0);
7
+
8
+ const mask0 = bmp.getWordMask(x0, 0),
9
+ mask1 = bmp.getWordMask(x1, 0),
10
+ rowMask = (1 << bmp.blockWidth) - 1,
11
+ firstRows = y0 % bmp.blockHeight,
12
+ firstRowShift = bmp.blockWidth * firstRows,
13
+ height = y1 - y0 + 1;
14
+
15
+ if (firstRows + (y1 - y0) < bmp.blockHeight) {
16
+ if (indexL === indexR) {
17
+ // our rows are in a word: calculate a block mask
18
+ const mask = ~(mask0 - 1) & (mask1 | (mask1 - 1));
19
+
20
+ // apply the mask to the last incomplete block
21
+ let fullMask = mask;
22
+ for (let i = 1; i < height; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
23
+ fullMask <<= firstRowShift;
24
+
25
+ bmp.bitmap[indexL] =
26
+ value > 0
27
+ ? bmp.bitmap[indexL] | fullMask
28
+ : value < 0
29
+ ? bmp.bitmap[indexL] ^ fullMask
30
+ : bmp.bitmap[indexL] & ~fullMask;
31
+ return;
32
+ }
33
+
34
+ // our rows are all in one block vertically but over several words
35
+ // calculate a left mask and a right masks
36
+ const maskL = ~(mask0 - 1) & rowMask,
37
+ maskR = mask1 | (mask1 - 1);
38
+
39
+ // calculate masks
40
+ let fullMaskL = maskL,
41
+ fullMaskR = maskR,
42
+ fullMaskM = ((1 << (bmp.blockWidth * height)) - 1) << firstRowShift;
43
+ for (let i = 1; i < height; ++i) {
44
+ fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
45
+ fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
46
+ }
47
+ fullMaskL <<= firstRowShift;
48
+ fullMaskR <<= firstRowShift;
49
+
50
+ // apply masks
51
+ bmp.bitmap[indexL] =
52
+ value > 0
53
+ ? bmp.bitmap[indexL] | fullMaskL
54
+ : value < 0
55
+ ? bmp.bitmap[indexL] ^ fullMaskL
56
+ : bmp.bitmap[indexL] & ~fullMaskL;
57
+ for (let index = indexL + 1; index < indexR; ++index)
58
+ bmp.bitmap[index] =
59
+ value > 0
60
+ ? bmp.bitmap[index] | fullMaskM
61
+ : value < 0
62
+ ? bmp.bitmap[index] ^ fullMaskM
63
+ : bmp.bitmap[index] & ~fullMaskM;
64
+ bmp.bitmap[indexR] =
65
+ value > 0
66
+ ? bmp.bitmap[indexR] | fullMaskR
67
+ : value < 0
68
+ ? bmp.bitmap[indexR] ^ fullMaskR
69
+ : bmp.bitmap[indexR] & ~fullMaskR;
70
+ return;
71
+ }
72
+
73
+ const rowsOverFullBlock = height - (firstRows ? bmp.blockHeight - firstRows : 0),
74
+ fullLines = Math.floor(rowsOverFullBlock / bmp.blockHeight),
75
+ lastRows = rowsOverFullBlock % bmp.blockHeight;
76
+
77
+ if (indexL === indexR) {
78
+ // our row is confined in a word: calculate a row mask
79
+ const mask = ~(mask0 - 1) & (mask1 | (mask1 - 1));
80
+ if (firstRows) {
81
+ // apply the mask to the first incomplete block
82
+ let fullMask = mask;
83
+ for (let i = 1, n = bmp.blockHeight - firstRows; i < n; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
84
+ fullMask <<= firstRowShift;
85
+ bmp.bitmap[indexL] =
86
+ value > 0
87
+ ? bmp.bitmap[indexL] | fullMask
88
+ : value < 0
89
+ ? bmp.bitmap[indexL] ^ fullMask
90
+ : bmp.bitmap[indexL] & ~fullMask;
91
+ indexL += bmp.lineSize;
92
+ }
93
+ if (fullLines) {
94
+ // calculate a mask for full blocks
95
+ let fullMask = mask;
96
+ for (let i = 1; i < bmp.blockHeight; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
97
+ // apply the mask to full blocks
98
+ for (let i = 0; i < fullLines; ++i, indexL += bmp.lineSize)
99
+ bmp.bitmap[indexL] =
100
+ value > 0
101
+ ? bmp.bitmap[indexL] | fullMask
102
+ : value < 0
103
+ ? bmp.bitmap[indexL] ^ fullMask
104
+ : bmp.bitmap[indexL] & ~fullMask;
105
+ }
106
+ if (lastRows) {
107
+ // apply the mask to the last incomplete block
108
+ let fullMask = mask;
109
+ for (let i = 1; i < lastRows; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
110
+ bmp.bitmap[indexL] =
111
+ value > 0
112
+ ? bmp.bitmap[indexL] | fullMask
113
+ : value < 0
114
+ ? bmp.bitmap[indexL] ^ fullMask
115
+ : bmp.bitmap[indexL] & ~fullMask;
116
+ }
117
+ return;
118
+ }
119
+
120
+ // calculate a left mask and a right masks
121
+ const maskL = ~(mask0 - 1) & rowMask,
122
+ maskR = mask1 | (mask1 - 1);
123
+
124
+ if (firstRows) {
125
+ // apply the mask to the first incomplete block
126
+ let fullMaskL = maskL,
127
+ fullMaskR = maskR,
128
+ fullMaskM = ((1 << (bmp.blockWidth * (bmp.blockHeight - firstRows))) - 1) << firstRowShift;
129
+ for (let i = 1, n = bmp.blockHeight - firstRows; i < n; ++i) {
130
+ fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
131
+ fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
132
+ }
133
+ fullMaskL <<= firstRowShift;
134
+ fullMaskR <<= firstRowShift;
135
+ bmp.bitmap[indexL] =
136
+ value > 0
137
+ ? bmp.bitmap[indexL] | fullMaskL
138
+ : value < 0
139
+ ? bmp.bitmap[indexL] ^ fullMaskL
140
+ : bmp.bitmap[indexL] & ~fullMaskL;
141
+ for (let index = indexL + 1; index < indexR; ++index)
142
+ bmp.bitmap[index] =
143
+ value > 0
144
+ ? bmp.bitmap[index] | fullMaskM
145
+ : value < 0
146
+ ? bmp.bitmap[index] ^ fullMaskM
147
+ : bmp.bitmap[index] & ~fullMaskM;
148
+ bmp.bitmap[indexR] =
149
+ value > 0
150
+ ? bmp.bitmap[indexR] | fullMaskR
151
+ : value < 0
152
+ ? bmp.bitmap[indexR] ^ fullMaskR
153
+ : bmp.bitmap[indexR] & ~fullMaskR;
154
+ indexL += bmp.lineSize;
155
+ indexR += bmp.lineSize;
156
+ }
157
+
158
+ if (fullLines) {
159
+ // calculate masks
160
+ let fullMaskL = maskL,
161
+ fullMaskR = maskR;
162
+ for (let i = 1; i < bmp.blockHeight; ++i) {
163
+ fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
164
+ fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
165
+ }
166
+ // apply masks
167
+ for (let i = 0; i < fullLines; ++i, indexL += bmp.lineSize, indexR += bmp.lineSize) {
168
+ bmp.bitmap[indexL] =
169
+ value > 0
170
+ ? bmp.bitmap[indexL] | fullMaskL
171
+ : value < 0
172
+ ? bmp.bitmap[indexL] ^ fullMaskL
173
+ : bmp.bitmap[indexL] & ~fullMaskL;
174
+ for (let index = indexL + 1; index < indexR; ++index)
175
+ bmp.bitmap[index] = value > 0 ? ~0 : value < 0 ? ~bmp.bitmap[index] : 0;
176
+ bmp.bitmap[indexR] =
177
+ value > 0
178
+ ? bmp.bitmap[indexR] | fullMaskR
179
+ : value < 0
180
+ ? bmp.bitmap[indexR] ^ fullMaskR
181
+ : bmp.bitmap[indexR] & ~fullMaskR;
182
+ }
183
+ }
184
+
185
+ if (lastRows) {
186
+ // apply the mask to an incomplete block
187
+ let fullMaskL = maskL,
188
+ fullMaskR = maskR,
189
+ fullMaskM = (1 << (bmp.blockWidth * lastRows)) - 1;
190
+ for (let i = 1; i < lastRows; ++i) {
191
+ fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
192
+ fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
193
+ }
194
+ bmp.bitmap[indexL] =
195
+ value > 0
196
+ ? bmp.bitmap[indexL] | fullMaskL
197
+ : value < 0
198
+ ? bmp.bitmap[indexL] ^ fullMaskL
199
+ : bmp.bitmap[indexL] & ~fullMaskL;
200
+ for (let index = indexL + 1; index < indexR; ++index)
201
+ bmp.bitmap[index] =
202
+ value > 0
203
+ ? bmp.bitmap[index] | fullMaskM
204
+ : value < 0
205
+ ? bmp.bitmap[index] ^ fullMaskM
206
+ : bmp.bitmap[index] & ~fullMaskM;
207
+ bmp.bitmap[indexR] =
208
+ value > 0
209
+ ? bmp.bitmap[indexR] | fullMaskR
210
+ : value < 0
211
+ ? bmp.bitmap[indexR] ^ fullMaskR
212
+ : bmp.bitmap[indexR] & ~fullMaskR;
213
+ }
214
+ };
215
+
216
+ export default drawRect;
@@ -0,0 +1,24 @@
1
+ import Bitmap from './bitmap.js';
2
+ import drawLine from './draw-line.js';
3
+ import drawRect from './draw-rect.js';
4
+ import toQuads from './to-quads.js';
5
+
6
+ // patch Bitmap
7
+
8
+ Bitmap.prototype.line = function (...args) {
9
+ drawLine(this, ...args);
10
+ return this;
11
+ };
12
+ Bitmap.prototype.rect = function (...args) {
13
+ drawRect(this, ...args);
14
+ return this;
15
+ };
16
+ Bitmap.prototype.toQuads = function () {
17
+ return toQuads(this);
18
+ };
19
+ Bitmap.prototype.toStrings = function () {
20
+ return this.toBox().toStrings();
21
+ };
22
+
23
+ export {Bitmap, drawLine, drawRect, toQuads};
24
+ export default Bitmap;
@@ -0,0 +1,32 @@
1
+ import Box from '../box.js';
2
+ import {quadrants} from '../symbols.js';
3
+
4
+ export const toQuads = bmp => {
5
+ const result = [],
6
+ rowSize = Math.floor((bmp.width + 1) / 2),
7
+ accumulator = new Array(rowSize).fill(0);
8
+ for (let k = 0, kBase = 0; k < bmp.lineCount; ++k, kBase += bmp.blockHeight) {
9
+ const iLimit = Math.min(bmp.blockHeight, bmp.height - kBase);
10
+ for (let i = 0; i < iLimit; ++i) {
11
+ for (let j = 0, jBase = 0; j < bmp.lineSize; ++j, jBase += bmp.blockWidth) {
12
+ const index = k * bmp.lineSize + j,
13
+ word = bmp.bitmap[index],
14
+ mLimit = Math.min(bmp.blockWidth, bmp.width - jBase);
15
+ let mask = 1 << (bmp.blockWidth * i);
16
+ for (let m = 0; m < mLimit; ++m, mask <<= 1) {
17
+ accumulator[(jBase + m) >> 1] += word & mask ? ((kBase + i) & 1 ? 4 : 1) << ((jBase + m) & 1) : 0;
18
+ }
19
+ }
20
+ if ((kBase + i) & 1) {
21
+ result.push(accumulator.map(i => quadrants[i]).join(''));
22
+ if (kBase + i + 1 < bmp.height) accumulator.fill(0);
23
+ }
24
+ }
25
+ }
26
+ if (bmp.height & 1) {
27
+ result.push(accumulator.map(i => quadrants[i]).join(''));
28
+ }
29
+ return new Box(result, true);
30
+ };
31
+
32
+ export default toQuads;
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ export {Spinner} from './spinner.js';
4
+ import spin from './spin.js';
5
+
6
+ export {spin};
7
+
8
+ export default spin;
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ import {SpinnerBase} from './spinner.js';
4
+
5
+ class Spinner extends SpinnerBase {
6
+ constructor(strings, args) {
7
+ super(false);
8
+ this.strings = strings;
9
+ this.args = args;
10
+ this.indices = new WeakMap();
11
+ }
12
+
13
+ getFrame() {
14
+ let result = '';
15
+ for (let i = 0; i < this.args.length; ++i) {
16
+ const arg = this.args[i];
17
+
18
+ result += this.strings[i];
19
+
20
+ if (typeof arg == 'function') {
21
+ result += String(arg(this.state));
22
+ continue;
23
+ }
24
+
25
+ if (arg instanceof SpinnerBase) {
26
+ arg.active = this.active;
27
+ arg.paused = this.paused;
28
+ arg.finished = this.finished;
29
+ result += arg.getFrame();
30
+ continue;
31
+ }
32
+
33
+ if (Array.isArray(arg)) {
34
+ if (!this.indices.has(arg)) this.indices.set(arg, 0);
35
+ const index = this.indices.get(arg);
36
+ if (!this.finished && !this.paused && this.active) this.indices.set(arg, (index + 1) % arg.length);
37
+ result += String(arg[index]);
38
+ continue;
39
+ }
40
+
41
+ result += String(arg);
42
+ }
43
+
44
+ result += this.strings[this.args.length];
45
+ return result;
46
+ }
47
+ }
48
+
49
+ const spin = (strings, ...args) => new Spinner(strings, args);
50
+
51
+ export default spin;
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ import {checkMarkHeavy} from '../symbols.js';
4
+
5
+ export class SpinnerBase {
6
+ constructor(isStarted) {
7
+ this.active = isStarted;
8
+ this.paused = false;
9
+ this.finished = false;
10
+ this.frameIndex = 0;
11
+ }
12
+
13
+ get isStarted() {
14
+ return this.active || this.finished;
15
+ }
16
+
17
+ get isActive() {
18
+ return this.active;
19
+ }
20
+
21
+ get isFinished() {
22
+ return this.finished;
23
+ }
24
+
25
+ get state() {
26
+ if (this.finished) return 'finished';
27
+ if (this.active) return this.paused ? 'paused' : 'active';
28
+ return '';
29
+ }
30
+
31
+ set state(value) {
32
+ this.finished = this.active = this.paused = false;
33
+ switch (value) {
34
+ case 'finished':
35
+ this.finished = true;
36
+ break;
37
+ case 'paused':
38
+ this.active = this.paused = true;
39
+ break;
40
+ case 'active':
41
+ this.active = true;
42
+ break;
43
+ }
44
+ }
45
+
46
+ reset() {
47
+ this.active = this.paused = this.finished = false;
48
+ }
49
+
50
+ nextFrameIndex(length) {
51
+ return (this.frameIndex = (this.frameIndex + 1) % length);
52
+ }
53
+
54
+ getFrame() {
55
+ return 'X';
56
+ }
57
+ }
58
+
59
+ const defaultSpinnerDefinition = {notStarted: [' '], finished: [checkMarkHeavy], frames: [...'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏']};
60
+
61
+ export class Spinner extends SpinnerBase {
62
+ constructor(spinnerDefinition, isStarted) {
63
+ super(isStarted);
64
+ this.spinner = {...defaultSpinnerDefinition, ...spinnerDefinition};
65
+ }
66
+
67
+ getFrame() {
68
+ if (this.finished) return this.spinner.finished[this.nextFrameIndex(this.spinner.finished.length)];
69
+ if (!this.active) return this.spinner.notStarted[this.nextFrameIndex(this.spinner.notStarted.length)];
70
+ if (!this.paused) this.nextFrameIndex(this.spinner.frames.length);
71
+ return this.spinner.frames[this.frameIndex];
72
+ }
73
+ }
74
+
75
+ export default Spinner;
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ // Spinners are from https://github.com/sindresorhus/cli-spinners under the MIT license.
4
+ // Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
5
+ // See `cli-spinners` for more great options.
6
+
7
+ export const dots = {frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']};
8
+ export const sand = {frames: [...'⠁⠂⠄⡀⡈⡐⡠⣀⣁⣂⣄⣌⣔⣤⣥⣦⣮⣶⣷⣿⡿⠿⢟⠟⡛⠛⠫⢋⠋⠍⡉⠉⠑⠡⢁']};
9
+ export const line = {frames: ['-', '\\', '|', '/']};
10
+ export const pipe = {frame: ['┤', '┘', '┴', '└', '├', '┌', '┬', '┐']};
11
+ export const growVertical = {frame: ['▁', '▃', '▄', '▅', '▆', '▇', '▆', '▅', '▄', '▃']};
12
+ export const growHorizontal = {frame: ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '▊', '▋', '▌', '▍', '▎']};
13
+ export const noise = {frame: ['▓', '▒', '░']};
14
+ export const bounce = {frame: ['⠁', '⠂', '⠄', '⠂']};
15
+ export const arc = {frame: ['◜', '◠', '◝', '◞', '◡', '◟']};
16
+ export const squareQuarters = {frames: ['◰', '◳', '◲', '◱']};
17
+ export const circleQuarters = {frames: ['◴', '◷', '◶', '◵']};
18
+ export const circleHalves = {frames: ['◐', '◓', '◑', '◒']};
19
+ export const arrows = {frame: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙']};
20
+
21
+ export const clock = {
22
+ frames: ['🕛 ', '🕐 ', '🕑 ', '🕒 ', '🕓 ', '🕔 ', '🕕 ', '🕖 ', '🕗 ', '🕘 ', '🕙 ', '🕚 '],
23
+ notStarted: [' '],
24
+ finished: ['✔ ']
25
+ };
26
+
27
+ export const bouncingBar = {
28
+ frames: [
29
+ '[ ]',
30
+ '[= ]',
31
+ '[== ]',
32
+ '[=== ]',
33
+ '[====]',
34
+ '[ ===]',
35
+ '[ ==]',
36
+ '[ =]',
37
+ '[ ]',
38
+ '[ =]',
39
+ '[ ==]',
40
+ '[ ===]',
41
+ '[====]',
42
+ '[=== ]',
43
+ '[== ]',
44
+ '[= ]'
45
+ ],
46
+ notStarted: ['[ ]'],
47
+ finished: ['[####]']
48
+ };
49
+
50
+ export const bouncingBall = {
51
+ frames: [
52
+ '( ● )',
53
+ '( ● )',
54
+ '( ● )',
55
+ '( ● )',
56
+ '( ●)',
57
+ '( ● )',
58
+ '( ● )',
59
+ '( ● )',
60
+ '( ● )',
61
+ '(● )'
62
+ ],
63
+ notStarted: ['( )'],
64
+ finished: ['(●●●●●●)']
65
+ };
package/src/strings.js ADDED
@@ -0,0 +1,72 @@
1
+ export const matchCsiNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]/g;
2
+ export const matchCsiNoSgrNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x6C\x6E-\x7E]/g;
3
+
4
+ export const getLength = (s, matcher = matchCsiNoGroups) => {
5
+ s = String(s);
6
+ let counter = 0,
7
+ start = 0;
8
+ matcher.lastIndex = 0;
9
+ for (const match of s.matchAll(matcher)) {
10
+ counter += [...s.substring(start, match.index)].length;
11
+ start = match.index + match[0].length;
12
+ }
13
+ counter += [...s.substring(start)].length;
14
+ return counter;
15
+ };
16
+
17
+ export const getMaxLength = (strings, matcher = matchCsiNoGroups) =>
18
+ Math.max(0, ...strings.map(s => getLength(s, matcher)));
19
+
20
+ export const clip = (s, width, includeLastCommand, matcher = matchCsiNoGroups) => {
21
+ s = String(s);
22
+ let counter = 0,
23
+ start = 0;
24
+ matcher.lastIndex = 0;
25
+ for (const match of s.matchAll(matcher)) {
26
+ if (counter >= width) return s.substring(0, match.index + (includeLastCommand ? match[0].length : 0));
27
+ const prev = [...s.substring(start, match.index)];
28
+ counter += prev.length;
29
+ if (includeLastCommand ? counter > width : counter >= width) {
30
+ const diff = width - counter,
31
+ end = start + (diff ? prev.slice(0, prev.length + diff).join('').length : prev.length);
32
+ return s.substring(0, end);
33
+ }
34
+ start = match.index + match[0].length;
35
+ }
36
+ if (counter >= width) return s.substring(0, start);
37
+ const prev = [...s.substring(start)];
38
+ if (counter + prev.length > width) {
39
+ const end = start + prev.slice(0, width - counter).join('').length;
40
+ return s.substring(0, end);
41
+ }
42
+ return s; // unchanged
43
+ };
44
+
45
+ export const clipStrings = (strings, width, includeLastCommand, matcher = matchCsiNoGroups) =>
46
+ strings.map(s => clip(s, width, includeLastCommand, matcher));
47
+
48
+ export const toStrings = s => {
49
+ main: for (;;) {
50
+ switch (typeof s) {
51
+ case 'function':
52
+ for (let i = 0; i < 10 && typeof s == 'function'; ++i) s = s();
53
+ if (typeof s == 'function') s = String(s);
54
+ continue main;
55
+ case 'number':
56
+ case 'boolean':
57
+ return [String(s)];
58
+ case 'string':
59
+ return String(s).split(/\r?\n/g);
60
+ case 'object':
61
+ if (Array.isArray(s)) return [...s];
62
+ if (!s) break main;
63
+ if (typeof s.toStrings == 'function') return s.toStrings();
64
+ if (typeof s.toBox == 'function') return s.toBox().box;
65
+ if (typeof s.toPanel == 'function') return s.toPanel().toStrings();
66
+ s = String(s);
67
+ continue main;
68
+ }
69
+ break main;
70
+ }
71
+ return [];
72
+ };