@lewishowles/cli-style 0.8.0 → 0.9.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 +17 -0
- package/README.md +31 -1
- package/adapters/bash/cli-style.sh +128 -6
- package/adapters/python/cli_style.py +68 -3
- package/adapters/swift/CliStyle.swift +81 -6
- package/package.json +1 -1
- package/src/capability/terminal.js +3 -1
- package/src/cli/render-command.js +6 -0
- package/src/gallery/render-gallery.js +25 -0
- package/src/index.js +1 -0
- package/src/primitives/row-group.js +84 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.9.0 - 2026-07-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added a `rowGroup` primitive (`rowGroup()` in JS, the `row-group` renderer for `cli-style render`, and the same generic `render()`/`cli_style_render`/`CliStyle.render` entry points in Python, Bash, and Swift) that aligns a group of related rows automatically, without the caller computing `labelWidth` by hand.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed the Python, Bash, and Swift adapters silently dropping `row()`, `span()`, and `divider()` options beyond their two or three main fields. `row()` now forwards `labelWidth`, `labelColour`, `valueColour`, and `separator`; `span()` forwards `weight`; `divider()` forwards `dividerWidth`, `dividerColour`, and `labelColour`.
|
|
14
|
+
|
|
15
|
+
## 0.8.1 - 2026-07-15
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed the Python and Swift adapters losing ANSI colour: they now set `FORCE_COLOR=1` for the child process when the caller's own stdout is a TTY, unless the caller has already set `FORCE_COLOR` or `NO_COLOR`.
|
|
20
|
+
- Documented that captured Bash output (command substitution) cannot auto-detect its eventual destination and requires an explicit `FORCE_COLOR=1` to opt into colour.
|
|
21
|
+
|
|
5
22
|
## 0.8.0 - 2026-07-05
|
|
6
23
|
|
|
7
24
|
### Added
|
package/README.md
CHANGED
|
@@ -107,6 +107,14 @@ cli_style_next_step_block "Update helpers scripts" "Wrappers are now available"
|
|
|
107
107
|
|
|
108
108
|
The convenience functions build JSON internally, so callers do not need to escape quotes, backslashes, or paths by hand.
|
|
109
109
|
|
|
110
|
+
Direct Bash calls use `cli-style`'s native stdout detection. When a helper's output is captured, the adapter cannot know where the captured text will be written, so set `FORCE_COLOR=1` explicitly when the destination is an interactive terminal:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
command="$(FORCE_COLOR=1 cli_style_span "npm run docs:readme" info)"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Python and Swift automatically set `FORCE_COLOR=1` for the child render process when their caller's stdout is an interactive terminal. An existing `FORCE_COLOR` or `NO_COLOR` value is preserved. `FORCE_COLOR=0` does not enable colour, while `NO_COLOR`, `TERM=dumb`, and explicit no-colour flags remain authoritative.
|
|
117
|
+
|
|
110
118
|
Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
|
|
111
119
|
|
|
112
120
|
```bash
|
|
@@ -219,6 +227,7 @@ Renderer names are stable for `cli-style render`, `cli_style_render`, and Python
|
|
|
219
227
|
| --------------------- | ------------------------------------- | ------------------------------------ | ---------------------------------- | -------------------------------------- | --------------------------------------------------------------- |
|
|
220
228
|
| `status` | `status(resultType, detail, options)` | `cli_style_status type label detail` | `status(type, label, detail)` | `CliStyle.status(type, label, detail)` | Result line with symbol, label, and detail. |
|
|
221
229
|
| `row` | `row(label, value, options)` | `cli_style_row label value [result]` | `row(label, value, result)` | `CliStyle.row(label, value, result)` | Aligned label/value row, optionally marked with a result state. |
|
|
230
|
+
| `row-group` | `rowGroup(options)` | Use `cli_style_render` | Use `render("row-group", ...)` | Use `CliStyle.render` | Auto-aligned group of labelled value rows. |
|
|
222
231
|
| `span` | `span(value, tone, options)` | `cli_style_span value [tone]` | `span(value, tone)` | `CliStyle.span(value, tone)` | Inline colour or weight for a word, command, file, or value. |
|
|
223
232
|
| `hint` | `hint(message, options)` | `cli_style_hint message` | `hint(message)` | `CliStyle.hint(message)` | Informational hint line. |
|
|
224
233
|
| `divider` | `divider(options)` | `cli_style_divider label` | `divider(label)` | `CliStyle.divider(label)` | Section divider with an optional label. |
|
|
@@ -274,6 +283,27 @@ Use these shapes with `cli-style render`, `cli_style_render`, or Python `render(
|
|
|
274
283
|
- `labelWidth`: optional minimum label width
|
|
275
284
|
- `labelColour`, `valueColour`, `separator`: optional presentation overrides
|
|
276
285
|
|
|
286
|
+
### `row-group`
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"rows": [
|
|
291
|
+
{
|
|
292
|
+
"label": "Package",
|
|
293
|
+
"value": "@lewishowles/components"
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"label": "Version",
|
|
297
|
+
"value": "minor: 2.7.4 -> 2.8.0"
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
- `rows`: labelled values to align and render
|
|
304
|
+
- `labelWidth`: optional minimum width for every label
|
|
305
|
+
- Per-row fields match `row`, including `label`, `value`, `result`, `labelColour`, `valueColour`, and `separator`
|
|
306
|
+
|
|
277
307
|
### `span`
|
|
278
308
|
|
|
279
309
|
```json
|
|
@@ -506,7 +536,7 @@ Most renderers accept the same output options:
|
|
|
506
536
|
| `unicode` | Whether Unicode symbols should be used. |
|
|
507
537
|
| `width` | Available output width for renderers that adapt layout. |
|
|
508
538
|
|
|
509
|
-
Use `createCliStyle()` to resolve these options from `argv`, `env`, `stdout`, CI, TTY, `NO_COLOR`, and terminal capability.
|
|
539
|
+
Use `createCliStyle()` to resolve these options from `argv`, `env`, `stdout`, CI, TTY, `NO_COLOR`, `FORCE_COLOR`, and terminal capability.
|
|
510
540
|
|
|
511
541
|
### CLI and Bash flags
|
|
512
542
|
|
|
@@ -159,6 +159,11 @@ cli_style_row() {
|
|
|
159
159
|
local label="${1:-}"
|
|
160
160
|
local value="${2:-}"
|
|
161
161
|
local result=""
|
|
162
|
+
local label_width=""
|
|
163
|
+
local label_colour_json="null"
|
|
164
|
+
local value_colour_json="null"
|
|
165
|
+
local separator_json="null"
|
|
166
|
+
local -a render_args=()
|
|
162
167
|
local json
|
|
163
168
|
|
|
164
169
|
if (($# >= 3)) && [[ "${3:-}" != --* ]]; then
|
|
@@ -170,9 +175,60 @@ cli_style_row() {
|
|
|
170
175
|
set --
|
|
171
176
|
fi
|
|
172
177
|
|
|
173
|
-
|
|
178
|
+
while (($# > 0)); do
|
|
179
|
+
case "$1" in
|
|
180
|
+
--label-width)
|
|
181
|
+
label_width="${2:-}"
|
|
182
|
+
shift 2
|
|
183
|
+
;;
|
|
184
|
+
--label-width=*)
|
|
185
|
+
label_width="${1#*=}"
|
|
186
|
+
shift
|
|
187
|
+
;;
|
|
188
|
+
--label-colour)
|
|
189
|
+
label_colour_json="$(cli_style_json_string "${2:-}")"
|
|
190
|
+
shift 2
|
|
191
|
+
;;
|
|
192
|
+
--label-colour=*)
|
|
193
|
+
label_colour_json="$(cli_style_json_string "${1#*=}")"
|
|
194
|
+
shift
|
|
195
|
+
;;
|
|
196
|
+
--value-colour)
|
|
197
|
+
value_colour_json="$(cli_style_json_string "${2:-}")"
|
|
198
|
+
shift 2
|
|
199
|
+
;;
|
|
200
|
+
--value-colour=*)
|
|
201
|
+
value_colour_json="$(cli_style_json_string "${1#*=}")"
|
|
202
|
+
shift
|
|
203
|
+
;;
|
|
204
|
+
--separator)
|
|
205
|
+
separator_json="$(cli_style_json_string "${2:-}")"
|
|
206
|
+
shift 2
|
|
207
|
+
;;
|
|
208
|
+
--separator=*)
|
|
209
|
+
separator_json="$(cli_style_json_string "${1#*=}")"
|
|
210
|
+
shift
|
|
211
|
+
;;
|
|
212
|
+
*)
|
|
213
|
+
render_args+=("$1")
|
|
214
|
+
shift
|
|
215
|
+
;;
|
|
216
|
+
esac
|
|
217
|
+
done
|
|
218
|
+
|
|
219
|
+
json="{\"label\":$(cli_style_json_string "$label"),\"value\":$(cli_style_json_string "$value"),\"result\":$(cli_style_json_string "$result"),\"labelWidth\":$(cli_style_json_integer_or_null "$label_width"),\"separator\":$separator_json"
|
|
220
|
+
|
|
221
|
+
if [[ "$label_colour_json" != "null" ]]; then
|
|
222
|
+
json="${json},\"labelColour\":$label_colour_json"
|
|
223
|
+
fi
|
|
174
224
|
|
|
175
|
-
|
|
225
|
+
if [[ "$value_colour_json" != "null" ]]; then
|
|
226
|
+
json="${json},\"valueColour\":$value_colour_json"
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
json="${json}}"
|
|
230
|
+
|
|
231
|
+
cli_style_render_json row "$json" "${render_args[@]}"
|
|
176
232
|
}
|
|
177
233
|
|
|
178
234
|
# Render an inline span from Bash string values.
|
|
@@ -186,6 +242,8 @@ cli_style_row() {
|
|
|
186
242
|
cli_style_span() {
|
|
187
243
|
local value="${1:-}"
|
|
188
244
|
local tone="info"
|
|
245
|
+
local weight_json="null"
|
|
246
|
+
local -a render_args=()
|
|
189
247
|
local json
|
|
190
248
|
|
|
191
249
|
if (($# >= 2)) && [[ "${2:-}" != --* ]]; then
|
|
@@ -197,9 +255,26 @@ cli_style_span() {
|
|
|
197
255
|
set --
|
|
198
256
|
fi
|
|
199
257
|
|
|
200
|
-
|
|
258
|
+
while (($# > 0)); do
|
|
259
|
+
case "$1" in
|
|
260
|
+
--weight)
|
|
261
|
+
weight_json="$(cli_style_json_string "${2:-}")"
|
|
262
|
+
shift 2
|
|
263
|
+
;;
|
|
264
|
+
--weight=*)
|
|
265
|
+
weight_json="$(cli_style_json_string "${1#*=}")"
|
|
266
|
+
shift
|
|
267
|
+
;;
|
|
268
|
+
*)
|
|
269
|
+
render_args+=("$1")
|
|
270
|
+
shift
|
|
271
|
+
;;
|
|
272
|
+
esac
|
|
273
|
+
done
|
|
274
|
+
|
|
275
|
+
json="{\"value\":$(cli_style_json_string "$value"),\"tone\":$(cli_style_json_string "$tone"),\"weight\":$weight_json}"
|
|
201
276
|
|
|
202
|
-
cli_style_render_json span "$json" "
|
|
277
|
+
cli_style_render_json span "$json" "${render_args[@]}"
|
|
203
278
|
}
|
|
204
279
|
|
|
205
280
|
# Render a divider from a Bash string value.
|
|
@@ -210,6 +285,10 @@ cli_style_span() {
|
|
|
210
285
|
# Render flags passed through to `cli-style render`.
|
|
211
286
|
cli_style_divider() {
|
|
212
287
|
local label="${1:-}"
|
|
288
|
+
local divider_width=""
|
|
289
|
+
local divider_colour_json="null"
|
|
290
|
+
local label_colour_json="null"
|
|
291
|
+
local -a render_args=()
|
|
213
292
|
local json
|
|
214
293
|
|
|
215
294
|
if (($# >= 1)); then
|
|
@@ -218,9 +297,52 @@ cli_style_divider() {
|
|
|
218
297
|
set --
|
|
219
298
|
fi
|
|
220
299
|
|
|
221
|
-
|
|
300
|
+
while (($# > 0)); do
|
|
301
|
+
case "$1" in
|
|
302
|
+
--divider-width)
|
|
303
|
+
divider_width="${2:-}"
|
|
304
|
+
shift 2
|
|
305
|
+
;;
|
|
306
|
+
--divider-width=*)
|
|
307
|
+
divider_width="${1#*=}"
|
|
308
|
+
shift
|
|
309
|
+
;;
|
|
310
|
+
--divider-colour)
|
|
311
|
+
divider_colour_json="$(cli_style_json_string "${2:-}")"
|
|
312
|
+
shift 2
|
|
313
|
+
;;
|
|
314
|
+
--divider-colour=*)
|
|
315
|
+
divider_colour_json="$(cli_style_json_string "${1#*=}")"
|
|
316
|
+
shift
|
|
317
|
+
;;
|
|
318
|
+
--label-colour)
|
|
319
|
+
label_colour_json="$(cli_style_json_string "${2:-}")"
|
|
320
|
+
shift 2
|
|
321
|
+
;;
|
|
322
|
+
--label-colour=*)
|
|
323
|
+
label_colour_json="$(cli_style_json_string "${1#*=}")"
|
|
324
|
+
shift
|
|
325
|
+
;;
|
|
326
|
+
*)
|
|
327
|
+
render_args+=("$1")
|
|
328
|
+
shift
|
|
329
|
+
;;
|
|
330
|
+
esac
|
|
331
|
+
done
|
|
332
|
+
|
|
333
|
+
json="{\"label\":$(cli_style_json_string "$label"),\"dividerWidth\":$(cli_style_json_integer_or_null "$divider_width")"
|
|
334
|
+
|
|
335
|
+
if [[ "$divider_colour_json" != "null" ]]; then
|
|
336
|
+
json="${json},\"dividerColour\":$divider_colour_json"
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
if [[ "$label_colour_json" != "null" ]]; then
|
|
340
|
+
json="${json},\"labelColour\":$label_colour_json"
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
json="${json}}"
|
|
222
344
|
|
|
223
|
-
cli_style_render_json divider "$json" "
|
|
345
|
+
cli_style_render_json divider "$json" "${render_args[@]}"
|
|
224
346
|
}
|
|
225
347
|
|
|
226
348
|
# Render a hint from a Bash string value.
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess
|
|
10
|
+
import sys
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Any, Sequence
|
|
12
13
|
|
|
@@ -23,6 +24,20 @@ class CliStyleRenderError(CliStyleError):
|
|
|
23
24
|
"""Raised when cli-style rejects a render request."""
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
# Resolve the child environment while preserving explicit caller controls.
|
|
28
|
+
def resolve_subprocess_environment() -> dict[str, str]:
|
|
29
|
+
environment = os.environ.copy()
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
sys.stdout.isatty()
|
|
33
|
+
and "FORCE_COLOR" not in environment
|
|
34
|
+
and "NO_COLOR" not in environment
|
|
35
|
+
):
|
|
36
|
+
environment["FORCE_COLOR"] = "1"
|
|
37
|
+
|
|
38
|
+
return environment
|
|
39
|
+
|
|
40
|
+
|
|
26
41
|
# Render caller data through `cli-style render`.
|
|
27
42
|
#
|
|
28
43
|
# @param {str} renderer
|
|
@@ -72,6 +87,7 @@ def render(
|
|
|
72
87
|
command,
|
|
73
88
|
capture_output=True,
|
|
74
89
|
check=False,
|
|
90
|
+
env=resolve_subprocess_environment(),
|
|
75
91
|
input=json.dumps(data),
|
|
76
92
|
text=True,
|
|
77
93
|
)
|
|
@@ -108,13 +124,39 @@ def status(
|
|
|
108
124
|
# Row value.
|
|
109
125
|
# @param {str} result
|
|
110
126
|
# Optional result type for a symbol and tone.
|
|
127
|
+
# @param {int|None} label_width
|
|
128
|
+
# Optional minimum label width.
|
|
129
|
+
# @param {str|None} label_colour
|
|
130
|
+
# Optional label colour token.
|
|
131
|
+
# @param {str|None} value_colour
|
|
132
|
+
# Optional value colour token.
|
|
133
|
+
# @param {str|None} separator
|
|
134
|
+
# Optional text between the label and value.
|
|
111
135
|
def row(
|
|
112
136
|
label: str,
|
|
113
137
|
value: str,
|
|
114
138
|
result: str = "",
|
|
139
|
+
label_width: int | None = None,
|
|
140
|
+
label_colour: str | None = None,
|
|
141
|
+
value_colour: str | None = None,
|
|
142
|
+
separator: str | None = None,
|
|
115
143
|
**kwargs: Any,
|
|
116
144
|
) -> str:
|
|
117
|
-
|
|
145
|
+
data = {
|
|
146
|
+
"label": label,
|
|
147
|
+
"value": value,
|
|
148
|
+
"result": result,
|
|
149
|
+
"labelWidth": label_width,
|
|
150
|
+
"separator": separator,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if label_colour is not None:
|
|
154
|
+
data["labelColour"] = label_colour
|
|
155
|
+
|
|
156
|
+
if value_colour is not None:
|
|
157
|
+
data["valueColour"] = value_colour
|
|
158
|
+
|
|
159
|
+
return render("row", data, **kwargs)
|
|
118
160
|
|
|
119
161
|
|
|
120
162
|
# Render an inline span from Python values.
|
|
@@ -123,23 +165,46 @@ def row(
|
|
|
123
165
|
# Span text.
|
|
124
166
|
# @param {str} tone
|
|
125
167
|
# Optional colour tone, defaulting to info.
|
|
168
|
+
# @param {str|None} weight
|
|
169
|
+
# Optional ANSI text weight.
|
|
126
170
|
def span(
|
|
127
171
|
value: str,
|
|
128
172
|
tone: str = "info",
|
|
173
|
+
weight: str | None = None,
|
|
129
174
|
**kwargs: Any,
|
|
130
175
|
) -> str:
|
|
131
|
-
return render("span", {"value": value, "tone": tone}, **kwargs)
|
|
176
|
+
return render("span", {"value": value, "tone": tone, "weight": weight}, **kwargs)
|
|
132
177
|
|
|
133
178
|
|
|
134
179
|
# Render a divider from a Python value.
|
|
135
180
|
#
|
|
136
181
|
# @param {str} label
|
|
137
182
|
# Optional divider label.
|
|
183
|
+
# @param {int|None} divider_width
|
|
184
|
+
# Optional divider width.
|
|
185
|
+
# @param {str|None} divider_colour
|
|
186
|
+
# Optional divider colour token.
|
|
187
|
+
# @param {str|None} label_colour
|
|
188
|
+
# Optional label colour token.
|
|
138
189
|
def divider(
|
|
139
190
|
label: str = "",
|
|
191
|
+
divider_width: int | None = None,
|
|
192
|
+
divider_colour: str | None = None,
|
|
193
|
+
label_colour: str | None = None,
|
|
140
194
|
**kwargs: Any,
|
|
141
195
|
) -> str:
|
|
142
|
-
|
|
196
|
+
data = {
|
|
197
|
+
"label": label,
|
|
198
|
+
"dividerWidth": divider_width,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if divider_colour is not None:
|
|
202
|
+
data["dividerColour"] = divider_colour
|
|
203
|
+
|
|
204
|
+
if label_colour is not None:
|
|
205
|
+
data["labelColour"] = label_colour
|
|
206
|
+
|
|
207
|
+
return render("divider", data, **kwargs)
|
|
143
208
|
|
|
144
209
|
|
|
145
210
|
# Render a hint from a Python value.
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
#if canImport(Darwin)
|
|
3
|
+
import Darwin
|
|
4
|
+
#elseif canImport(Glibc)
|
|
5
|
+
import Glibc
|
|
6
|
+
#endif
|
|
2
7
|
|
|
3
8
|
// Error types for cli-style adapter failures.
|
|
4
9
|
enum CliStyleError: Error {
|
|
@@ -89,6 +94,7 @@ enum CliStyle {
|
|
|
89
94
|
let process = Process()
|
|
90
95
|
process.executableURL = URL(fileURLWithPath: resolvedBinary)
|
|
91
96
|
process.arguments = arguments
|
|
97
|
+
process.environment = resolveProcessEnvironment()
|
|
92
98
|
|
|
93
99
|
let stdinPipe = Pipe()
|
|
94
100
|
let stdoutPipe = Pipe()
|
|
@@ -125,6 +131,21 @@ enum CliStyle {
|
|
|
125
131
|
return stdoutText.trimmingCharacters(in: .newlines)
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Resolve the child environment for a captured render.
|
|
136
|
+
*/
|
|
137
|
+
private static func resolveProcessEnvironment() -> [String: String] {
|
|
138
|
+
var environment = ProcessInfo.processInfo.environment
|
|
139
|
+
|
|
140
|
+
if isatty(STDOUT_FILENO) == 1 &&
|
|
141
|
+
environment["FORCE_COLOR"] == nil &&
|
|
142
|
+
environment["NO_COLOR"] == nil {
|
|
143
|
+
environment["FORCE_COLOR"] = "1"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return environment
|
|
147
|
+
}
|
|
148
|
+
|
|
128
149
|
/**
|
|
129
150
|
Render a result status line.
|
|
130
151
|
|
|
@@ -154,6 +175,10 @@ enum CliStyle {
|
|
|
154
175
|
- parameter label: Row label.
|
|
155
176
|
- parameter value: Row value.
|
|
156
177
|
- parameter result: Optional result type for a symbol and tone.
|
|
178
|
+
- parameter labelWidth: Optional minimum label width.
|
|
179
|
+
- parameter labelColour: Optional label colour token.
|
|
180
|
+
- parameter valueColour: Optional value colour token.
|
|
181
|
+
- parameter separator: Optional text between the label and value.
|
|
157
182
|
- parameter options: Render options.
|
|
158
183
|
- returns: Rendered row line.
|
|
159
184
|
- throws: `CliStyleError` on failure.
|
|
@@ -162,13 +187,35 @@ enum CliStyle {
|
|
|
162
187
|
label: String,
|
|
163
188
|
value: String,
|
|
164
189
|
result: String = "",
|
|
190
|
+
labelWidth: Int? = nil,
|
|
191
|
+
labelColour: String? = nil,
|
|
192
|
+
valueColour: String? = nil,
|
|
193
|
+
separator: String? = nil,
|
|
165
194
|
options: CliStyleOptions = .init()
|
|
166
195
|
) throws -> String {
|
|
167
|
-
|
|
196
|
+
var data: [String: Any] = [
|
|
168
197
|
"label": label,
|
|
169
198
|
"value": value,
|
|
170
199
|
"result": result,
|
|
171
|
-
]
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
if let labelWidth = labelWidth {
|
|
203
|
+
data["labelWidth"] = labelWidth
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if let labelColour = labelColour {
|
|
207
|
+
data["labelColour"] = labelColour
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if let valueColour = valueColour {
|
|
211
|
+
data["valueColour"] = valueColour
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if let separator = separator {
|
|
215
|
+
data["separator"] = separator
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return try render("row", data: data, options: options)
|
|
172
219
|
}
|
|
173
220
|
|
|
174
221
|
/**
|
|
@@ -176,6 +223,7 @@ enum CliStyle {
|
|
|
176
223
|
|
|
177
224
|
- parameter value: Span text.
|
|
178
225
|
- parameter tone: Optional colour tone, defaulting to info.
|
|
226
|
+
- parameter weight: Optional ANSI text weight.
|
|
179
227
|
- parameter options: Render options.
|
|
180
228
|
- returns: Rendered span string.
|
|
181
229
|
- throws: `CliStyleError` on failure.
|
|
@@ -183,29 +231,56 @@ enum CliStyle {
|
|
|
183
231
|
static func span(
|
|
184
232
|
value: String,
|
|
185
233
|
tone: String = "info",
|
|
234
|
+
weight: String? = nil,
|
|
186
235
|
options: CliStyleOptions = .init()
|
|
187
236
|
) throws -> String {
|
|
188
|
-
|
|
237
|
+
var data: [String: Any] = [
|
|
189
238
|
"value": value,
|
|
190
239
|
"tone": tone,
|
|
191
|
-
]
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
if let weight = weight {
|
|
243
|
+
data["weight"] = weight
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return try render("span", data: data, options: options)
|
|
192
247
|
}
|
|
193
248
|
|
|
194
249
|
/**
|
|
195
250
|
Render a divider.
|
|
196
251
|
|
|
197
252
|
- parameter label: Optional divider label.
|
|
253
|
+
- parameter dividerWidth: Optional divider width.
|
|
254
|
+
- parameter dividerColour: Optional divider colour token.
|
|
255
|
+
- parameter labelColour: Optional label colour token.
|
|
198
256
|
- parameter options: Render options.
|
|
199
257
|
- returns: Rendered divider line.
|
|
200
258
|
- throws: `CliStyleError` on failure.
|
|
201
259
|
*/
|
|
202
260
|
static func divider(
|
|
203
261
|
label: String = "",
|
|
262
|
+
dividerWidth: Int? = nil,
|
|
263
|
+
dividerColour: String? = nil,
|
|
264
|
+
labelColour: String? = nil,
|
|
204
265
|
options: CliStyleOptions = .init()
|
|
205
266
|
) throws -> String {
|
|
206
|
-
|
|
267
|
+
var data: [String: Any] = [
|
|
207
268
|
"label": label,
|
|
208
|
-
]
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
if let dividerWidth = dividerWidth {
|
|
272
|
+
data["dividerWidth"] = dividerWidth
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if let dividerColour = dividerColour {
|
|
276
|
+
data["dividerColour"] = dividerColour
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if let labelColour = labelColour {
|
|
280
|
+
data["labelColour"] = labelColour
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return try render("divider", data: data, options: options)
|
|
209
284
|
}
|
|
210
285
|
|
|
211
286
|
/**
|
package/package.json
CHANGED
|
@@ -23,12 +23,14 @@ export function resolveTerminalCapabilities(options = {}) {
|
|
|
23
23
|
const isTty = stdout.isTTY === true;
|
|
24
24
|
const isCi = env.CI === "true" || env.CI === "1";
|
|
25
25
|
const isDumb = env.TERM === "dumb";
|
|
26
|
+
const forceColour = env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "0";
|
|
26
27
|
const width = resolveWidth(stdout.columns);
|
|
27
28
|
const usesPlain = argv.includes("--plain");
|
|
28
29
|
|
|
29
30
|
const colour = resolveColour({
|
|
30
31
|
argv,
|
|
31
32
|
env,
|
|
33
|
+
forceColour,
|
|
32
34
|
isDumb,
|
|
33
35
|
isTty,
|
|
34
36
|
usesPlain,
|
|
@@ -69,7 +71,7 @@ function resolveColour(options) {
|
|
|
69
71
|
return false;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
return options.isTty;
|
|
74
|
+
return options.forceColour || options.isTty;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/**
|
|
@@ -13,6 +13,7 @@ import { emptyState, errorBlock, hint } from "../primitives/feedback.js";
|
|
|
13
13
|
import { panel } from "../primitives/panel.js";
|
|
14
14
|
import { progressBar } from "../primitives/progress-bar.js";
|
|
15
15
|
import { row } from "../primitives/row.js";
|
|
16
|
+
import { rowGroup } from "../primitives/row-group.js";
|
|
16
17
|
import { span } from "../primitives/span.js";
|
|
17
18
|
import { status } from "../primitives/status.js";
|
|
18
19
|
import { step, stepProgress } from "../primitives/step-progress.js";
|
|
@@ -73,6 +74,11 @@ const renderers = {
|
|
|
73
74
|
...input,
|
|
74
75
|
...options,
|
|
75
76
|
}),
|
|
77
|
+
"row-group": (input, options) =>
|
|
78
|
+
rowGroup({
|
|
79
|
+
...input,
|
|
80
|
+
...options,
|
|
81
|
+
}),
|
|
76
82
|
span: (input, options) =>
|
|
77
83
|
span(input.value, input.tone, {
|
|
78
84
|
...input,
|
|
@@ -14,6 +14,7 @@ import { emptyState, errorBlock, hint } from "../primitives/feedback.js";
|
|
|
14
14
|
import { panel } from "../primitives/panel.js";
|
|
15
15
|
import { progressBar } from "../primitives/progress-bar.js";
|
|
16
16
|
import { row } from "../primitives/row.js";
|
|
17
|
+
import { rowGroup } from "../primitives/row-group.js";
|
|
17
18
|
import { span } from "../primitives/span.js";
|
|
18
19
|
import { status } from "../primitives/status.js";
|
|
19
20
|
import { step, stepProgress, stepStates } from "../primitives/step-progress.js";
|
|
@@ -715,6 +716,30 @@ function renderPrimitives(options) {
|
|
|
715
716
|
labelWidth: 8,
|
|
716
717
|
result: resultTypes.FAILED,
|
|
717
718
|
}),
|
|
719
|
+
"",
|
|
720
|
+
"Row groups",
|
|
721
|
+
rowGroup({
|
|
722
|
+
...options,
|
|
723
|
+
rows: [
|
|
724
|
+
{
|
|
725
|
+
label: "Package",
|
|
726
|
+
value: "@lewishowles/components",
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
label: "Version",
|
|
730
|
+
value: "minor: 2.7.4 -> 2.8.0",
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
label: "Checks",
|
|
734
|
+
value: "type-check, unit, build",
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
label: "Bundle",
|
|
738
|
+
result: resultTypes.FAILED,
|
|
739
|
+
value: "is 22.3 KB, above the 12.0 KB budget",
|
|
740
|
+
},
|
|
741
|
+
],
|
|
742
|
+
}),
|
|
718
743
|
].join("\n");
|
|
719
744
|
}
|
|
720
745
|
|
package/src/index.js
CHANGED
|
@@ -35,6 +35,7 @@ export { emptyState, errorBlock, hint } from "./primitives/feedback.js";
|
|
|
35
35
|
export { panel } from "./primitives/panel.js";
|
|
36
36
|
export { progressBar } from "./primitives/progress-bar.js";
|
|
37
37
|
export { row } from "./primitives/row.js";
|
|
38
|
+
export { rowGroup } from "./primitives/row-group.js";
|
|
38
39
|
export { span } from "./primitives/span.js";
|
|
39
40
|
export { status } from "./primitives/status.js";
|
|
40
41
|
export { step, stepProgress, stepStates } from "./primitives/step-progress.js";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { getResultSymbol } from "../theme/results.js";
|
|
2
|
+
import { row } from "./row.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render related labelled values against a shared label width.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} options
|
|
8
|
+
* Rendering options.
|
|
9
|
+
* @param {number} options.labelWidth
|
|
10
|
+
* Optional minimum label width.
|
|
11
|
+
* @param {object[]} options.rows
|
|
12
|
+
* Labelled values to render.
|
|
13
|
+
* @returns {string}
|
|
14
|
+
* Rendered row group.
|
|
15
|
+
*/
|
|
16
|
+
export function rowGroup(options = {}) {
|
|
17
|
+
const rows = normaliseRows(options.rows);
|
|
18
|
+
|
|
19
|
+
if (rows.length === 0) {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const labelWidth = Math.max(
|
|
24
|
+
...rows.map((entry) => displayedLabelLength(entry, options)),
|
|
25
|
+
options.labelWidth ?? 0,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return rows
|
|
29
|
+
.map((entry) =>
|
|
30
|
+
row(entry.label, entry.value ?? "", {
|
|
31
|
+
...options,
|
|
32
|
+
...entry,
|
|
33
|
+
labelWidth,
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
.join("\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return rows with usable labels.
|
|
41
|
+
*
|
|
42
|
+
* @param {object[]} rows
|
|
43
|
+
* Requested row entries.
|
|
44
|
+
* @returns {object[]}
|
|
45
|
+
* Valid row entries.
|
|
46
|
+
*/
|
|
47
|
+
function normaliseRows(rows) {
|
|
48
|
+
if (!Array.isArray(rows)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return rows.filter(
|
|
53
|
+
(entry) =>
|
|
54
|
+
entry !== null &&
|
|
55
|
+
typeof entry === "object" &&
|
|
56
|
+
typeof entry.label === "string" &&
|
|
57
|
+
entry.label !== "",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Return the visible width of a row label.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} entry
|
|
65
|
+
* Row entry.
|
|
66
|
+
* @param {object} options
|
|
67
|
+
* Group rendering options.
|
|
68
|
+
* @returns {number}
|
|
69
|
+
* Displayed label length.
|
|
70
|
+
*/
|
|
71
|
+
function displayedLabelLength(entry, options) {
|
|
72
|
+
if (typeof entry.result !== "string" || entry.result === "") {
|
|
73
|
+
return entry.label.length;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const entryOptions = {
|
|
77
|
+
...options,
|
|
78
|
+
...entry,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const symbol = getResultSymbol(entry.result, entryOptions);
|
|
82
|
+
|
|
83
|
+
return [symbol, entry.label].join(" ").length;
|
|
84
|
+
}
|