@lewishowles/cli-style 0.12.1 → 0.13.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/CHANGELOG.md +7 -0
- package/adapters/python/cli_style.py +44 -0
- package/package.json +1 -1
- package/src/gallery/render-gallery.js +40 -0
- package/src/primitives/table.js +103 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.13.0 - 2026-09-05
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added Python `table()` and `row_group()` wrapper functions to the Python adapter, matching the existing shell/JS renderers.
|
|
10
|
+
- `table()` columns can now set `maxWidth` and `overflow` (`'truncate'`, the default, or `'wrap'`) to cap a column's width and control how long values render within it.
|
|
11
|
+
|
|
5
12
|
## 0.12.1 - 2026-08-20
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -159,6 +159,50 @@ def row(
|
|
|
159
159
|
return render("row", data, **kwargs)
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
# Render an aligned table from Python values.
|
|
163
|
+
#
|
|
164
|
+
# @param {list[dict[str, Any]]} columns
|
|
165
|
+
# Table column definitions, including per-column overflow settings.
|
|
166
|
+
# @param {list[dict[str, Any]]} rows
|
|
167
|
+
# Table row values.
|
|
168
|
+
# @param {bool|None} unicode
|
|
169
|
+
# Optional Unicode border preference.
|
|
170
|
+
# @param {int|None} width
|
|
171
|
+
# Optional table width.
|
|
172
|
+
def table(
|
|
173
|
+
columns: list[dict[str, Any]],
|
|
174
|
+
rows: list[dict[str, Any]],
|
|
175
|
+
*,
|
|
176
|
+
unicode: bool | None = None,
|
|
177
|
+
width: int | None = None,
|
|
178
|
+
**kwargs: Any,
|
|
179
|
+
) -> str:
|
|
180
|
+
return render("table", {
|
|
181
|
+
"columns": columns,
|
|
182
|
+
"rows": rows,
|
|
183
|
+
"unicode": unicode,
|
|
184
|
+
"width": width,
|
|
185
|
+
}, **kwargs)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Render a group of labelled value rows from Python values.
|
|
189
|
+
#
|
|
190
|
+
# @param {list[dict[str, Any]]} rows
|
|
191
|
+
# Labelled values to render together.
|
|
192
|
+
# @param {int|None} label_width
|
|
193
|
+
# Optional minimum label width.
|
|
194
|
+
def row_group(
|
|
195
|
+
rows: list[dict[str, Any]],
|
|
196
|
+
*,
|
|
197
|
+
label_width: int | None = None,
|
|
198
|
+
**kwargs: Any,
|
|
199
|
+
) -> str:
|
|
200
|
+
return render("row-group", {
|
|
201
|
+
"rows": rows,
|
|
202
|
+
"labelWidth": label_width,
|
|
203
|
+
}, **kwargs)
|
|
204
|
+
|
|
205
|
+
|
|
162
206
|
# Render an inline span from Python values.
|
|
163
207
|
#
|
|
164
208
|
# @param {str} value
|
package/package.json
CHANGED
|
@@ -764,6 +764,12 @@ function renderPrimitives(options) {
|
|
|
764
764
|
"Tables",
|
|
765
765
|
renderTableExample(options),
|
|
766
766
|
"",
|
|
767
|
+
"Truncated tables",
|
|
768
|
+
renderOverflowTableExample(options, "truncate"),
|
|
769
|
+
"",
|
|
770
|
+
"Wrapped tables",
|
|
771
|
+
renderOverflowTableExample(options, "wrap"),
|
|
772
|
+
"",
|
|
767
773
|
"Narrow tables",
|
|
768
774
|
renderTableExample({
|
|
769
775
|
...options,
|
|
@@ -900,6 +906,40 @@ function renderTableExample(options) {
|
|
|
900
906
|
});
|
|
901
907
|
}
|
|
902
908
|
|
|
909
|
+
/**
|
|
910
|
+
* Render a table with a long-detail column capped by maxWidth.
|
|
911
|
+
*
|
|
912
|
+
* @param {object} options
|
|
913
|
+
* Rendering options.
|
|
914
|
+
* @param {string} overflow
|
|
915
|
+
* Overflow mode ('truncate' or 'wrap') for the capped column.
|
|
916
|
+
* @returns {string}
|
|
917
|
+
* Table example.
|
|
918
|
+
*/
|
|
919
|
+
function renderOverflowTableExample(options, overflow) {
|
|
920
|
+
return table({
|
|
921
|
+
...options,
|
|
922
|
+
columns: [
|
|
923
|
+
{
|
|
924
|
+
key: "source",
|
|
925
|
+
label: "Source",
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
key: "detail",
|
|
929
|
+
label: "Detail",
|
|
930
|
+
maxWidth: 28,
|
|
931
|
+
overflow,
|
|
932
|
+
},
|
|
933
|
+
],
|
|
934
|
+
rows: [
|
|
935
|
+
{
|
|
936
|
+
detail: "packages/cli-style/src/primitives/table.js",
|
|
937
|
+
source: "Table",
|
|
938
|
+
},
|
|
939
|
+
],
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
903
943
|
/**
|
|
904
944
|
* Render a chart that exposes positive, warning, and negative colours.
|
|
905
945
|
*
|
package/src/primitives/table.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { foreground } from "../formatters/ansi.js";
|
|
2
|
+
import { defaultWidth, minimumWidth, normaliseWidth } from "../formatters/width.js";
|
|
2
3
|
import { tableColours } from "../theme/colours.js";
|
|
3
4
|
|
|
4
|
-
// Tables assume a standard terminal width when no capability is provided.
|
|
5
|
-
const defaultWidth = 80;
|
|
6
|
-
|
|
7
5
|
// Columns use a compact but readable gap.
|
|
8
6
|
const columnSeparator = " ";
|
|
9
7
|
|
|
@@ -15,7 +13,9 @@ const columnSeparator = " ";
|
|
|
15
13
|
* @param {boolean} options.colour
|
|
16
14
|
* Whether ANSI colour should be applied.
|
|
17
15
|
* @param {object[]} options.columns
|
|
18
|
-
* Column keys and labels.
|
|
16
|
+
* Column keys and labels. A column may set maxWidth to cap its width,
|
|
17
|
+
* with overflow ('truncate', the default, or 'wrap') controlling how
|
|
18
|
+
* values beyond that width render.
|
|
19
19
|
* @param {object[]} options.rows
|
|
20
20
|
* Table records.
|
|
21
21
|
* @param {boolean} options.unicode
|
|
@@ -39,7 +39,18 @@ export function table(options = {}) {
|
|
|
39
39
|
widths.reduce((total, width) => total + width, 0) +
|
|
40
40
|
columnSeparator.length * (columns.length - 1);
|
|
41
41
|
|
|
42
|
-
const
|
|
42
|
+
const requestedWidth = Number.isFinite(options.width) ? options.width : undefined;
|
|
43
|
+
|
|
44
|
+
// Let a requested width smaller than minimumWidth pass through unclamped.
|
|
45
|
+
const minimumTableWidth =
|
|
46
|
+
requestedWidth === undefined
|
|
47
|
+
? minimumWidth
|
|
48
|
+
: Math.max(1, Math.min(requestedWidth, minimumWidth));
|
|
49
|
+
|
|
50
|
+
const availableWidth = normaliseWidth(requestedWidth, {
|
|
51
|
+
defaultWidth,
|
|
52
|
+
minWidth: minimumTableWidth,
|
|
53
|
+
});
|
|
43
54
|
|
|
44
55
|
if (naturalWidth > availableWidth) {
|
|
45
56
|
return renderBlocks(columns, rows, options);
|
|
@@ -99,9 +110,65 @@ function normaliseRows(rows) {
|
|
|
99
110
|
* Column widths.
|
|
100
111
|
*/
|
|
101
112
|
function columnWidths(columns, rows) {
|
|
102
|
-
return columns.map((column) =>
|
|
103
|
-
Math.max(
|
|
104
|
-
|
|
113
|
+
return columns.map((column) => {
|
|
114
|
+
const naturalWidth = Math.max(
|
|
115
|
+
column.label.length,
|
|
116
|
+
...rows.map((row) => cellValue(row[column.key]).length),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const maxWidth = columnMaxWidth(column);
|
|
120
|
+
|
|
121
|
+
return maxWidth === null ? naturalWidth : Math.min(naturalWidth, maxWidth);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Return a column's configured width cap, if any.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} column
|
|
129
|
+
* Table column.
|
|
130
|
+
* @returns {number|null}
|
|
131
|
+
* Normalised maxWidth, or null when the column has none.
|
|
132
|
+
*/
|
|
133
|
+
function columnMaxWidth(column) {
|
|
134
|
+
if (!Number.isFinite(column.maxWidth) || column.maxWidth < 1) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return normaliseWidth(column.maxWidth, {
|
|
139
|
+
defaultWidth: 1,
|
|
140
|
+
minWidth: 1,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Split a cell's value into the lines it renders as within a fixed-width column.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} value
|
|
148
|
+
* Cell value.
|
|
149
|
+
* @param {object} column
|
|
150
|
+
* Table column, read for its overflow mode.
|
|
151
|
+
* @param {number} width
|
|
152
|
+
* Column width.
|
|
153
|
+
* @returns {string[]}
|
|
154
|
+
* Lines the cell renders as; truncated to one line with a trailing
|
|
155
|
+
* ellipsis unless the column's overflow is 'wrap'.
|
|
156
|
+
*/
|
|
157
|
+
function columnLines(value, column, width) {
|
|
158
|
+
if (value.length <= width) {
|
|
159
|
+
return [value];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (column.overflow === "wrap") {
|
|
163
|
+
return Array.from(
|
|
164
|
+
{
|
|
165
|
+
length: Math.ceil(value.length / width),
|
|
166
|
+
},
|
|
167
|
+
(_, index) => value.slice(index * width, (index + 1) * width),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [`${value.slice(0, width - 1)}…`];
|
|
105
172
|
}
|
|
106
173
|
|
|
107
174
|
/**
|
|
@@ -120,9 +187,11 @@ function columnWidths(columns, rows) {
|
|
|
120
187
|
*/
|
|
121
188
|
function renderTable(columns, rows, widths, options) {
|
|
122
189
|
const header = columns
|
|
123
|
-
.map((column, index) =>
|
|
124
|
-
|
|
125
|
-
|
|
190
|
+
.map((column, index) => {
|
|
191
|
+
const headerLine = columnLines(column.label, { overflow: "truncate" }, widths[index])[0];
|
|
192
|
+
|
|
193
|
+
return renderHeader(padColumn(headerLine, widths[index], index, columns.length), options);
|
|
194
|
+
})
|
|
126
195
|
.join(columnSeparator);
|
|
127
196
|
|
|
128
197
|
const ruleCharacter = options.unicode === false ? "-" : "─";
|
|
@@ -131,17 +200,29 @@ function renderTable(columns, rows, widths, options) {
|
|
|
131
200
|
const renderedRule =
|
|
132
201
|
options.colour === true ? foreground(rule, tableColours.border, options) : rule;
|
|
133
202
|
|
|
134
|
-
const body = rows.
|
|
135
|
-
columns
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
203
|
+
const body = rows.flatMap((row) => {
|
|
204
|
+
const lines = columns.map((column, index) =>
|
|
205
|
+
columnLines(cellValue(row[column.key]), column, widths[index]),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const lineCount = Math.max(...lines.map((column) => column.length));
|
|
209
|
+
|
|
210
|
+
return Array.from(
|
|
211
|
+
{
|
|
212
|
+
length: lineCount,
|
|
213
|
+
},
|
|
214
|
+
(_, lineIndex) =>
|
|
215
|
+
columns
|
|
216
|
+
.map((column, index) =>
|
|
217
|
+
renderCell(
|
|
218
|
+
padColumn(lines[index][lineIndex] ?? "", widths[index], index, columns.length),
|
|
219
|
+
index,
|
|
220
|
+
options,
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
.join(columnSeparator),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
145
226
|
|
|
146
227
|
return [header, renderedRule, ...body].join("\n");
|
|
147
228
|
}
|