@lewishowles/cli-style 0.16.0 → 0.17.1
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/CHANGELOG.md +25 -1
- package/README.md +22 -1
- package/adapters/python/README.md +14 -0
- package/adapters/python/cli_style/__init__.py +2 -0
- package/adapters/python/cli_style/_wrappers.py +55 -1
- package/adapters/python/pyproject.toml +1 -1
- package/adapters/swift/Sources/CliStyle/Wrappers.swift +47 -0
- package/package.json +4 -1
- package/scripts/generate-adapters.js +25 -4
- package/src/catalogue/renderer-catalogue.js +44 -4
- package/src/cli/render-command.js +2 -0
- package/src/formatters/ansi.js +187 -2
- package/src/formatters/wrap.js +24 -18
- package/src/gallery/render-gallery.js +97 -13
- package/src/index.js +1 -0
- package/src/patterns/quote-block.js +70 -0
- package/src/primitives/bar-chart.js +3 -3
- package/src/primitives/chip.js +10 -2
- package/src/primitives/divider.js +2 -2
- package/src/primitives/feedback.js +8 -2
- package/src/primitives/labelled-line.js +20 -10
- package/src/primitives/panel.js +20 -5
- package/src/primitives/row-group.js +8 -6
- package/src/primitives/row.js +5 -5
- package/src/primitives/span.js +12 -3
- package/src/primitives/status.js +8 -2
- package/src/primitives/table.js +30 -16
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foreground } from "../formatters/ansi.js";
|
|
1
|
+
import { foreground, visibleWidth } from "../formatters/ansi.js";
|
|
2
2
|
|
|
3
3
|
// Dividers use enough width to read as a section break in compact output.
|
|
4
4
|
const defaultWidth = 40;
|
|
@@ -33,7 +33,7 @@ export function divider(options = {}) {
|
|
|
33
33
|
const character = options.character ?? defaultCharacter;
|
|
34
34
|
const label = options.label ?? "";
|
|
35
35
|
const width = options.dividerWidth ?? defaultWidth;
|
|
36
|
-
const lineWidth = Math.max(width - label
|
|
36
|
+
const lineWidth = Math.max(width - visibleWidth(label) - labelGap(label), 0);
|
|
37
37
|
const line = character.repeat(lineWidth);
|
|
38
38
|
const output = label === "" ? line : `${label} ${line}`;
|
|
39
39
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foreground, style } from "../formatters/ansi.js";
|
|
1
|
+
import { foreground, restoreStyles, style } from "../formatters/ansi.js";
|
|
2
2
|
import { getResultSymbol, resultTypes } from "../theme/results.js";
|
|
3
3
|
import { labelledLine } from "./labelled-line.js";
|
|
4
4
|
import { panel } from "./panel.js";
|
|
@@ -48,7 +48,13 @@ export function emptyState(title = "No results", detail = "", options = {}) {
|
|
|
48
48
|
return `${prefix}${detailText}`;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// The prefix with its bold and muted tone switched back on after a coloured title.
|
|
52
|
+
const restoredPrefix = restoreStyles(prefix, [
|
|
53
|
+
style("", "bold", options),
|
|
54
|
+
foreground("", "muted", options),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const renderedPrefix = style(foreground(restoredPrefix, "muted", options), "bold", options);
|
|
52
58
|
|
|
53
59
|
return `${renderedPrefix}${detailText}`;
|
|
54
60
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foreground, style } from "../formatters/ansi.js";
|
|
1
|
+
import { foreground, restoreStyles, style, visibleWidth } from "../formatters/ansi.js";
|
|
2
2
|
import { wrapText } from "../formatters/wrap.js";
|
|
3
3
|
|
|
4
4
|
// Falls back to the informational tone when no colour token is given.
|
|
@@ -44,20 +44,19 @@ export function labelledLine(label = "", message = "", options = {}) {
|
|
|
44
44
|
return `${prefix}${messageSeparator}${messageText}`;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const wrapWidth = resolveWrapWidth(
|
|
47
|
+
const wrapWidth = resolveWrapWidth(prefix, options);
|
|
48
48
|
const messageLines = wrapText(messageText, wrapWidth);
|
|
49
|
-
const continuationIndent = " ".repeat(
|
|
49
|
+
const continuationIndent = " ".repeat(visibleWidth(prefix) + visibleWidth(messageSeparator));
|
|
50
50
|
const wrappedMessage = messageLines.join(`\n${continuationIndent}`);
|
|
51
51
|
|
|
52
52
|
return `${prefix}${messageSeparator}${wrappedMessage}`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Colour the icon
|
|
57
|
-
* continuation indent lines up whether or not colour is applied.
|
|
56
|
+
* Colour the icon and label prefix in the line's tone when colour is on.
|
|
58
57
|
*
|
|
59
58
|
* @param {string} prefix
|
|
60
|
-
*
|
|
59
|
+
* Icon and label prefix. The icon or label may already hold colour.
|
|
61
60
|
* @param {object} options
|
|
62
61
|
* Rendering options.
|
|
63
62
|
* @returns {string}
|
|
@@ -72,13 +71,24 @@ function renderPrefix(prefix, options) {
|
|
|
72
71
|
return prefix;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
|
|
74
|
+
// The caller's tone, or "info" when none is given.
|
|
75
|
+
const tone = resolveColour(options);
|
|
76
|
+
|
|
77
|
+
// The prefix with bold and the line's colour switched back on after a coloured icon or
|
|
78
|
+
// label, so the rest of the prefix keeps them.
|
|
79
|
+
const restoredPrefix = restoreStyles(prefix, [
|
|
80
|
+
style("", "bold", options),
|
|
81
|
+
foreground("", tone, options),
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// The whole prefix in the line's colour, ready to be made bold.
|
|
85
|
+
const colouredPrefix = foreground(restoredPrefix, tone, options);
|
|
76
86
|
|
|
77
87
|
return style(colouredPrefix, "bold", options);
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
/**
|
|
81
|
-
* Build the
|
|
91
|
+
* Build the icon and label prefix. The icon or label may already hold colour.
|
|
82
92
|
*
|
|
83
93
|
* @param {string} label
|
|
84
94
|
* Label text.
|
|
@@ -115,7 +125,7 @@ function resolveColour(options) {
|
|
|
115
125
|
* Work out how wide the message can be before it wraps.
|
|
116
126
|
*
|
|
117
127
|
* @param {string} prefix
|
|
118
|
-
*
|
|
128
|
+
* The icon and label prefix as it will be printed, including any colour.
|
|
119
129
|
* @param {object} options
|
|
120
130
|
* Rendering options.
|
|
121
131
|
* @returns {number|undefined}
|
|
@@ -127,7 +137,7 @@ function resolveWrapWidth(prefix, options) {
|
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
if (Number.isFinite(options.width)) {
|
|
130
|
-
return Math.max(1, options.width - prefix
|
|
140
|
+
return Math.max(1, options.width - visibleWidth(prefix) - visibleWidth(messageSeparator));
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
return undefined;
|
package/src/primitives/panel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { background, foreground, style } from "../formatters/ansi.js";
|
|
1
|
+
import { background, foreground, restoreStyles, style, visibleWidth } from "../formatters/ansi.js";
|
|
2
2
|
import { wrapText } from "../formatters/wrap.js";
|
|
3
3
|
import { profiles } from "../profiles/profiles.js";
|
|
4
4
|
import { panelColours } from "../theme/colours.js";
|
|
@@ -64,23 +64,38 @@ export function panel(options = {}) {
|
|
|
64
64
|
* @param {object} options
|
|
65
65
|
* Rendering options.
|
|
66
66
|
* @returns {string[]}
|
|
67
|
-
* The panel lines, each exactly `width` columns wide
|
|
67
|
+
* The panel lines, each exactly `width` columns wide, unless a wide character
|
|
68
|
+
* does not fit in a panel at its minimum width.
|
|
68
69
|
*/
|
|
69
70
|
function renderLine(line, width, isTitle, options) {
|
|
70
71
|
const accent = options.unicode === false ? "|" : "▌";
|
|
71
72
|
const contentWidth = width - 5;
|
|
73
|
+
// Whether this line is drawn with ANSI colour.
|
|
74
|
+
const useColour = options.colour === true;
|
|
72
75
|
|
|
73
76
|
return wrapText(line, contentWidth).map((visibleLine) => {
|
|
74
|
-
|
|
77
|
+
// Spaces that fill the line to the content width on screen. A wide character can
|
|
78
|
+
// overflow a very narrow panel, so the count never drops below zero.
|
|
79
|
+
const padding = " ".repeat(Math.max(0, contentWidth - visibleWidth(visibleLine)));
|
|
80
|
+
// The line with its inner margins, ready for the panel colours.
|
|
81
|
+
const content = ` ${visibleLine}${padding} `;
|
|
75
82
|
|
|
76
|
-
if (
|
|
83
|
+
if (!useColour) {
|
|
77
84
|
return `${accent}${content}`;
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
const renderedAccent = renderPanelColour(accent, options);
|
|
81
88
|
|
|
89
|
+
// The content with the panel colours switched back on after each reset inside it, so
|
|
90
|
+
// the panel background carries on to the right edge.
|
|
91
|
+
const restoredContent = restoreStyles(content, [
|
|
92
|
+
background("", panelColours.background, options),
|
|
93
|
+
foreground("", panelColours.body, options),
|
|
94
|
+
isTitle ? style("", "bold", options) : "",
|
|
95
|
+
]);
|
|
96
|
+
|
|
82
97
|
const renderedContent = foreground(
|
|
83
|
-
background(
|
|
98
|
+
background(restoredContent, panelColours.background, options),
|
|
84
99
|
panelColours.body,
|
|
85
100
|
options,
|
|
86
101
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { visibleWidth } from "../formatters/ansi.js";
|
|
1
2
|
import { getResultSymbol } from "../theme/results.js";
|
|
2
3
|
import { row } from "./row.js";
|
|
3
4
|
|
|
@@ -21,7 +22,7 @@ export function rowGroup(options = {}) {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const labelWidth = Math.max(
|
|
24
|
-
...rows.map((entry) =>
|
|
25
|
+
...rows.map((entry) => displayedLabelWidth(entry, options)),
|
|
25
26
|
options.labelWidth ?? 0,
|
|
26
27
|
);
|
|
27
28
|
|
|
@@ -59,18 +60,19 @@ function normaliseRows(rows) {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
|
-
*
|
|
63
|
+
* Count the columns a row's label fills, including its result symbol when it
|
|
64
|
+
* has one.
|
|
63
65
|
*
|
|
64
66
|
* @param {object} entry
|
|
65
67
|
* Row entry.
|
|
66
68
|
* @param {object} options
|
|
67
69
|
* Group rendering options.
|
|
68
70
|
* @returns {number}
|
|
69
|
-
*
|
|
71
|
+
* The number of terminal columns the label takes up.
|
|
70
72
|
*/
|
|
71
|
-
function
|
|
73
|
+
function displayedLabelWidth(entry, options) {
|
|
72
74
|
if (typeof entry.result !== "string" || entry.result === "") {
|
|
73
|
-
return entry.label
|
|
75
|
+
return visibleWidth(entry.label);
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
const entryOptions = {
|
|
@@ -80,5 +82,5 @@ function displayedLabelLength(entry, options) {
|
|
|
80
82
|
|
|
81
83
|
const symbol = getResultSymbol(entry.result, entryOptions);
|
|
82
84
|
|
|
83
|
-
return [symbol, entry.label].join(" ")
|
|
85
|
+
return visibleWidth([symbol, entry.label].join(" "));
|
|
84
86
|
}
|
package/src/primitives/row.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foreground } from "../formatters/ansi.js";
|
|
1
|
+
import { foreground, padVisible, visibleWidth } from "../formatters/ansi.js";
|
|
2
2
|
import { wrapText } from "../formatters/wrap.js";
|
|
3
3
|
import { getResultSymbol, getResultToken } from "../theme/results.js";
|
|
4
4
|
|
|
@@ -49,8 +49,8 @@ const minimumWrapWidth = 10;
|
|
|
49
49
|
export function row(label, value = "", options = {}) {
|
|
50
50
|
const resultToken = resolveResultToken(options);
|
|
51
51
|
const displayLabel = renderDisplayLabel(label, resultToken, options);
|
|
52
|
-
const labelWidth = Math.max(displayLabel
|
|
53
|
-
const labelText = displayLabel
|
|
52
|
+
const labelWidth = Math.max(visibleWidth(displayLabel), options.labelWidth ?? 0);
|
|
53
|
+
const labelText = padVisible(displayLabel, labelWidth);
|
|
54
54
|
const renderedLabel = renderLabel(labelText, options);
|
|
55
55
|
|
|
56
56
|
if (value === "") {
|
|
@@ -66,7 +66,7 @@ export function row(label, value = "", options = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const availableWrapWidth = Number.isFinite(options.width)
|
|
69
|
-
? options.width - labelWidth - separator
|
|
69
|
+
? options.width - labelWidth - visibleWidth(separator)
|
|
70
70
|
: defaultWrapWidth;
|
|
71
71
|
|
|
72
72
|
const hasExplicitWrapWidth = options.wrapWidth !== undefined && options.wrapWidth !== null;
|
|
@@ -78,7 +78,7 @@ export function row(label, value = "", options = {}) {
|
|
|
78
78
|
const wrapWidth = options.wrapWidth ?? availableWrapWidth;
|
|
79
79
|
|
|
80
80
|
const valueLines = wrapText(value, wrapWidth);
|
|
81
|
-
const continuationIndent = " ".repeat(labelWidth + separator
|
|
81
|
+
const continuationIndent = " ".repeat(labelWidth + visibleWidth(separator));
|
|
82
82
|
const renderedValueLines = valueLines.map((line) => renderValue(line, options));
|
|
83
83
|
const wrappedValue = renderedValueLines.join(`\n${continuationIndent}`);
|
|
84
84
|
|
package/src/primitives/span.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foreground, style } from "../formatters/ansi.js";
|
|
1
|
+
import { foreground, restoreStyles, style } from "../formatters/ansi.js";
|
|
2
2
|
|
|
3
3
|
// Inline spans use the same informational tone as hints by default.
|
|
4
4
|
const defaultTone = "info";
|
|
@@ -27,7 +27,16 @@ export function span(value = "", tone = defaultTone, options = {}) {
|
|
|
27
27
|
return value;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// The text weight, bold unless the options ask for another.
|
|
31
|
+
const weight = options.weight ?? defaultWeight;
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
// The text with the span's weight and tone switched back on after any coloured text.
|
|
34
|
+
const restoredValue = restoreStyles(value, [
|
|
35
|
+
style("", weight, options),
|
|
36
|
+
foreground("", tone, options),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const renderedValue = foreground(restoredValue, tone, options);
|
|
40
|
+
|
|
41
|
+
return style(renderedValue, weight, options);
|
|
33
42
|
}
|
package/src/primitives/status.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foreground, style } from "../formatters/ansi.js";
|
|
1
|
+
import { foreground, restoreStyles, style } from "../formatters/ansi.js";
|
|
2
2
|
import { getResultSymbol, getResultToken, resultTypes } from "../theme/results.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -33,7 +33,13 @@ export function status(resultType = resultTypes.UNKNOWN, detail = "", options =
|
|
|
33
33
|
return `${prefix}${detailText}`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// The prefix with its bold and tone switched back on after a coloured label.
|
|
37
|
+
const restoredPrefix = restoreStyles(prefix, [
|
|
38
|
+
style("", "bold", options),
|
|
39
|
+
foreground("", token.tone, options),
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const colouredPrefix = foreground(restoredPrefix, token.tone, options);
|
|
37
43
|
const boldPrefix = style(colouredPrefix, "bold", options);
|
|
38
44
|
|
|
39
45
|
return `${boldPrefix}${detailText}`;
|
package/src/primitives/table.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foreground } from "../formatters/ansi.js";
|
|
1
|
+
import { foreground, padVisible, sliceVisible, visibleWidth } from "../formatters/ansi.js";
|
|
2
2
|
import { defaultWidth, minimumWidth, normaliseWidth } from "../formatters/width.js";
|
|
3
3
|
import { tableColours } from "../theme/colours.js";
|
|
4
4
|
|
|
@@ -112,8 +112,8 @@ function normaliseRows(rows) {
|
|
|
112
112
|
function columnWidths(columns, rows) {
|
|
113
113
|
return columns.map((column) => {
|
|
114
114
|
const naturalWidth = Math.max(
|
|
115
|
-
column.label
|
|
116
|
-
...rows.map((row) => cellValue(row[column.key])
|
|
115
|
+
visibleWidth(column.label),
|
|
116
|
+
...rows.map((row) => visibleWidth(cellValue(row[column.key]))),
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
const maxWidth = columnMaxWidth(column);
|
|
@@ -151,24 +151,38 @@ function columnMaxWidth(column) {
|
|
|
151
151
|
* @param {number} width
|
|
152
152
|
* Column width.
|
|
153
153
|
* @returns {string[]}
|
|
154
|
-
* Lines the cell renders as
|
|
155
|
-
*
|
|
154
|
+
* Lines the cell renders as, each no wider on screen than the column
|
|
155
|
+
* unless one character is wider than the whole column. A value that does
|
|
156
|
+
* not fit is cut to one line with a trailing ellipsis, or split across
|
|
157
|
+
* lines when the column's overflow is 'wrap'. Coloured text keeps its
|
|
158
|
+
* colour on every line.
|
|
156
159
|
*/
|
|
157
160
|
function columnLines(value, column, width) {
|
|
158
|
-
if (value
|
|
161
|
+
if (visibleWidth(value) <= width) {
|
|
159
162
|
return [value];
|
|
160
163
|
}
|
|
161
164
|
|
|
162
165
|
if (column.overflow === "wrap") {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
const lines = [];
|
|
167
|
+
|
|
168
|
+
let remaining = value;
|
|
169
|
+
|
|
170
|
+
while (remaining !== "") {
|
|
171
|
+
let { head, tail } = sliceVisible(remaining, width);
|
|
172
|
+
|
|
173
|
+
if (visibleWidth(head) === 0 && tail !== "") {
|
|
174
|
+
// In a one-column cell, a two-column character gets a line of its own so wrapping always moves forward.
|
|
175
|
+
({ head, tail } = sliceVisible(remaining, 2));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
lines.push(head);
|
|
179
|
+
remaining = tail;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return lines;
|
|
169
183
|
}
|
|
170
184
|
|
|
171
|
-
return [`${value
|
|
185
|
+
return [`${sliceVisible(value, width - 1).head}…`];
|
|
172
186
|
}
|
|
173
187
|
|
|
174
188
|
/**
|
|
@@ -242,7 +256,7 @@ function renderTable(columns, rows, widths, options) {
|
|
|
242
256
|
* Padded column text.
|
|
243
257
|
*/
|
|
244
258
|
function padColumn(value, width, index, columnCount) {
|
|
245
|
-
return index === columnCount - 1 ? value : value
|
|
259
|
+
return index === columnCount - 1 ? value : padVisible(value, width);
|
|
246
260
|
}
|
|
247
261
|
|
|
248
262
|
/**
|
|
@@ -258,13 +272,13 @@ function padColumn(value, width, index, columnCount) {
|
|
|
258
272
|
* Rendered key/value blocks.
|
|
259
273
|
*/
|
|
260
274
|
function renderBlocks(columns, rows, options) {
|
|
261
|
-
const labelWidth = Math.max(...columns.map((column) => column.label
|
|
275
|
+
const labelWidth = Math.max(...columns.map((column) => visibleWidth(column.label)));
|
|
262
276
|
|
|
263
277
|
return rows
|
|
264
278
|
.map((row) =>
|
|
265
279
|
columns
|
|
266
280
|
.map((column) => {
|
|
267
|
-
const label = renderHeader(column.label
|
|
281
|
+
const label = renderHeader(padVisible(column.label, labelWidth), options);
|
|
268
282
|
const value = renderCell(cellValue(row[column.key]), 0, options);
|
|
269
283
|
|
|
270
284
|
return `${label}${columnSeparator}${value}`;
|