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,63 @@
1
+ import style from '../../style.js';
2
+ import {optimize} from '../../ansi/sgr-state.js';
3
+ import {allocateSizes, makeBgFromFg} from '../utils.js';
4
+ import drawStackedChart from './draw-stacked.js';
5
+ import {vBlocks8th, ellipsis} from '../../symbols.js';
6
+ import defaultTheme from '../themes/default.js';
7
+
8
+ // data = [datum]
9
+ // datum = {value, colorState, symbol, state}
10
+
11
+ const defaultSymbol = vBlocks8th[7];
12
+
13
+ export const defaultDrawItem = (datum, size, _, {initState = {}}) =>
14
+ datum
15
+ ? style
16
+ .addState(initState)
17
+ .addState(datum.colorState || {})
18
+ .addState(datum.state || {})
19
+ .text((datum.symbol || defaultSymbol).repeat(size))
20
+ : '';
21
+
22
+ export const drawItemLabel = (datum, size, _, {reverse, truncate, useEllipsis = true, initState = {}}) => {
23
+ if (!datum) return '';
24
+ const symbol = datum.symbol || ' ';
25
+ let label = datum.label || '';
26
+ if (label.length <= size) {
27
+ label = reverse ? label.padStart(size, symbol) : label.padEnd(size, symbol);
28
+ } else if (truncate && (!useEllipsis || size > 1)) {
29
+ label = useEllipsis ? label.substring(0, size - 1) + ellipsis : label.substring(0, size);
30
+ } else {
31
+ label = symbol.repeat(size);
32
+ }
33
+ return style
34
+ .addState(initState)
35
+ .addState(datum.colorState ? makeBgFromFg(datum.colorState) : {})
36
+ .addState(datum.state || {})
37
+ .text(label);
38
+ };
39
+
40
+ export const drawRow = (data, width, maxValue, options = {}) => {
41
+ const {drawItem = defaultDrawItem, rectSize = 0, theme = defaultTheme, initState, reverse} = options,
42
+ {symbol = ' ', state = null, colorState} = theme?.empty || {},
43
+ sizes = allocateSizes(data, maxValue, width),
44
+ items = data.map((datum, index) => drawItem(datum, sizes[index], {index, data, sizes, maxValue, width}, options));
45
+ if (theme?.empty && sizes[sizes.length - 1] > 0) {
46
+ // fill the row
47
+ items.push(
48
+ style
49
+ .addState(initState)
50
+ .addState(colorState)
51
+ .addState(state)
52
+ .text(symbol.repeat(sizes[sizes.length - 1]))
53
+ );
54
+ }
55
+ if (reverse) items.reverse();
56
+ const row = optimize(items.join(''));
57
+ if (rectSize <= 1) return row;
58
+ return new Array(rectSize).fill(row);
59
+ };
60
+
61
+ export const drawChart = drawStackedChart(drawRow);
62
+
63
+ export default drawChart;
@@ -0,0 +1,6 @@
1
+ import {drawColumn} from './block-frac.js';
2
+ import drawGroupedChart from './draw-grouped.js';
3
+
4
+ export const drawChart = drawGroupedChart(drawColumn);
5
+
6
+ export default drawChart;
@@ -0,0 +1,30 @@
1
+ import style from '../../style.js';
2
+ import Box from '../../box.js';
3
+ import {optimize} from '../../ansi/sgr-state.js';
4
+ import {allocateSizes} from '../utils.js';
5
+ import drawStackedChart from './draw-stacked.js';
6
+ import {drawRealWidthBlock} from '../../draw-block-frac.js';
7
+
8
+ // data = [datum]
9
+ // datum = {value, colorState, symbol, state}
10
+
11
+ export const drawColumn = (data, width, maxValue, options = {}) => {
12
+ const {reverse, drawEmptyBorder, initState} = options,
13
+ rectSize = Math.max(0, options.rectSize ?? 0.5),
14
+ sizes = allocateSizes(data, maxValue, width),
15
+ blocks = data.map((datum, i) => {
16
+ if (!datum) return Box.makeBlank(/*getFracSize(rectSize, drawEmptyBorder)*/ 0, 0);
17
+ const box = drawRealWidthBlock(rectSize, sizes[i], drawEmptyBorder),
18
+ boxStyle = style.addState(initState).addState(datum.colorState).addState(datum.state);
19
+ return new Box(
20
+ box.box.map(line => boxStyle.text(line)),
21
+ true
22
+ );
23
+ }),
24
+ result = (reverse ? blocks : blocks.reverse()).map(block => block.box).flat(1);
25
+ return result.map(line => optimize(line));
26
+ };
27
+
28
+ export const drawChart = drawStackedChart(drawColumn);
29
+
30
+ export default drawChart;
@@ -0,0 +1,6 @@
1
+ import {drawColumn} from './block.js';
2
+ import drawGroupedChart from './draw-grouped.js';
3
+
4
+ export const drawChart = drawGroupedChart(drawColumn);
5
+
6
+ export default drawChart;
@@ -0,0 +1,37 @@
1
+ import style from '../../style.js';
2
+ import Box from '../../box.js';
3
+ import {optimize} from '../../ansi/sgr-state.js';
4
+ import {allocateSizes} from '../utils.js';
5
+ import drawStackedChart from './draw-stacked.js';
6
+ import drawBlock from '../../draw-block.js';
7
+ import defaultBlockTheme from '../../themes/blocks/unicode-half.js';
8
+
9
+ // data = [datum]
10
+ // datum = {value, colorState, symbol, state}
11
+
12
+ export const drawColumn = (data, width, maxValue, options = {}) => {
13
+ const {reverse, blockTheme = defaultBlockTheme, rectSize = 0, initState = {}} = options,
14
+ sizes = allocateSizes(data, maxValue, width),
15
+ {t = reverse ? 0 : 1, b = reverse ? 1 : 0, l = 1, r = 1} = options;
16
+
17
+ const blocks = data.map((datum, i) => {
18
+ if (!datum) return Box.makeBlank(Math.max(2, rectSize), 0);
19
+ const box = drawBlock(Math.max(0, rectSize - 2), sizes[i], blockTheme, {
20
+ bottom: reverse ? (i + 1 < data.length ? 0 : b) : i ? 0 : b,
21
+ top: reverse ? (i ? 0 : t) : i + 1 < data.length ? 0 : t,
22
+ left: l,
23
+ right: r
24
+ }),
25
+ boxStyle = style.addState(initState).addState(datum.colorState).addState(datum.state);
26
+ return new Box(
27
+ box.box.map(line => boxStyle.text(line)),
28
+ true
29
+ );
30
+ }),
31
+ result = (reverse ? blocks : blocks.reverse()).map(block => block.box).flat(1);
32
+ return result.map(line => optimize(line));
33
+ };
34
+
35
+ export const drawChart = drawStackedChart(drawColumn);
36
+
37
+ export default drawChart;
@@ -0,0 +1,48 @@
1
+ import defaultTheme from '../themes/default.js';
2
+ import {normalizeData} from '../utils.js';
3
+ import Panel from '../../panel.js';
4
+ import style from '../../style.js';
5
+
6
+ export const drawChart =
7
+ drawColumn =>
8
+ (values, width, options = {}) => {
9
+ const {maxValue, reverse, initState, groupGap = 1, gap = 0, theme = defaultTheme} = options;
10
+ if (isNaN(width) || width <= 0) throw new Error(`"width" should be a positive integer instead of "${width}"`);
11
+
12
+ const data = normalizeData(values, theme),
13
+ maxSeriesLength = Math.max(0, ...data.map(series => series.length));
14
+
15
+ // normalize length of series and flatten the result
16
+ const newData = [];
17
+ data.forEach(series => {
18
+ newData.push(...series);
19
+ if (series.length < maxSeriesLength) newData.push(...new Array(maxSeriesLength - series.length).fill(null));
20
+ });
21
+
22
+ const max =
23
+ typeof maxValue == 'number' && maxValue >= 0
24
+ ? maxValue
25
+ : Math.max(0, ...newData.map(datum => datum?.value || 0)),
26
+ {symbol = ' ', state = null, colorState} = theme?.empty || {},
27
+ emptyState = style.addState(initState).addState(colorState).addState(state).getState();
28
+
29
+ const panel = new Panel(0, width);
30
+ for (let i = 0; i < newData.length; ++i) {
31
+ if (i) {
32
+ if (i % maxSeriesLength == 0) {
33
+ if (groupGap > 0) panel.padRight(groupGap);
34
+ } else {
35
+ if (gap > 0) panel.padRight(gap);
36
+ }
37
+ }
38
+ const p = Panel.make(drawColumn([newData[i]], width, max, options));
39
+ if (p) {
40
+ panel.addRight(p, {align: reverse ? 'top' : 'bottom'});
41
+ } else {
42
+ panel.padRight(1);
43
+ }
44
+ }
45
+ return panel.toBox({emptySymbol: symbol, emptyState}).box;
46
+ };
47
+
48
+ export default drawChart;
@@ -0,0 +1,31 @@
1
+ import defaultTheme from '../themes/default.js';
2
+ import {normalizeData, sumValues} from '../utils.js';
3
+ import Panel from '../../panel.js';
4
+ import style from '../../style.js';
5
+
6
+ export const drawChart =
7
+ drawColumn =>
8
+ (values, width, options = {}) => {
9
+ const {maxValue, reverse, initState, gap = 0, theme = defaultTheme} = options;
10
+ if (isNaN(width) || width <= 0) throw new Error(`"width" should be a positive integer instead of "${width}"`);
11
+
12
+ const data = normalizeData(values, theme),
13
+ max =
14
+ typeof maxValue == 'number' ? (maxValue < 0 ? -1 : maxValue) : Math.max(0, ...data.map(row => sumValues(row))),
15
+ {symbol = ' ', state = null, colorState} = theme?.empty || {},
16
+ emptyState = style.addState(initState).addState(colorState).addState(state).getState();
17
+
18
+ const panel = new Panel(0, width);
19
+ for (let i = 0; i < data.length; ++i) {
20
+ if (i && gap > 0) panel.padRight(gap);
21
+ const p = Panel.make(drawColumn(data[i], width, max, options));
22
+ if (p) {
23
+ panel.addRight(p, {align: reverse ? 'top' : 'bottom'});
24
+ } else {
25
+ panel.padRight(1);
26
+ }
27
+ }
28
+ return panel.toBox({emptySymbol: symbol, emptyState}).box;
29
+ };
30
+
31
+ export default drawChart;
@@ -0,0 +1,27 @@
1
+ import style from '../../style.js';
2
+ import Box from '../../box.js';
3
+ import {optimize} from '../../ansi/sgr-state.js';
4
+ import {drawRealHeightBlock} from '../../draw-block-frac.js';
5
+ import drawGroupedChart from './draw-grouped.js';
6
+
7
+ // data = [datum]
8
+ // datum = {value, colorState, symbol, state}
9
+
10
+ export const drawColumn = (data, width, maxValue, options = {}) => {
11
+ const {reverse, rectSize = 1, initState = {}} = options,
12
+ blocks = data.map(datum => {
13
+ if (!datum) return Box.makeBlank(rectSize, 0);
14
+ const box = drawRealHeightBlock(rectSize, (datum.value / maxValue) * width),
15
+ boxStyle = style.addState(initState).addState(datum.colorState).addState(datum.state);
16
+ return new Box(
17
+ box.box.map(line => boxStyle.text(line)),
18
+ true
19
+ );
20
+ }),
21
+ result = (reverse ? blocks : blocks.reverse()).map(block => block.box).flat(1);
22
+ return result.map(line => optimize(line));
23
+ };
24
+
25
+ export const drawChart = drawGroupedChart(drawColumn);
26
+
27
+ export default drawChart;
@@ -0,0 +1,6 @@
1
+ import {drawColumn} from './plain.js';
2
+ import drawGroupedChart from './draw-grouped.js';
3
+
4
+ export const drawChart = drawGroupedChart(drawColumn);
5
+
6
+ export default drawChart;
@@ -0,0 +1,39 @@
1
+ import style from '../../style.js';
2
+ import Box from '../../box.js';
3
+ import {optimize} from '../../ansi/sgr-state.js';
4
+ import {allocateSizes} from '../utils.js';
5
+ import drawStackedChart from './draw-stacked.js';
6
+ import {hBlocks8th} from '../../symbols.js';
7
+ import defaultTheme from '../themes/default.js';
8
+
9
+ // data = [datum]
10
+ // datum = {value, colorState, symbol, state}
11
+
12
+ const defaultSymbol = hBlocks8th[7];
13
+
14
+ export const drawColumn = (data, width, maxValue, options = {}) => {
15
+ const {reverse, rectSize = 1, initState = {}, theme = defaultTheme} = options,
16
+ {symbol = ' ', state = null, colorState} = theme?.empty || {},
17
+ sizes = allocateSizes(data, maxValue, width),
18
+ blocks = data.map((datum, i) => {
19
+ if (!datum) return Box.makeBlank(rectSize, 0);
20
+ const boxStyle = style.addState(initState).addState(datum.colorState).addState(datum.state);
21
+ return new Box(new Array(sizes[i]).fill(boxStyle.text(datum.symbol || defaultSymbol)), true);
22
+ });
23
+ if (sizes[sizes.length - 1] > 0) {
24
+ blocks.push(
25
+ new Box(
26
+ new Array(sizes[sizes.length - 1]).fill(
27
+ style.addState(initState).addState(colorState).addState(state).text(symbol.repeat(rectSize))
28
+ ),
29
+ true
30
+ )
31
+ );
32
+ }
33
+ const result = (reverse ? blocks : blocks.reverse()).map(block => block.box).flat(1);
34
+ return result.map(line => optimize(line));
35
+ };
36
+
37
+ export const drawChart = drawStackedChart(drawColumn);
38
+
39
+ export default drawChart;
@@ -0,0 +1,12 @@
1
+ import {capitalize} from '../../meta.js';
2
+ import style from '../../style.js';
3
+
4
+ const seriesColors = 'cyan,magenta,blue,yellow,green,red'.split(',');
5
+
6
+ export const chartTheme = [
7
+ ...seriesColors.map(name => ({colorState: style['bright' + capitalize(name)].getState()})),
8
+ ...seriesColors.map(name => ({colorState: style[name].getState()}))
9
+ ];
10
+ chartTheme.empty = {state: style.reset.all.getState(), symbol: ' '};
11
+
12
+ export default chartTheme;
@@ -0,0 +1,5 @@
1
+ import rainbow from "./rainbow.js";
2
+
3
+ export const chartTheme = rainbow.slice().reverse();
4
+
5
+ export default chartTheme;
@@ -0,0 +1,8 @@
1
+ import style from '../../style.js';
2
+
3
+ // red, orange, yellow, green, blue, indigo, violet
4
+ const colors = [0xff0000, 0xffa500, 0xffff00, 0x008000, 0x0000ff, 0x4b0082, 0xee82ee];
5
+
6
+ export const chartTheme = colors.map(color => ({colorState: style.hex(color).getState()}));
7
+
8
+ export default chartTheme;
@@ -0,0 +1,75 @@
1
+ import {Commands} from '../ansi/sgr.js';
2
+ import defaultTheme from './themes/default.js';
3
+
4
+ export const makeBgFromFg = state => ({
5
+ background: !state.foreground
6
+ ? null
7
+ : Array.isArray(state.foreground)
8
+ ? [Commands.BG_EXTENDED_COLOR, ...state.foreground.slice(1)]
9
+ : Number(state.foreground) + 10
10
+ });
11
+
12
+ export const makeFgFromBg = state => ({
13
+ foreground: !state.background
14
+ ? null
15
+ : Array.isArray(state.background)
16
+ ? [Commands.EXTENDED_COLOR, ...state.background.slice(1)]
17
+ : Number(state.background) - 10
18
+ });
19
+
20
+ export const sumValues = series => series.reduce((acc, datum) => acc + (datum?.value || 0), 0);
21
+
22
+ export const normalizeData = (data, theme) =>
23
+ data.map(series => {
24
+ if (!Array.isArray(series)) series = [series];
25
+ return series.map((datum, i) => {
26
+ if (typeof datum == 'number') datum = {value: datum};
27
+ const value = Math.max(0, datum?.value ?? 0),
28
+ defaultSeriesTheme = defaultTheme[i % defaultTheme.length],
29
+ seriesTheme = theme[i % theme.length];
30
+ return {...defaultSeriesTheme, ...seriesTheme, ...datum, value: isNaN(value) || value < 0 ? 0 : value};
31
+ });
32
+ });
33
+
34
+ export const allocateSizes = (data, maxValue, size) => {
35
+ const values = data.map((datum, index) => ({value: datum?.value || 0, index})),
36
+ seriesValue = values.reduce((acc, datum) => acc + datum.value, 0);
37
+
38
+ if (maxValue < 0) {
39
+ maxValue = seriesValue;
40
+ if (!maxValue) maxValue = 1;
41
+ }
42
+ if (seriesValue < maxValue) {
43
+ values.push({value: maxValue - seriesValue, index: -1}); // add an empty bin
44
+ } else {
45
+ // truncate values above maxValue
46
+ let acc = 0;
47
+ for (const datum of values) {
48
+ datum.value = acc < maxValue ? Math.min(datum.value, maxValue - acc) : 0;
49
+ acc += datum.value;
50
+ }
51
+ }
52
+
53
+ let allocated = 0;
54
+ values.forEach(datum => {
55
+ const allocation = (datum.value / maxValue) * size;
56
+ allocated += datum.size = Math.floor(allocation);
57
+ datum.frac = allocation - datum.size;
58
+ });
59
+
60
+ values.sort((a, b) => b.frac - a.frac);
61
+ for (let i = 0, n = size - allocated; i < n; ++i) ++values[i].size;
62
+
63
+ const sizes = new Array(data.length + 1).fill(0);
64
+ values.forEach(datum => (sizes[datum.index >= 0 ? datum.index : sizes.length - 1] = datum.size));
65
+ return sizes;
66
+ };
67
+
68
+ export const getFracSize = (value, drawEmptyBorder) => {
69
+ const intValue = Math.floor(value),
70
+ hasFrac = value - intValue > 0,
71
+ index = (value - intValue) * 8,
72
+ drawBorder = hasFrac && (drawEmptyBorder || index > 0);
73
+
74
+ return intValue + (drawBorder ? 1 : 0);
75
+ };
@@ -0,0 +1,33 @@
1
+ import {fullBlock, hBlocks8th, vBlocks8th} from './symbols.js';
2
+ import Box from './box.js';
3
+
4
+ export const drawRealWidthBlock = (realWidth, height, drawEmptyBorder) => {
5
+ realWidth = Math.max(0, realWidth);
6
+ height = Math.max(0, Math.floor(height));
7
+
8
+ const intWidth = Math.floor(realWidth),
9
+ index = Math.round((realWidth - intWidth) * 8),
10
+ hasFrac = realWidth - intWidth > 0,
11
+ drawBorder = hasFrac && (drawEmptyBorder || index > 0);
12
+
13
+ let line = fullBlock.repeat(intWidth);
14
+ if (drawBorder) line += hBlocks8th[index];
15
+ return new Box(new Array(height).fill(line), true);
16
+ };
17
+
18
+ export const drawRealHeightBlock = (width, realHeight, drawEmptyBorder) => {
19
+ width = Math.max(0, Math.floor(width));
20
+ realHeight = Math.max(0, realHeight);
21
+
22
+ const intHeight = Math.floor(realHeight),
23
+ index = Math.round((realHeight - intHeight) * 8),
24
+ hasFrac = realHeight - intHeight > 0,
25
+ drawBorder = hasFrac && (drawEmptyBorder || index > 0);
26
+
27
+ let result = fullBlock.repeat(intHeight);
28
+ if (drawBorder) result += hBlocks8th[index];
29
+ return new Box(
30
+ [...(drawBorder ? [vBlocks8th[index].repeat(width)] : []), ...new Array(intHeight).fill(fullBlock.repeat(width))],
31
+ true
32
+ );
33
+ };
@@ -0,0 +1,55 @@
1
+ import Box from './box.js';
2
+
3
+ const getIndex = (h, v) => 4 * h + v;
4
+
5
+ const T = 1,
6
+ B = 2,
7
+ L = 1,
8
+ R = 2,
9
+ LT = getIndex(L, T),
10
+ RT = getIndex(R, T),
11
+ LB = getIndex(L, B),
12
+ RB = getIndex(R, B);
13
+
14
+ export const drawBlock = (
15
+ width,
16
+ height,
17
+ blockTheme,
18
+ {top, bottom, left, right, vTheme, hTheme, theme, symbol} = {}
19
+ ) => {
20
+ // decode options
21
+ top ??= hTheme ?? theme ?? 1;
22
+ bottom ??= hTheme ?? theme ?? 1;
23
+ left ??= vTheme ?? theme ?? 1;
24
+ right ??= vTheme ?? theme ?? 1;
25
+ symbol ??= blockTheme?.f || ' ';
26
+
27
+ const topLine = top
28
+ ? (left ? blockTheme['t_' + top + '_' + left][LT] : '') +
29
+ blockTheme['h_' + top][T].repeat(width) +
30
+ (right ? blockTheme['t_' + top + '_' + right][RT] : '')
31
+ : null,
32
+ bottomLine = bottom
33
+ ? (left ? blockTheme['t_' + bottom + '_' + left][LB] : '') +
34
+ blockTheme['h_' + bottom][B].repeat(width) +
35
+ (right ? blockTheme['t_' + bottom + '_' + right][RB] : '')
36
+ : null,
37
+ middleLine =
38
+ (left ? blockTheme['v_' + left][L] : '') + symbol.repeat(width) + (right ? blockTheme['v_' + right][R] : '');
39
+
40
+ return new Box(
41
+ [
42
+ topLine === null ? [] : [topLine],
43
+ new Array(height).fill(middleLine),
44
+ bottomLine === null ? [] : [bottomLine]
45
+ ].flat(1),
46
+ true
47
+ );
48
+ };
49
+
50
+ export const drawFrame = (width, height, blockTheme, options) => {
51
+ if (!options?.symbol) options = {...options, symbol: ' '};
52
+ return drawBlock(width, height, blockTheme, options);
53
+ }
54
+
55
+ export default drawBlock;
package/src/meta.js ADDED
@@ -0,0 +1,41 @@
1
+ export const capitalize = name => (name ? name[0].toUpperCase() + name.substring(1).toLowerCase() : name);
2
+
3
+ export const toCamelCase = names =>
4
+ names.map((name, index) => (index ? capitalize(name) : name.toLowerCase())).join('');
5
+ export const fromCamelCase = name => name.split(/(?=[A-Z])/g);
6
+
7
+ export const toPascalCase = names => names.map(name => capitalize(name)).join('');
8
+ export const fromPascalCase = name => name.split(/(?=[A-Z])/g);
9
+
10
+ export const toAllCapsSnakeCase = names => names.map(name => name.toUpperCase()).join('_');
11
+ export const toSnakeCase = names => names.map(name => name.toLowerCase()).join('_');
12
+ export const fromSnakeCase = name => name.split('_');
13
+
14
+ export const toKebabCase = names => names.map(name => name.toLowerCase()).join('-');
15
+ export const fromKebabCase = name => name.split('-');
16
+
17
+ export const addGetter = (Class, name, getter, force) => {
18
+ const object = Class.prototype || Class;
19
+ if (!force && object.hasOwnProperty(name)) return object;
20
+ return Object.defineProperty(object, name, {configurable: true, enumerable: true, get: getter});
21
+ };
22
+
23
+ export const addGetters = (Class, getters, force) => {
24
+ for (const [name, value] of Object.entries(getters)) {
25
+ addGetter(Class, name, value, force);
26
+ }
27
+ };
28
+
29
+ export const addAlias = (Class, name, oldName, force) => {
30
+ const object = Class.prototype || Class;
31
+ if (!force && object.hasOwnProperty(name)) return object;
32
+ const descriptor = Object.getOwnPropertyDescriptor(object, oldName);
33
+ if (!descriptor) return object;
34
+ return Object.defineProperty(object, name, descriptor);
35
+ };
36
+
37
+ export const addAliases = (Class, aliases, force) => {
38
+ for (const [name, oldName] of Object.entries(aliases)) {
39
+ addAlias(Class, name, oldName, force);
40
+ }
41
+ };
@@ -0,0 +1,40 @@
1
+ import process from 'node:process';
2
+ import {matchCsiNoGroups} from '../strings.js';
3
+ import Box from '../box.js';
4
+
5
+ export const log = (s, {endOfLineCommand = '\x1B[m', colorDepth = 24} = {}) => {
6
+ s = Box.make(s);
7
+ if (colorDepth < 4) {
8
+ matchCsiNoGroups.lastIndex = 0;
9
+ s.box.forEach(row => console.log((row + endOfLineCommand).replace(matchCsiNoGroups, '')));
10
+ return;
11
+ }
12
+ s.box.forEach(row => console.log(row + endOfLineCommand));
13
+ };
14
+
15
+ export const out = (s, {stream = process.stdout, endOfLineCommand = '\x1B[m', colorDepth} = {}) => {
16
+ s = Box.make(s);
17
+ if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
18
+ if (colorDepth < 4) {
19
+ matchCsiNoGroups.lastIndex = 0;
20
+ s.box.forEach(row => stream.write((row + endOfLineCommand).replace(matchCsiNoGroups, '') + '\n'));
21
+ return;
22
+ }
23
+ s.box.forEach(row => stream.write(row + endOfLineCommand + '\n'));
24
+ };
25
+
26
+ export class Out {
27
+ constructor(stream) {
28
+ this.stream = stream;
29
+ this.colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
30
+ }
31
+ out(s, {endOfLineCommand = '\x1B[m', colorDepth} = {}) {
32
+ if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = this.colorDepth;
33
+ return out(s, {stream: this.stream, endOfLineCommand, colorDepth});
34
+ }
35
+ }
36
+
37
+ export const debug = string =>
38
+ console.log(
39
+ string.replace(/[\x00-\x1F]/g, m => '\\x' + m[0].charCodeAt(0).toString(16).padStart(2, '0').toUpperCase())
40
+ );
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ import Writer from './writer.js';
4
+ import {toStrings} from '../strings.js';
5
+ import {cursorUp, setCommands} from '../ansi/csi.js';
6
+
7
+ const RESET = setCommands([]);
8
+
9
+ export class Updater {
10
+ constructor(updater, {prologue, epilogue, noLastNewLine} = {}, writer = new Writer()) {
11
+ this.updater = updater;
12
+ this.writer = writer;
13
+ this.prologue = prologue || RESET;
14
+ this.epilogue = epilogue || RESET;
15
+ this.noLastNewLine = noLastNewLine;
16
+ this.lastHeight = 0;
17
+ this.isDone = false;
18
+ this.first = true;
19
+ this.intervalHandle = null;
20
+ }
21
+
22
+ get isRefreshing() {
23
+ return this.intervalHandle !== null;
24
+ }
25
+
26
+ startRefreshing(ms = 100) {
27
+ if (this.intervalHandle || this.isDone || !this.writer.isTTY) return;
28
+ this.intervalHandle = setInterval(this.update.bind(this), ms);
29
+ }
30
+
31
+ stopRefreshing() {
32
+ if (!this.intervalHandle) return;
33
+ clearInterval(this.intervalHandle);
34
+ this.intervalHandle = null;
35
+ }
36
+
37
+ getFrame(state) {
38
+ if (typeof this.updater == 'function') return this.updater(state);
39
+ if (typeof this.updater?.getFrame == 'function') {
40
+ this.updater.state = state;
41
+ return this.updater.getFrame();
42
+ }
43
+ throw new TypeError('Updater must be a function or implement getFrame()');
44
+ }
45
+
46
+ async writeFrame(state) {
47
+ if (this.first) {
48
+ this.prologue && (await this.writer.writeString(this.prologue));
49
+ this.first = false;
50
+ }
51
+
52
+ const frame = toStrings(this.getFrame(state));
53
+ if (!frame) return;
54
+
55
+ if (this.lastHeight) await this.writer.writeString('\r' + cursorUp(this.lastHeight));
56
+
57
+ this.lastHeight = frame.length;
58
+ if (this.noLastNewLine) --this.lastHeight;
59
+
60
+ await this.writer.write(frame, false, this.noLastNewLine);
61
+ }
62
+
63
+ async done() {
64
+ if (this.isDone) return;
65
+ this.isDone = true;
66
+ this.stopRefreshing();
67
+ this.epilogue && (await this.writer.writeString(this.epilogue));
68
+ }
69
+
70
+ async update(state = 'active') {
71
+ if (this.isDone || !this.writer.isTTY) return;
72
+ await this.writeFrame(state);
73
+ }
74
+
75
+ async final() {
76
+ if (this.isDone) return;
77
+ await this.writeFrame('finished');
78
+ await this.done();
79
+ }
80
+ }
81
+
82
+ export default Updater;