console-toolkit 1.2.15 → 1.3.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.
- package/README.md +3 -0
- package/llms-full.txt +64 -4
- package/package.json +6 -6
- package/src/alphanumeric/arrows.js +0 -23
- package/src/alphanumeric/fractions.js +0 -49
- package/src/alphanumeric/number-formatters.js +0 -44
- package/src/alphanumeric/roman.js +0 -12
- package/src/alphanumeric/unicode-cultural-numbers.js +0 -1
- package/src/alphanumeric/unicode-letters.js +0 -8
- package/src/alphanumeric/unicode-numbers.js +0 -21
- package/src/alphanumeric/utils.js +0 -26
- package/src/ansi/csi.js +0 -49
- package/src/ansi/sgr-state.js +7 -50
- package/src/ansi/sgr.js +0 -420
- package/src/box.d.ts +4 -1
- package/src/box.js +15 -115
- package/src/charts/bars/block-frac-grouped.js +0 -6
- package/src/charts/bars/block-frac.js +1 -14
- package/src/charts/bars/block-grouped.js +0 -6
- package/src/charts/bars/block.js +1 -14
- package/src/charts/bars/draw-grouped.js +0 -4
- package/src/charts/bars/draw-stacked.js +0 -4
- package/src/charts/bars/frac-grouped.js +2 -14
- package/src/charts/bars/plain-grouped.js +0 -6
- package/src/charts/bars/plain.js +1 -28
- package/src/charts/columns/block-frac-grouped.js +0 -6
- package/src/charts/columns/block-frac.js +0 -13
- package/src/charts/columns/block-grouped.js +0 -6
- package/src/charts/columns/block.js +0 -13
- package/src/charts/columns/draw-grouped.js +1 -5
- package/src/charts/columns/draw-stacked.js +0 -4
- package/src/charts/columns/frac-grouped.js +1 -13
- package/src/charts/columns/plain-grouped.js +0 -6
- package/src/charts/columns/plain.js +0 -13
- package/src/charts/themes/default.js +0 -1
- package/src/charts/themes/rainbow-reversed.js +0 -1
- package/src/charts/themes/rainbow.js +0 -1
- package/src/charts/utils.js +2 -28
- package/src/draw-block-frac.js +0 -14
- package/src/draw-block.js +0 -24
- package/src/meta.js +0 -64
- package/src/output/show.d.ts +8 -3
- package/src/output/show.js +7 -38
- package/src/output/updater.d.ts +8 -3
- package/src/output/updater.js +4 -51
- package/src/output/writer.js +1 -53
- package/src/panel.d.ts +16 -10
- package/src/panel.js +21 -276
- package/src/plot/bitmap.js +5 -33
- package/src/plot/draw-line.js +0 -8
- package/src/plot/draw-rect.js +25 -103
- package/src/plot/index.js +0 -22
- package/src/plot/to-quads.js +3 -6
- package/src/spinner/spin.js +23 -20
- package/src/spinner/spinner.d.ts +16 -2
- package/src/spinner/spinner.js +22 -34
- package/src/spinner/spinners.js +0 -16
- package/src/strings/clip.js +0 -10
- package/src/strings/parse.js +0 -7
- package/src/strings/split.js +0 -15
- package/src/strings.d.ts +2 -0
- package/src/strings.js +2 -32
- package/src/style.js +2 -58
- package/src/symbols.js +0 -84
- package/src/table/draw-borders.js +0 -9
- package/src/table/index.d.ts +1 -1
- package/src/table/index.js +0 -1
- package/src/table/table.js +3 -58
- package/src/themes/blocks/unicode-half.js +0 -1
- package/src/themes/blocks/unicode-thin.js +0 -1
- package/src/themes/lines/ascii-compact.js +5 -9
- package/src/themes/lines/ascii-dots.js +2 -7
- package/src/themes/lines/ascii-girder.js +4 -7
- package/src/themes/lines/ascii-github.js +5 -9
- package/src/themes/lines/ascii-reddit.js +5 -9
- package/src/themes/lines/ascii-rounded.js +5 -9
- package/src/themes/lines/ascii.js +5 -9
- package/src/themes/lines/unicode-bold.js +8 -14
- package/src/themes/lines/unicode-rounded.js +8 -14
- package/src/themes/lines/unicode.js +8 -14
- package/src/themes/utils.d.ts +6 -0
- package/src/themes/utils.js +6 -7
- package/src/turtle/draw-line-art.js +26 -18
- package/src/turtle/draw-unicode.js +0 -8
- package/src/turtle/index.d.ts +1 -1
- package/src/turtle/index.js +0 -8
- package/src/turtle/turtle.js +0 -120
|
@@ -11,13 +11,6 @@ import defaultTheme from '../themes/default.js';
|
|
|
11
11
|
|
|
12
12
|
const defaultSymbol = hBlocks8th[7];
|
|
13
13
|
|
|
14
|
-
/** Draws a single stacked column using plain symbols.
|
|
15
|
-
* @param {object[]} data - Normalized data series.
|
|
16
|
-
* @param {number} width - Total height.
|
|
17
|
-
* @param {number} maxValue - Maximum value for scaling.
|
|
18
|
-
* @param {object} [options] - Options including `reverse`, `rectSize`, `initState`, `theme`.
|
|
19
|
-
* @returns {string[]} The drawn column lines.
|
|
20
|
-
*/
|
|
21
14
|
export const drawColumn = (data, width, maxValue, options = {}) => {
|
|
22
15
|
const {reverse, rectSize = 1, initState = {}, theme = defaultTheme} = options,
|
|
23
16
|
{symbol = ' ', state = null, colorState} = theme?.empty || {},
|
|
@@ -41,12 +34,6 @@ export const drawColumn = (data, width, maxValue, options = {}) => {
|
|
|
41
34
|
return result.map(line => optimize(line));
|
|
42
35
|
};
|
|
43
36
|
|
|
44
|
-
/** Draws a complete plain stacked column chart.
|
|
45
|
-
* @param {any[]} values - Chart data.
|
|
46
|
-
* @param {number} width - Available height.
|
|
47
|
-
* @param {object} [options] - Options.
|
|
48
|
-
* @returns {string[]} Array of strings representing the chart.
|
|
49
|
-
*/
|
|
50
37
|
export const drawChart = drawStackedChart(drawColumn);
|
|
51
38
|
|
|
52
39
|
export default drawChart;
|
|
@@ -3,7 +3,6 @@ import style from '../../style.js';
|
|
|
3
3
|
|
|
4
4
|
const seriesColors = 'cyan,magenta,blue,yellow,green,red'.split(',');
|
|
5
5
|
|
|
6
|
-
/** The default chart theme with standard colors. */
|
|
7
6
|
export const chartTheme = [
|
|
8
7
|
...seriesColors.map(name => ({colorState: style['bright' + capitalize(name)].getState()})),
|
|
9
8
|
...seriesColors.map(name => ({colorState: style[name].getState()}))
|
|
@@ -3,7 +3,6 @@ import style from '../../style.js';
|
|
|
3
3
|
// red, orange, yellow, green, blue, indigo, violet
|
|
4
4
|
const colors = [0xff0000, 0xffa500, 0xffff00, 0x008000, 0x0000ff, 0x4b0082, 0xee82ee];
|
|
5
5
|
|
|
6
|
-
/** Rainbow chart theme with spectrum colors. */
|
|
7
6
|
export const chartTheme = colors.map(color => ({colorState: style.hex(color).getState()}));
|
|
8
7
|
|
|
9
8
|
export default chartTheme;
|
package/src/charts/utils.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import {Commands} from '../ansi/sgr.js';
|
|
2
2
|
import defaultTheme from './themes/default.js';
|
|
3
3
|
|
|
4
|
-
/** Converts a foreground color state to a background color state.
|
|
5
|
-
* @param {object} state - SGR state with a `foreground` property.
|
|
6
|
-
* @returns {object} State with a `background` property.
|
|
7
|
-
*/
|
|
8
4
|
export const makeBgFromFg = state => ({
|
|
9
5
|
background: !state.foreground
|
|
10
6
|
? null
|
|
@@ -13,10 +9,6 @@ export const makeBgFromFg = state => ({
|
|
|
13
9
|
: Number(state.foreground) + 10
|
|
14
10
|
});
|
|
15
11
|
|
|
16
|
-
/** Converts a background color state to a foreground color state.
|
|
17
|
-
* @param {object} state - SGR state with a `background` property.
|
|
18
|
-
* @returns {object} State with a `foreground` property.
|
|
19
|
-
*/
|
|
20
12
|
export const makeFgFromBg = state => ({
|
|
21
13
|
foreground: !state.background
|
|
22
14
|
? null
|
|
@@ -25,17 +17,8 @@ export const makeFgFromBg = state => ({
|
|
|
25
17
|
: Number(state.background) - 10
|
|
26
18
|
});
|
|
27
19
|
|
|
28
|
-
/** Sums the values in a data series.
|
|
29
|
-
* @param {{value?: number}[]} series
|
|
30
|
-
* @returns {number}
|
|
31
|
-
*/
|
|
32
20
|
export const sumValues = series => series.reduce((acc, datum) => acc + (datum?.value || 0), 0);
|
|
33
21
|
|
|
34
|
-
/** Normalizes chart data, merging with default and custom themes.
|
|
35
|
-
* @param {(number|object|Array)[]} data - Raw chart data.
|
|
36
|
-
* @param {object[]} theme - Theme array for series styling.
|
|
37
|
-
* @returns {object[][]} Normalized data.
|
|
38
|
-
*/
|
|
39
22
|
export const normalizeData = (data, theme) =>
|
|
40
23
|
data.map(series => {
|
|
41
24
|
if (!Array.isArray(series)) series = [series];
|
|
@@ -48,12 +31,6 @@ export const normalizeData = (data, theme) =>
|
|
|
48
31
|
});
|
|
49
32
|
});
|
|
50
33
|
|
|
51
|
-
/** Allocates pixel/character sizes to data values proportionally.
|
|
52
|
-
* @param {{value?: number}[]} data - Data series.
|
|
53
|
-
* @param {number} maxValue - Maximum value (-1 for auto).
|
|
54
|
-
* @param {number} size - Total available size.
|
|
55
|
-
* @returns {number[]} Allocated sizes per datum plus one extra for remainder.
|
|
56
|
-
*/
|
|
57
34
|
export const allocateSizes = (data, maxValue, size) => {
|
|
58
35
|
const values = data.map((datum, index) => ({value: datum?.value || 0, index})),
|
|
59
36
|
seriesValue = values.reduce((acc, datum) => acc + datum.value, 0);
|
|
@@ -61,6 +38,8 @@ export const allocateSizes = (data, maxValue, size) => {
|
|
|
61
38
|
if (maxValue < 0) {
|
|
62
39
|
maxValue = seriesValue;
|
|
63
40
|
if (!maxValue) maxValue = 1;
|
|
41
|
+
} else if (!maxValue) {
|
|
42
|
+
maxValue = 1;
|
|
64
43
|
}
|
|
65
44
|
if (seriesValue < maxValue) {
|
|
66
45
|
values.push({value: maxValue - seriesValue, index: -1}); // add an empty bin
|
|
@@ -88,11 +67,6 @@ export const allocateSizes = (data, maxValue, size) => {
|
|
|
88
67
|
return sizes;
|
|
89
68
|
};
|
|
90
69
|
|
|
91
|
-
/** Calculates the integer size needed for a fractional value.
|
|
92
|
-
* @param {number} value - The fractional value.
|
|
93
|
-
* @param {boolean} [drawEmptyBorder] - Whether to draw a border for empty fractional parts.
|
|
94
|
-
* @returns {number} The integer size.
|
|
95
|
-
*/
|
|
96
70
|
export const getFracSize = (value, drawEmptyBorder) => {
|
|
97
71
|
const intValue = Math.floor(value),
|
|
98
72
|
hasFrac = value - intValue > 0,
|
package/src/draw-block-frac.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import {fullBlock, hBlocks8th, vBlocks8th} from './symbols.js';
|
|
2
2
|
import Box from './box.js';
|
|
3
3
|
|
|
4
|
-
/** Draws a block with a fractional width using 1/8th Unicode block characters.
|
|
5
|
-
* The integer part is filled with full blocks; the fractional part appears on the right.
|
|
6
|
-
* @param {number} realWidth - The real width (float). Fractional part is interpreted in 1/8th steps.
|
|
7
|
-
* @param {number} height - The integer height of the block.
|
|
8
|
-
* @param {boolean} [drawEmptyBorder=false] - If true, add an empty border column when the fractional part is close to 0 but not exactly 0.
|
|
9
|
-
* @returns {import('./box.js').Box} A Box containing the drawn block.
|
|
10
|
-
*/
|
|
11
4
|
export const drawRealWidthBlock = (realWidth, height, drawEmptyBorder) => {
|
|
12
5
|
realWidth = Math.max(0, realWidth);
|
|
13
6
|
height = Math.max(0, Math.floor(height));
|
|
@@ -22,13 +15,6 @@ export const drawRealWidthBlock = (realWidth, height, drawEmptyBorder) => {
|
|
|
22
15
|
return new Box(new Array(height).fill(line), true);
|
|
23
16
|
};
|
|
24
17
|
|
|
25
|
-
/** Draws a block with a fractional height using 1/8th Unicode block characters.
|
|
26
|
-
* The integer part is filled with full blocks; the fractional part appears on the top.
|
|
27
|
-
* @param {number} width - The integer width of the block.
|
|
28
|
-
* @param {number} realHeight - The real height (float). Fractional part is interpreted in 1/8th steps.
|
|
29
|
-
* @param {boolean} [drawEmptyBorder=false] - If true, add an empty border row when the fractional part is close to 0 but not exactly 0.
|
|
30
|
-
* @returns {import('./box.js').Box} A Box containing the drawn block.
|
|
31
|
-
*/
|
|
32
18
|
export const drawRealHeightBlock = (width, realHeight, drawEmptyBorder) => {
|
|
33
19
|
width = Math.max(0, Math.floor(width));
|
|
34
20
|
realHeight = Math.max(0, realHeight);
|
package/src/draw-block.js
CHANGED
|
@@ -11,23 +11,6 @@ const T = 1,
|
|
|
11
11
|
LB = getIndex(L, B),
|
|
12
12
|
RB = getIndex(R, B);
|
|
13
13
|
|
|
14
|
-
/** Draws a filled rectangular block using a block theme.
|
|
15
|
-
* The interior is filled with a symbol from the theme's `f` property, or space by default.
|
|
16
|
-
* @param {number} width - Interior width in columns.
|
|
17
|
-
* @param {number} height - Interior height in rows.
|
|
18
|
-
* @param {object} blockTheme - Block theme object defining border characters.
|
|
19
|
-
* @param {object} [options] - Drawing options.
|
|
20
|
-
* @param {number} [options.top] - Sub-theme for the top border (defaults to `hTheme`, then `theme`, then `1`).
|
|
21
|
-
* @param {number} [options.bottom] - Sub-theme for the bottom border (defaults to `hTheme`, then `theme`, then `1`).
|
|
22
|
-
* @param {number} [options.left] - Sub-theme for the left border (defaults to `vTheme`, then `theme`, then `1`).
|
|
23
|
-
* @param {number} [options.right] - Sub-theme for the right border (defaults to `vTheme`, then `theme`, then `1`).
|
|
24
|
-
* @param {number} [options.vTheme] - Sub-theme for vertical borders (left and right). Defaults to `theme`, then `1`.
|
|
25
|
-
* @param {number} [options.hTheme] - Sub-theme for horizontal borders (top and bottom). Defaults to `theme`, then `1`.
|
|
26
|
-
* @param {number} [options.theme] - Sub-theme for all borders (default: `1`).
|
|
27
|
-
* @param {string} [options.symbol] - Fill character for the interior. Defaults to the theme's `f` property or space.
|
|
28
|
-
* @returns {import('./box.js').Box} A Box containing the drawn block.
|
|
29
|
-
* @see {@link https://github.com/uhop/console-toolkit/wiki/Module:-draw-block}
|
|
30
|
-
*/
|
|
31
14
|
export const drawBlock = (
|
|
32
15
|
width,
|
|
33
16
|
height,
|
|
@@ -64,13 +47,6 @@ export const drawBlock = (
|
|
|
64
47
|
);
|
|
65
48
|
};
|
|
66
49
|
|
|
67
|
-
/** Draws a rectangular frame using a block theme. Same as `drawBlock()` but defaults the interior fill to space if `symbol` is not specified.
|
|
68
|
-
* @param {number} width - Interior width in columns.
|
|
69
|
-
* @param {number} height - Interior height in rows.
|
|
70
|
-
* @param {object} blockTheme - Block theme object defining border characters.
|
|
71
|
-
* @param {object} [options] - Drawing options (same as `drawBlock()`).
|
|
72
|
-
* @returns {import('./box.js').Box} A Box containing the drawn frame.
|
|
73
|
-
*/
|
|
74
50
|
export const drawFrame = (width, height, blockTheme, options) => {
|
|
75
51
|
if (!options?.symbol) options = {...options, symbol: ' '};
|
|
76
52
|
return drawBlock(width, height, blockTheme, options);
|
package/src/meta.js
CHANGED
|
@@ -1,90 +1,31 @@
|
|
|
1
|
-
/** Capitalizes the first letter of a string and lowercases the rest.
|
|
2
|
-
* @param {string} name - The string to capitalize.
|
|
3
|
-
* @returns {string} The capitalized string.
|
|
4
|
-
*/
|
|
5
1
|
export const capitalize = name => (name ? name[0].toUpperCase() + name.substring(1).toLowerCase() : name);
|
|
6
2
|
|
|
7
|
-
/** Converts an array of name parts to camelCase.
|
|
8
|
-
* @param {string[]} names - The name parts.
|
|
9
|
-
* @returns {string} The camelCase string.
|
|
10
|
-
*/
|
|
11
3
|
export const toCamelCase = names =>
|
|
12
4
|
names.map((name, index) => (index ? capitalize(name) : name.toLowerCase())).join('');
|
|
13
|
-
/** Splits a camelCase string into an array of name parts.
|
|
14
|
-
* @param {string} name - The camelCase string.
|
|
15
|
-
* @returns {string[]} The name parts.
|
|
16
|
-
*/
|
|
17
5
|
export const fromCamelCase = name => name.split(/(?=[A-Z])/g);
|
|
18
6
|
|
|
19
|
-
/** Converts an array of name parts to PascalCase.
|
|
20
|
-
* @param {string[]} names - The name parts.
|
|
21
|
-
* @returns {string} The PascalCase string.
|
|
22
|
-
*/
|
|
23
7
|
export const toPascalCase = names => names.map(name => capitalize(name)).join('');
|
|
24
|
-
/** Splits a PascalCase string into an array of name parts.
|
|
25
|
-
* @param {string} name - The PascalCase string.
|
|
26
|
-
* @returns {string[]} The name parts.
|
|
27
|
-
*/
|
|
28
8
|
export const fromPascalCase = name => name.split(/(?=[A-Z])/g);
|
|
29
9
|
|
|
30
|
-
/** Converts an array of name parts to ALL_CAPS_SNAKE_CASE.
|
|
31
|
-
* @param {string[]} names - The name parts.
|
|
32
|
-
* @returns {string} The ALL_CAPS_SNAKE_CASE string.
|
|
33
|
-
*/
|
|
34
10
|
export const toAllCapsSnakeCase = names => names.map(name => name.toUpperCase()).join('_');
|
|
35
|
-
/** Converts an array of name parts to snake_case.
|
|
36
|
-
* @param {string[]} names - The name parts.
|
|
37
|
-
* @returns {string} The snake_case string.
|
|
38
|
-
*/
|
|
39
11
|
export const toSnakeCase = names => names.map(name => name.toLowerCase()).join('_');
|
|
40
|
-
/** Splits a snake_case string into an array of name parts.
|
|
41
|
-
* @param {string} name - The snake_case string.
|
|
42
|
-
* @returns {string[]} The name parts.
|
|
43
|
-
*/
|
|
44
12
|
export const fromSnakeCase = name => name.split('_');
|
|
45
13
|
|
|
46
|
-
/** Converts an array of name parts to kebab-case.
|
|
47
|
-
* @param {string[]} names - The name parts.
|
|
48
|
-
* @returns {string} The kebab-case string.
|
|
49
|
-
*/
|
|
50
14
|
export const toKebabCase = names => names.map(name => name.toLowerCase()).join('-');
|
|
51
|
-
/** Splits a kebab-case string into an array of name parts.
|
|
52
|
-
* @param {string} name - The kebab-case string.
|
|
53
|
-
* @returns {string[]} The name parts.
|
|
54
|
-
*/
|
|
55
15
|
export const fromKebabCase = name => name.split('-');
|
|
56
16
|
|
|
57
|
-
/** Adds a getter property to a class prototype or object.
|
|
58
|
-
* @param {Function|object} Class - The class or object to add the getter to.
|
|
59
|
-
* @param {string} name - The property name.
|
|
60
|
-
* @param {Function} getter - The getter function.
|
|
61
|
-
* @param {boolean} [force] - If true, overwrite existing properties.
|
|
62
|
-
* @returns {object} The modified prototype or object.
|
|
63
|
-
*/
|
|
64
17
|
export const addGetter = (Class, name, getter, force) => {
|
|
65
18
|
const object = Class.prototype || Class;
|
|
66
19
|
if (!force && object.hasOwnProperty(name)) return object;
|
|
67
20
|
return Object.defineProperty(object, name, {configurable: true, enumerable: true, get: getter});
|
|
68
21
|
};
|
|
69
22
|
|
|
70
|
-
/** Adds multiple getter properties to a class prototype or object.
|
|
71
|
-
* @param {Function|object} Class - The class or object to add getters to.
|
|
72
|
-
* @param {Record<string, Function>} getters - An object mapping property names to getter functions.
|
|
73
|
-
* @param {boolean} [force] - If true, overwrite existing properties.
|
|
74
|
-
*/
|
|
75
23
|
export const addGetters = (Class, getters, force) => {
|
|
76
24
|
for (const [name, value] of Object.entries(getters)) {
|
|
77
25
|
addGetter(Class, name, value, force);
|
|
78
26
|
}
|
|
79
27
|
};
|
|
80
28
|
|
|
81
|
-
/** Adds an alias property that mirrors an existing property descriptor.
|
|
82
|
-
* @param {Function|object} Class - The class or object to add the alias to.
|
|
83
|
-
* @param {string} name - The new alias name.
|
|
84
|
-
* @param {string} oldName - The existing property name to alias.
|
|
85
|
-
* @param {boolean} [force] - If true, overwrite existing properties.
|
|
86
|
-
* @returns {object} The modified prototype or object.
|
|
87
|
-
*/
|
|
88
29
|
export const addAlias = (Class, name, oldName, force) => {
|
|
89
30
|
const object = Class.prototype || Class;
|
|
90
31
|
if (!force && object.hasOwnProperty(name)) return object;
|
|
@@ -93,11 +34,6 @@ export const addAlias = (Class, name, oldName, force) => {
|
|
|
93
34
|
return Object.defineProperty(object, name, descriptor);
|
|
94
35
|
};
|
|
95
36
|
|
|
96
|
-
/** Adds multiple alias properties to a class prototype or object.
|
|
97
|
-
* @param {Function|object} Class - The class or object to add aliases to.
|
|
98
|
-
* @param {Record<string, string>} aliases - An object mapping new names to existing property names.
|
|
99
|
-
* @param {boolean} [force] - If true, overwrite existing properties.
|
|
100
|
-
*/
|
|
101
37
|
export const addAliases = (Class, aliases, force) => {
|
|
102
38
|
for (const [name, oldName] of Object.entries(aliases)) {
|
|
103
39
|
addAlias(Class, name, oldName, force);
|
package/src/output/show.d.ts
CHANGED
|
@@ -19,18 +19,23 @@ export interface OutOptions {
|
|
|
19
19
|
colorDepth?: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/** Logs a text container
|
|
22
|
+
/** Logs a text container via `console.log()`. Defaults to passing ANSI through (colorDepth = 24).
|
|
23
|
+
* Use this when you know your terminal supports the colors you're emitting.
|
|
24
|
+
* Strips ANSI when colorDepth < 4.
|
|
23
25
|
* @param s - Input: Box, string, string array, or object with `toStrings()`.
|
|
24
26
|
* @param options - Log options.
|
|
25
27
|
*/
|
|
26
28
|
export function log(s: StringsInput, options?: LogOptions): void;
|
|
27
|
-
/** Writes a text container to
|
|
29
|
+
/** Writes a text container to any writable stream (default: stdout).
|
|
30
|
+
* Auto-detects colorDepth from `stream.isTTY` and `stream.getColorDepth()` when omitted.
|
|
31
|
+
* Strips ANSI when colorDepth < 4.
|
|
28
32
|
* @param s - Input: Box, string, string array, or object with `toStrings()`.
|
|
29
33
|
* @param options - Output options.
|
|
30
34
|
*/
|
|
31
35
|
export function out(s: StringsInput, options?: OutOptions): void;
|
|
32
36
|
|
|
33
|
-
/** Wraps a writable stream for outputting styled text.
|
|
37
|
+
/** Wraps a writable stream for outputting styled text. Caches `colorDepth` once at construction
|
|
38
|
+
* to avoid per-call detection — useful for tight write loops to the same stream. */
|
|
34
39
|
export class Out {
|
|
35
40
|
/** The underlying writable stream. */
|
|
36
41
|
stream: Writable;
|
package/src/output/show.js
CHANGED
|
@@ -2,66 +2,35 @@ import process from 'node:process';
|
|
|
2
2
|
import {matchCsiNoGroups} from '../strings.js';
|
|
3
3
|
import Box from '../box.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
|
|
7
|
-
* @param {object} [options] - Options.
|
|
8
|
-
* @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
|
|
9
|
-
* @param {number} [options.colorDepth=24] - Color depth (1 = no color, 4/8/24 = color).
|
|
10
|
-
*/
|
|
11
|
-
export const log = (s, {endOfLineCommand = '\x1B[m', colorDepth = 24} = {}) => {
|
|
5
|
+
const writeRows = (s, endOfLineCommand, colorDepth, write) => {
|
|
12
6
|
s = Box.make(s);
|
|
13
7
|
if (colorDepth < 4) {
|
|
14
8
|
matchCsiNoGroups.lastIndex = 0;
|
|
15
|
-
s.box.forEach(row =>
|
|
9
|
+
s.box.forEach(row => write((row + endOfLineCommand).replace(matchCsiNoGroups, '')));
|
|
16
10
|
return;
|
|
17
11
|
}
|
|
18
|
-
s.box.forEach(row =>
|
|
12
|
+
s.box.forEach(row => write(row + endOfLineCommand));
|
|
19
13
|
};
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @param {import('node:stream').Writable} [options.stream=process.stdout] - The output stream.
|
|
25
|
-
* @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
|
|
26
|
-
* @param {number} [options.colorDepth] - Color depth. Auto-detected from stream if not specified.
|
|
27
|
-
*/
|
|
15
|
+
export const log = (s, {endOfLineCommand = '\x1B[m', colorDepth = 24} = {}) =>
|
|
16
|
+
writeRows(s, endOfLineCommand, colorDepth, row => console.log(row));
|
|
17
|
+
|
|
28
18
|
export const out = (s, {stream = process.stdout, endOfLineCommand = '\x1B[m', colorDepth} = {}) => {
|
|
29
|
-
s = Box.make(s);
|
|
30
19
|
if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
|
|
31
|
-
|
|
32
|
-
matchCsiNoGroups.lastIndex = 0;
|
|
33
|
-
s.box.forEach(row => stream.write((row + endOfLineCommand).replace(matchCsiNoGroups, '') + '\n'));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
s.box.forEach(row => stream.write(row + endOfLineCommand + '\n'));
|
|
20
|
+
writeRows(s, endOfLineCommand, colorDepth, row => stream.write(row + '\n'));
|
|
37
21
|
};
|
|
38
22
|
|
|
39
|
-
/** Convenience wrapper around a writable stream for formatted output.
|
|
40
|
-
* Auto-detects color depth from the stream.
|
|
41
|
-
*/
|
|
42
23
|
export class Out {
|
|
43
|
-
/**
|
|
44
|
-
* @param {import('node:stream').Writable} stream - The writable stream to wrap.
|
|
45
|
-
*/
|
|
46
24
|
constructor(stream) {
|
|
47
25
|
this.stream = stream;
|
|
48
26
|
this.colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
|
|
49
27
|
}
|
|
50
|
-
/** Writes a text container to the stream.
|
|
51
|
-
* @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
|
|
52
|
-
* @param {object} [options] - Options.
|
|
53
|
-
* @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
|
|
54
|
-
* @param {number} [options.colorDepth] - Color depth override.
|
|
55
|
-
*/
|
|
56
28
|
out(s, {endOfLineCommand = '\x1B[m', colorDepth} = {}) {
|
|
57
29
|
if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = this.colorDepth;
|
|
58
30
|
return out(s, {stream: this.stream, endOfLineCommand, colorDepth});
|
|
59
31
|
}
|
|
60
32
|
}
|
|
61
33
|
|
|
62
|
-
/** Logs a string with control characters visualized as hex escape sequences.
|
|
63
|
-
* @param {string} string - The string to debug.
|
|
64
|
-
*/
|
|
65
34
|
export const debug = string =>
|
|
66
35
|
console.log(
|
|
67
36
|
string.replace(/[\x00-\x1F]/g, m => '\\x' + m[0].charCodeAt(0).toString(16).padStart(2, '0').toUpperCase())
|
package/src/output/updater.d.ts
CHANGED
|
@@ -19,14 +19,19 @@ export interface UpdaterOptions {
|
|
|
19
19
|
noLastNewLine?: boolean;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/** An object that can provide frames for the Updater.
|
|
22
|
+
/** An object that can provide frames for the Updater. Prefer implementing `nextFrame()`
|
|
23
|
+
* (advance + return) for spinners and progress bars; `getFrame()` is the read-only fallback. */
|
|
23
24
|
export interface UpdaterTarget {
|
|
24
25
|
/** Current state string. */
|
|
25
26
|
state?: string;
|
|
26
|
-
/** Returns the
|
|
27
|
+
/** Returns the next frame content (advance + return). Preferred entry point for spinners.
|
|
27
28
|
* @returns Frame content (Box, string, or string array).
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
|
+
nextFrame?(...args: unknown[]): StringsInput;
|
|
31
|
+
/** Returns the current frame content. Used as fallback when `nextFrame()` is not implemented.
|
|
32
|
+
* @returns Frame content (Box, string, or string array).
|
|
33
|
+
*/
|
|
34
|
+
getFrame?(...args: unknown[]): StringsInput;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
/** Manages auto-refreshing console output for spinners, progress bars, etc. */
|
package/src/output/updater.js
CHANGED
|
@@ -4,22 +4,7 @@ import {cursorUp, setCommands} from '../ansi/csi.js';
|
|
|
4
4
|
|
|
5
5
|
const RESET = setCommands([]);
|
|
6
6
|
|
|
7
|
-
/** Manages continuously updating console output (spinners, progress bars, etc.).
|
|
8
|
-
* Handles refreshing frames, prologue/epilogue sequences, and interacts with a Writer instance.
|
|
9
|
-
*/
|
|
10
7
|
export class Updater {
|
|
11
|
-
/**
|
|
12
|
-
* @param {((state: string, ...args: unknown[]) => import('../strings.js').StringsInput)|{state?: string, getFrame: (...args: unknown[]) => import('../strings.js').StringsInput}} updater - A function `(state, ...args) => frame` or an object with `getFrame()`.
|
|
13
|
-
* @param {object} [options] - Options.
|
|
14
|
-
* @param {string} [options.prologue] - String written before the first frame.
|
|
15
|
-
* @param {string} [options.epilogue] - String written after the last frame.
|
|
16
|
-
* @param {string} [options.beforeFrame] - String written before each frame.
|
|
17
|
-
* @param {string} [options.afterFrame] - String written after each frame.
|
|
18
|
-
* @param {string} [options.beforeLine] - String prepended to each line.
|
|
19
|
-
* @param {string} [options.afterLine] - String appended to each line.
|
|
20
|
-
* @param {boolean} [options.noLastNewLine] - If true, omit the trailing newline of each frame.
|
|
21
|
-
* @param {import('./writer.js').default} [writer=new Writer()] - The Writer instance to use.
|
|
22
|
-
*/
|
|
23
8
|
constructor(
|
|
24
9
|
updater,
|
|
25
10
|
{prologue, epilogue, beforeFrame, afterFrame, beforeLine, afterLine, noLastNewLine} = {},
|
|
@@ -40,24 +25,16 @@ export class Updater {
|
|
|
40
25
|
this.intervalHandle = null;
|
|
41
26
|
}
|
|
42
27
|
|
|
43
|
-
/** Whether the updater is currently auto-refreshing. */
|
|
44
28
|
get isRefreshing() {
|
|
45
29
|
return this.intervalHandle !== null;
|
|
46
30
|
}
|
|
47
31
|
|
|
48
|
-
/** Starts auto-refreshing at the given interval.
|
|
49
|
-
* @param {number} [ms=100] - Refresh interval in milliseconds.
|
|
50
|
-
* @returns {this}
|
|
51
|
-
*/
|
|
52
32
|
startRefreshing(ms = 100) {
|
|
53
33
|
if (this.intervalHandle || this.isDone || !this.writer.isTTY) return this;
|
|
54
34
|
this.intervalHandle = setInterval(this.update.bind(this), ms);
|
|
55
35
|
return this;
|
|
56
36
|
}
|
|
57
37
|
|
|
58
|
-
/** Stops auto-refreshing.
|
|
59
|
-
* @returns {this}
|
|
60
|
-
*/
|
|
61
38
|
stopRefreshing() {
|
|
62
39
|
if (!this.intervalHandle) return this;
|
|
63
40
|
clearInterval(this.intervalHandle);
|
|
@@ -65,9 +42,6 @@ export class Updater {
|
|
|
65
42
|
return this;
|
|
66
43
|
}
|
|
67
44
|
|
|
68
|
-
/** Resets the updater state, stopping any refresh and clearing the done flag.
|
|
69
|
-
* @returns {this}
|
|
70
|
-
*/
|
|
71
45
|
reset() {
|
|
72
46
|
this.stopRefreshing();
|
|
73
47
|
this.isDone = false;
|
|
@@ -75,25 +49,16 @@ export class Updater {
|
|
|
75
49
|
return this;
|
|
76
50
|
}
|
|
77
51
|
|
|
78
|
-
/** Gets a frame from the updater function or object.
|
|
79
|
-
* @param {string} state - The current state ('active', 'paused', 'finished', etc.).
|
|
80
|
-
* @param {...unknown} args - Additional arguments.
|
|
81
|
-
* @returns {import('../strings.js').StringsInput} The frame content.
|
|
82
|
-
*/
|
|
83
52
|
getFrame(state, ...args) {
|
|
84
53
|
if (typeof this.updater == 'function') return this.updater(state, ...args);
|
|
85
|
-
if (
|
|
54
|
+
if (this.updater) {
|
|
86
55
|
this.updater.state = state;
|
|
87
|
-
return this.updater.
|
|
56
|
+
if (typeof this.updater.nextFrame == 'function') return this.updater.nextFrame(...args);
|
|
57
|
+
if (typeof this.updater.getFrame == 'function') return this.updater.getFrame(...args);
|
|
88
58
|
}
|
|
89
|
-
throw new TypeError('Updater must be a function or implement getFrame()');
|
|
59
|
+
throw new TypeError('Updater must be a function or implement nextFrame()/getFrame()');
|
|
90
60
|
}
|
|
91
61
|
|
|
92
|
-
/** Writes a single frame to the output, handling cursor repositioning.
|
|
93
|
-
* @param {string} state - The current state.
|
|
94
|
-
* @param {...unknown} args - Additional arguments passed to `getFrame()`.
|
|
95
|
-
* @returns {Promise<void>}
|
|
96
|
-
*/
|
|
97
62
|
async writeFrame(state, ...args) {
|
|
98
63
|
if (this.first) {
|
|
99
64
|
this.prologue && (await this.writer.writeString(this.prologue));
|
|
@@ -116,9 +81,6 @@ export class Updater {
|
|
|
116
81
|
this.afterFrame && (await this.writer.writeString(this.afterFrame));
|
|
117
82
|
}
|
|
118
83
|
|
|
119
|
-
/** Marks the updater as done, stops refreshing, and writes the epilogue.
|
|
120
|
-
* @returns {Promise<void>}
|
|
121
|
-
*/
|
|
122
84
|
async done() {
|
|
123
85
|
if (this.isDone) return;
|
|
124
86
|
this.isDone = true;
|
|
@@ -126,20 +88,11 @@ export class Updater {
|
|
|
126
88
|
this.epilogue && (await this.writer.writeString(this.epilogue));
|
|
127
89
|
}
|
|
128
90
|
|
|
129
|
-
/** Updates the display with a new frame.
|
|
130
|
-
* @param {string} [state='active'] - The current state.
|
|
131
|
-
* @param {...unknown} args - Additional arguments passed to `getFrame()`.
|
|
132
|
-
* @returns {Promise<void>}
|
|
133
|
-
*/
|
|
134
91
|
async update(state = 'active', ...args) {
|
|
135
92
|
if (this.isDone || !this.writer.isTTY) return;
|
|
136
93
|
await this.writeFrame(state, ...args);
|
|
137
94
|
}
|
|
138
95
|
|
|
139
|
-
/** Writes the final frame with state 'finished' and calls `done()`.
|
|
140
|
-
* @param {...unknown} args - Additional arguments passed to `getFrame()`.
|
|
141
|
-
* @returns {Promise<void>}
|
|
142
|
-
*/
|
|
143
96
|
async final(...args) {
|
|
144
97
|
if (this.isDone) return;
|
|
145
98
|
await this.writeFrame('finished', ...args);
|
package/src/output/writer.js
CHANGED
|
@@ -6,59 +6,35 @@ import {getLength, matchCsiNoGroups, matchCsiNoSgrNoGroups, toStrings} from '../
|
|
|
6
6
|
const write = async (stream, chunk, encoding = 'utf8') =>
|
|
7
7
|
new Promise((resolve, reject) => stream.write(chunk, encoding, error => (error ? reject(error) : resolve())));
|
|
8
8
|
|
|
9
|
-
/** Abstracts writing to a stream (defaulting to `process.stdout`).
|
|
10
|
-
* Handles TTY capabilities, color depth, cursor manipulation, and ANSI stripping for non-TTY streams.
|
|
11
|
-
*/
|
|
12
9
|
export class Writer {
|
|
13
|
-
/**
|
|
14
|
-
* @param {import('node:stream').Writable} [stream=process.stdout] - The output stream.
|
|
15
|
-
* @param {number} [forceColorDepth] - Force a specific color depth instead of auto-detecting.
|
|
16
|
-
*/
|
|
17
10
|
constructor(stream = process.stdout, forceColorDepth) {
|
|
18
11
|
this.stream = stream;
|
|
19
12
|
this.forceColorDepth = forceColorDepth;
|
|
20
13
|
}
|
|
21
14
|
|
|
22
|
-
/** Whether the stream is a TTY. */
|
|
23
15
|
get isTTY() {
|
|
24
16
|
return this.stream.isTTY;
|
|
25
17
|
}
|
|
26
|
-
/** The number of columns in the terminal. */
|
|
27
18
|
get columns() {
|
|
28
19
|
return this.stream.columns;
|
|
29
20
|
}
|
|
30
|
-
/** The number of rows in the terminal. */
|
|
31
21
|
get rows() {
|
|
32
22
|
return this.stream.rows;
|
|
33
23
|
}
|
|
34
|
-
/** The terminal size as `{columns, rows}`. */
|
|
35
24
|
get size() {
|
|
36
25
|
const [columns, rows] = this.stream.getWindowSize?.() || [];
|
|
37
26
|
return {columns, rows};
|
|
38
27
|
}
|
|
39
|
-
/** Returns the color depth of the stream.
|
|
40
|
-
* @param {object} [env] - Environment variables to check (default: `process.env`).
|
|
41
|
-
* @returns {number} The color depth (1, 4, 8, or 24).
|
|
42
|
-
*/
|
|
43
28
|
getColorDepth(...args) {
|
|
44
29
|
return this.forceColorDepth || this.stream.getColorDepth?.(...args);
|
|
45
30
|
}
|
|
46
31
|
|
|
47
|
-
/** Checks if the stream supports the given number of colors.
|
|
48
|
-
* @param {number} [count] - Number of colors to check for.
|
|
49
|
-
* @param {object} [env] - Environment variables to check.
|
|
50
|
-
* @returns {boolean} True if supported.
|
|
51
|
-
*/
|
|
52
32
|
hasColors(...args) {
|
|
53
33
|
if (!this.forceColorDepth) return this.stream.hasColors?.(...args);
|
|
54
34
|
const count = typeof args[0] == 'number' ? args[0] : 2;
|
|
55
35
|
return count <= Math.pow(2, this.forceColorDepth);
|
|
56
36
|
}
|
|
57
37
|
|
|
58
|
-
/** Clears the current line.
|
|
59
|
-
* @param {number} dir - Direction: -1 = left, 0 = entire line, 1 = right.
|
|
60
|
-
* @returns {Promise<boolean>} True if the operation was supported.
|
|
61
|
-
*/
|
|
62
38
|
clearLine(dir) {
|
|
63
39
|
return new Promise((resolve, reject) => {
|
|
64
40
|
if (typeof this.stream.clearLine == 'function') {
|
|
@@ -68,9 +44,6 @@ export class Writer {
|
|
|
68
44
|
}
|
|
69
45
|
});
|
|
70
46
|
}
|
|
71
|
-
/** Clears the screen from the cursor down.
|
|
72
|
-
* @returns {Promise<boolean>} True if the operation was supported.
|
|
73
|
-
*/
|
|
74
47
|
clearScreenDown() {
|
|
75
48
|
return new Promise((resolve, reject) => {
|
|
76
49
|
if (typeof this.stream.clearScreenDown == 'function') {
|
|
@@ -81,11 +54,6 @@ export class Writer {
|
|
|
81
54
|
});
|
|
82
55
|
}
|
|
83
56
|
|
|
84
|
-
/** Moves the cursor to an absolute position.
|
|
85
|
-
* @param {number} x - Column.
|
|
86
|
-
* @param {number} [y] - Row.
|
|
87
|
-
* @returns {Promise<boolean>} True if the operation was supported.
|
|
88
|
-
*/
|
|
89
57
|
cursorTo(x, y) {
|
|
90
58
|
return new Promise((resolve, reject) => {
|
|
91
59
|
if (typeof this.stream.cursorTo == 'function') {
|
|
@@ -95,11 +63,6 @@ export class Writer {
|
|
|
95
63
|
}
|
|
96
64
|
});
|
|
97
65
|
}
|
|
98
|
-
/** Moves the cursor relative to its current position.
|
|
99
|
-
* @param {number} dx - Columns to move.
|
|
100
|
-
* @param {number} dy - Rows to move.
|
|
101
|
-
* @returns {Promise<boolean>} True if the operation was supported.
|
|
102
|
-
*/
|
|
103
66
|
moveCursor(dx, dy) {
|
|
104
67
|
return new Promise((resolve, reject) => {
|
|
105
68
|
if (typeof this.stream.moveCursor == 'function') {
|
|
@@ -110,10 +73,6 @@ export class Writer {
|
|
|
110
73
|
});
|
|
111
74
|
}
|
|
112
75
|
|
|
113
|
-
/** Writes a raw string to the stream, stripping ANSI codes for non-TTY streams.
|
|
114
|
-
* @param {string} s - The string to write.
|
|
115
|
-
* @returns {Promise<void>}
|
|
116
|
-
*/
|
|
117
76
|
async writeString(s) {
|
|
118
77
|
s = String(s);
|
|
119
78
|
|
|
@@ -134,15 +93,6 @@ export class Writer {
|
|
|
134
93
|
await write(this.stream, s.replace(matchCsiNoGroups, ''));
|
|
135
94
|
}
|
|
136
95
|
|
|
137
|
-
/** Writes a text container to the stream.
|
|
138
|
-
* @param {import('../strings.js').StringsInput} s - Input convertible to strings.
|
|
139
|
-
* @param {object} [options] - Options.
|
|
140
|
-
* @param {boolean|'save'} [options.sameColumn] - If true or 'save', keep cursor in the same column between lines.
|
|
141
|
-
* @param {boolean} [options.noLastNewLine] - If true, omit the trailing newline.
|
|
142
|
-
* @param {string} [options.beforeLine=''] - String prepended to each line.
|
|
143
|
-
* @param {string} [options.afterLine=''] - String appended to each line.
|
|
144
|
-
* @returns {Promise<void>}
|
|
145
|
-
*/
|
|
146
96
|
async write(s, {sameColumn, noLastNewLine, beforeLine = '', afterLine = ''} = {}) {
|
|
147
97
|
s = toStrings(s);
|
|
148
98
|
|
|
@@ -175,9 +125,7 @@ export class Writer {
|
|
|
175
125
|
return;
|
|
176
126
|
}
|
|
177
127
|
|
|
178
|
-
let lines =
|
|
179
|
-
.map(line => beforeLine + line + afterLine)
|
|
180
|
-
.join('\n');
|
|
128
|
+
let lines = s.map(line => beforeLine + line + afterLine).join('\n');
|
|
181
129
|
if (!noLastNewLine) lines += '\n';
|
|
182
130
|
await write(this.stream, lines);
|
|
183
131
|
}
|