cli-kiss 0.2.3 → 0.2.5
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 +1 -1
- package/dist/index.d.ts +696 -734
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.mts +2 -3
- package/docs/.vitepress/theme/index.ts +4 -0
- package/docs/.vitepress/theme/style.css +4 -0
- package/docs/guide/01_getting_started.md +12 -13
- package/docs/guide/02_commands.md +71 -52
- package/docs/guide/03_options.md +25 -33
- package/docs/guide/04_positionals.md +45 -55
- package/docs/guide/05_types.md +66 -66
- package/docs/guide/06_run.md +28 -40
- package/docs/index.md +8 -3
- package/docs/public/favicon.ico +0 -0
- package/docs/public/hero.png +0 -0
- package/package.json +1 -1
- package/src/index.ts +0 -2
- package/src/lib/Command.ts +45 -123
- package/src/lib/Operation.ts +23 -32
- package/src/lib/Option.ts +150 -170
- package/src/lib/Positional.ts +44 -94
- package/src/lib/Reader.ts +123 -99
- package/src/lib/Run.ts +86 -45
- package/src/lib/Type.ts +246 -156
- package/src/lib/Typo.ts +98 -107
- package/src/lib/Usage.ts +163 -82
- package/tests/unit.Reader.aliases.ts +31 -15
- package/tests/unit.Reader.commons.ts +99 -43
- package/tests/unit.Reader.parsings.ts +50 -0
- package/tests/unit.Reader.shortBig.ts +75 -31
- package/tests/unit.command.execute.ts +86 -43
- package/tests/unit.command.usage.ts +88 -82
- package/tests/unit.runner.colors.ts +197 -0
- package/tests/unit.runner.cycle.ts +77 -63
- package/tests/unit.runner.errors.ts +23 -15
package/src/lib/Typo.ts
CHANGED
|
@@ -25,6 +25,10 @@ export type TypoColor =
|
|
|
25
25
|
* All fields are optional; ignored entirely in `"none"` mode.
|
|
26
26
|
*/
|
|
27
27
|
export type TypoStyle = {
|
|
28
|
+
/**
|
|
29
|
+
* Letter case.
|
|
30
|
+
*/
|
|
31
|
+
case?: "upper" | "lower";
|
|
28
32
|
/**
|
|
29
33
|
* Foreground (text) color.
|
|
30
34
|
*/
|
|
@@ -34,43 +38,43 @@ export type TypoStyle = {
|
|
|
34
38
|
*/
|
|
35
39
|
bgColor?: TypoColor;
|
|
36
40
|
/**
|
|
37
|
-
*
|
|
41
|
+
* Reduced intensity.
|
|
38
42
|
*/
|
|
39
43
|
dim?: boolean;
|
|
40
44
|
/**
|
|
41
|
-
*
|
|
45
|
+
* Bold.
|
|
42
46
|
*/
|
|
43
47
|
bold?: boolean;
|
|
44
48
|
/**
|
|
45
|
-
*
|
|
49
|
+
* Italic.
|
|
46
50
|
*/
|
|
47
51
|
italic?: boolean;
|
|
48
52
|
/**
|
|
49
|
-
*
|
|
53
|
+
* Underline.
|
|
50
54
|
*/
|
|
51
55
|
underline?: boolean;
|
|
52
56
|
/**
|
|
53
|
-
*
|
|
57
|
+
* Strikethrough.
|
|
54
58
|
*/
|
|
55
59
|
strikethrough?: boolean;
|
|
56
60
|
};
|
|
57
61
|
|
|
58
62
|
/**
|
|
59
|
-
*
|
|
63
|
+
* Section title style. Bold dark-green.
|
|
60
64
|
*/
|
|
61
65
|
export const typoStyleTitle: TypoStyle = {
|
|
62
66
|
fgColor: "darkGreen",
|
|
63
67
|
bold: true,
|
|
64
68
|
};
|
|
65
69
|
/**
|
|
66
|
-
*
|
|
70
|
+
* Logic/type identifier style. Bold dark-magenta.
|
|
67
71
|
*/
|
|
68
72
|
export const typoStyleLogic: TypoStyle = {
|
|
69
73
|
fgColor: "darkMagenta",
|
|
70
74
|
bold: true,
|
|
71
75
|
};
|
|
72
76
|
/**
|
|
73
|
-
*
|
|
77
|
+
* Quoted user-input style. Bold dark-yellow.
|
|
74
78
|
*/
|
|
75
79
|
export const typoStyleQuote: TypoStyle = {
|
|
76
80
|
fgColor: "darkYellow",
|
|
@@ -78,7 +82,7 @@ export const typoStyleQuote: TypoStyle = {
|
|
|
78
82
|
};
|
|
79
83
|
|
|
80
84
|
/**
|
|
81
|
-
*
|
|
85
|
+
* Error label style. Bold dark-red.
|
|
82
86
|
*/
|
|
83
87
|
export const typoStyleFailure: TypoStyle = {
|
|
84
88
|
fgColor: "darkRed",
|
|
@@ -86,14 +90,14 @@ export const typoStyleFailure: TypoStyle = {
|
|
|
86
90
|
};
|
|
87
91
|
|
|
88
92
|
/**
|
|
89
|
-
*
|
|
93
|
+
* Option/command name style. Bold dark-cyan.
|
|
90
94
|
*/
|
|
91
95
|
export const typoStyleConstants: TypoStyle = {
|
|
92
96
|
fgColor: "darkCyan",
|
|
93
97
|
bold: true,
|
|
94
98
|
};
|
|
95
99
|
/**
|
|
96
|
-
*
|
|
100
|
+
* Positional/user-input label style. Bold dark-blue.
|
|
97
101
|
*/
|
|
98
102
|
export const typoStyleUserInput: TypoStyle = {
|
|
99
103
|
fgColor: "darkBlue",
|
|
@@ -101,13 +105,13 @@ export const typoStyleUserInput: TypoStyle = {
|
|
|
101
105
|
};
|
|
102
106
|
|
|
103
107
|
/**
|
|
104
|
-
*
|
|
108
|
+
* Strong text style. Bold.
|
|
105
109
|
*/
|
|
106
110
|
export const typoStyleRegularStrong: TypoStyle = {
|
|
107
111
|
bold: true,
|
|
108
112
|
};
|
|
109
113
|
/**
|
|
110
|
-
*
|
|
114
|
+
* Subtle text style. Italic and dim.
|
|
111
115
|
*/
|
|
112
116
|
export const typoStyleRegularWeaker: TypoStyle = {
|
|
113
117
|
italic: true,
|
|
@@ -115,8 +119,7 @@ export const typoStyleRegularWeaker: TypoStyle = {
|
|
|
115
119
|
};
|
|
116
120
|
|
|
117
121
|
/**
|
|
118
|
-
*
|
|
119
|
-
* Compose multiple segments into a {@link TypoText}; rendering is deferred to {@link TypoString.computeStyledString}.
|
|
122
|
+
* Immutable styled string segment. Compose into a {@link TypoText}.
|
|
120
123
|
*/
|
|
121
124
|
export class TypoString {
|
|
122
125
|
#value: string;
|
|
@@ -129,12 +132,6 @@ export class TypoString {
|
|
|
129
132
|
this.#value = value;
|
|
130
133
|
this.#typoStyle = typoStyle;
|
|
131
134
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Returns the unstyled raw text content.
|
|
134
|
-
*/
|
|
135
|
-
getRawString(): string {
|
|
136
|
-
return this.#value;
|
|
137
|
-
}
|
|
138
135
|
/**
|
|
139
136
|
* Returns the text styled by `typoSupport`.
|
|
140
137
|
*
|
|
@@ -143,53 +140,46 @@ export class TypoString {
|
|
|
143
140
|
computeStyledString(typoSupport: TypoSupport): string {
|
|
144
141
|
return typoSupport.computeStyledString(this.#value, this.#typoStyle);
|
|
145
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Returns the raw text.
|
|
145
|
+
*/
|
|
146
|
+
getRawString(): string {
|
|
147
|
+
return this.#value;
|
|
148
|
+
}
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
/**
|
|
149
|
-
*
|
|
150
|
-
* Rendering is deferred to {@link TypoText.computeStyledString}.
|
|
152
|
+
* Mutable sequence of {@link TypoString} segments.
|
|
151
153
|
*/
|
|
152
154
|
export class TypoText {
|
|
153
155
|
#typoStrings: Array<TypoString>;
|
|
154
156
|
/**
|
|
155
|
-
* @param
|
|
157
|
+
* @param segments - Initial text segments
|
|
156
158
|
*/
|
|
157
|
-
constructor(
|
|
159
|
+
constructor(
|
|
160
|
+
...segments: Array<TypoText | Array<TypoString> | TypoString | string>
|
|
161
|
+
) {
|
|
158
162
|
this.#typoStrings = [];
|
|
159
|
-
for (const typoPart of
|
|
160
|
-
|
|
161
|
-
this.pushText(typoPart);
|
|
162
|
-
} else if (typoPart instanceof TypoString) {
|
|
163
|
-
this.pushString(typoPart);
|
|
164
|
-
} else if (typeof typoPart === "string") {
|
|
165
|
-
this.pushString(new TypoString(typoPart));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Appends a {@link TypoString} segment.
|
|
171
|
-
*
|
|
172
|
-
* @param typoString - Segment to append.
|
|
173
|
-
*/
|
|
174
|
-
pushString(typoString: TypoString | string) {
|
|
175
|
-
if (typeof typoString === "string") {
|
|
176
|
-
this.#typoStrings.push(new TypoString(typoString));
|
|
177
|
-
} else {
|
|
178
|
-
this.#typoStrings.push(typoString);
|
|
163
|
+
for (const typoPart of segments) {
|
|
164
|
+
this.push(typoPart);
|
|
179
165
|
}
|
|
180
166
|
}
|
|
181
167
|
/**
|
|
182
|
-
* Appends
|
|
168
|
+
* Appends new text segment(s).
|
|
183
169
|
*
|
|
184
|
-
* @param
|
|
170
|
+
* @param segment - Text segment(s) to append.
|
|
185
171
|
*/
|
|
186
|
-
|
|
187
|
-
if (typeof
|
|
188
|
-
this.
|
|
189
|
-
} else {
|
|
190
|
-
|
|
172
|
+
push(segment: TypoText | Array<TypoString> | TypoString | string) {
|
|
173
|
+
if (typeof segment === "string") {
|
|
174
|
+
this.#typoStrings.push(new TypoString(segment));
|
|
175
|
+
} else if (segment instanceof TypoText) {
|
|
176
|
+
this.#typoStrings.push(...segment.#typoStrings);
|
|
177
|
+
} else if (Array.isArray(segment)) {
|
|
178
|
+
for (const typoString of segment) {
|
|
191
179
|
this.#typoStrings.push(typoString);
|
|
192
180
|
}
|
|
181
|
+
} else {
|
|
182
|
+
this.#typoStrings.push(segment);
|
|
193
183
|
}
|
|
194
184
|
}
|
|
195
185
|
/**
|
|
@@ -204,13 +194,13 @@ export class TypoText {
|
|
|
204
194
|
.join("");
|
|
205
195
|
}
|
|
206
196
|
/**
|
|
207
|
-
* Returns the
|
|
197
|
+
* Returns the concatenated raw text.
|
|
208
198
|
*/
|
|
209
199
|
computeRawString(): string {
|
|
210
200
|
return this.#typoStrings.map((t) => t.getRawString()).join("");
|
|
211
201
|
}
|
|
212
202
|
/**
|
|
213
|
-
* Returns the total character count
|
|
203
|
+
* Returns the total raw character count.
|
|
214
204
|
*/
|
|
215
205
|
computeRawLength(): number {
|
|
216
206
|
let length = 0;
|
|
@@ -222,9 +212,8 @@ export class TypoText {
|
|
|
222
212
|
}
|
|
223
213
|
|
|
224
214
|
/**
|
|
225
|
-
*
|
|
215
|
+
* Column-aligned grid of {@link TypoText} cells.
|
|
226
216
|
* Each column is padded to the widest cell (raw chars); the last column is not padded.
|
|
227
|
-
* Used by {@link usageToStyledLines} to render `Positionals:`, `Subcommands:`, and `Options:`.
|
|
228
217
|
*/
|
|
229
218
|
export class TypoGrid {
|
|
230
219
|
#typoRows: Array<Array<TypoText>>;
|
|
@@ -232,7 +221,7 @@ export class TypoGrid {
|
|
|
232
221
|
this.#typoRows = [];
|
|
233
222
|
}
|
|
234
223
|
/**
|
|
235
|
-
* Appends a row. All rows should have the same cell count
|
|
224
|
+
* Appends a row. All rows should have the same cell count.
|
|
236
225
|
*
|
|
237
226
|
* @param cells - Ordered {@link TypoText} cells.
|
|
238
227
|
*/
|
|
@@ -240,15 +229,14 @@ export class TypoGrid {
|
|
|
240
229
|
this.#typoRows.push(cells);
|
|
241
230
|
}
|
|
242
231
|
/**
|
|
243
|
-
* Renders
|
|
244
|
-
* Join each inner array with `""` to get a line.
|
|
232
|
+
* Renders as an array of styled, column-padded strings.
|
|
245
233
|
*
|
|
246
234
|
* @param typoSupport - Rendering mode.
|
|
247
|
-
* @returns
|
|
235
|
+
* @returns Array of styled strings.
|
|
248
236
|
*/
|
|
249
|
-
|
|
237
|
+
computeStyledLines(typoSupport: TypoSupport): Array<string> {
|
|
250
238
|
const widths = new Array<number>();
|
|
251
|
-
const
|
|
239
|
+
const styledLines = new Array<string>();
|
|
252
240
|
for (const typoGridRow of this.#typoRows) {
|
|
253
241
|
for (
|
|
254
242
|
let typoGridColumnIndex = 0;
|
|
@@ -266,31 +254,29 @@ export class TypoGrid {
|
|
|
266
254
|
}
|
|
267
255
|
}
|
|
268
256
|
for (const typoGridRow of this.#typoRows) {
|
|
269
|
-
const
|
|
257
|
+
const styledGridRow = new Array<string>();
|
|
270
258
|
for (
|
|
271
259
|
let typoGridColumnIndex = 0;
|
|
272
260
|
typoGridColumnIndex < typoGridRow.length;
|
|
273
261
|
typoGridColumnIndex++
|
|
274
262
|
) {
|
|
275
263
|
const typoGridCell = typoGridRow[typoGridColumnIndex]!;
|
|
276
|
-
|
|
277
|
-
printableGridRow.push(printableGridCell);
|
|
264
|
+
styledGridRow.push(typoGridCell.computeStyledString(typoSupport));
|
|
278
265
|
if (typoGridColumnIndex < typoGridRow.length - 1) {
|
|
279
266
|
const width = typoGridCell.computeRawLength();
|
|
280
267
|
const padding = " ".repeat(widths[typoGridColumnIndex]! - width);
|
|
281
|
-
|
|
268
|
+
styledGridRow.push(padding);
|
|
282
269
|
}
|
|
283
270
|
}
|
|
284
|
-
|
|
271
|
+
styledLines.push(styledGridRow.join(""));
|
|
285
272
|
}
|
|
286
|
-
return
|
|
273
|
+
return styledLines;
|
|
287
274
|
}
|
|
288
275
|
}
|
|
289
276
|
|
|
290
277
|
/**
|
|
291
278
|
* `Error` subclass with a {@link TypoText} styled message for rich terminal output.
|
|
292
|
-
*
|
|
293
|
-
* If `source` is a `TypoError`, its styled text is chained after `": "`.
|
|
279
|
+
* Chains `TypoError` sources after `": "`.
|
|
294
280
|
*/
|
|
295
281
|
export class TypoError extends Error {
|
|
296
282
|
#typoText: TypoText;
|
|
@@ -300,20 +286,20 @@ export class TypoError extends Error {
|
|
|
300
286
|
*/
|
|
301
287
|
constructor(currentTypoText: TypoText, source?: unknown) {
|
|
302
288
|
const typoText = new TypoText();
|
|
303
|
-
typoText.
|
|
289
|
+
typoText.push(currentTypoText);
|
|
304
290
|
if (source instanceof TypoError) {
|
|
305
|
-
typoText.
|
|
306
|
-
typoText.
|
|
291
|
+
typoText.push(new TypoString(": "));
|
|
292
|
+
typoText.push(source.#typoText);
|
|
307
293
|
} else if (source instanceof Error) {
|
|
308
|
-
typoText.
|
|
294
|
+
typoText.push(new TypoString(`: ${source.message}`));
|
|
309
295
|
} else if (source !== undefined) {
|
|
310
|
-
typoText.
|
|
296
|
+
typoText.push(new TypoString(`: ${String(source)}`));
|
|
311
297
|
}
|
|
312
298
|
super(typoText.computeRawString());
|
|
313
299
|
this.#typoText = typoText;
|
|
314
300
|
}
|
|
315
301
|
/**
|
|
316
|
-
* Renders the styled
|
|
302
|
+
* Renders the styled message (without `"Error:"` prefix).
|
|
317
303
|
*
|
|
318
304
|
* @param typoSupport - Rendering mode.
|
|
319
305
|
* @returns Styled error string.
|
|
@@ -322,8 +308,7 @@ export class TypoError extends Error {
|
|
|
322
308
|
return this.#typoText.computeStyledString(typoSupport);
|
|
323
309
|
}
|
|
324
310
|
/**
|
|
325
|
-
* Runs `thrower`;
|
|
326
|
-
* Useful for adding call-chain context (e.g. `"at 0: Number: ..."`).
|
|
311
|
+
* Runs `thrower`; wraps any thrown error as a `TypoError` with `context()` prepended.
|
|
327
312
|
*
|
|
328
313
|
* @typeParam Value - Return type of `thrower`.
|
|
329
314
|
* @param thrower - Function to execute; result passed through on success.
|
|
@@ -344,9 +329,7 @@ export class TypoError extends Error {
|
|
|
344
329
|
}
|
|
345
330
|
|
|
346
331
|
/**
|
|
347
|
-
* Controls ANSI terminal styling
|
|
348
|
-
* Create via {@link TypoSupport.none}, {@link TypoSupport.tty}, {@link TypoSupport.mock},
|
|
349
|
-
* or {@link TypoSupport.inferFromProcess}.
|
|
332
|
+
* Controls ANSI terminal styling. Create via the static factory methods.
|
|
350
333
|
*/
|
|
351
334
|
export class TypoSupport {
|
|
352
335
|
#kind: "none" | "tty" | "mock";
|
|
@@ -354,49 +337,50 @@ export class TypoSupport {
|
|
|
354
337
|
this.#kind = kind;
|
|
355
338
|
}
|
|
356
339
|
/**
|
|
357
|
-
*
|
|
340
|
+
* Plain text — no ANSI codes.
|
|
358
341
|
*/
|
|
359
342
|
static none(): TypoSupport {
|
|
360
343
|
return new TypoSupport("none");
|
|
361
344
|
}
|
|
362
345
|
/**
|
|
363
|
-
*
|
|
346
|
+
* ANSI escape codes for color terminals.
|
|
364
347
|
*/
|
|
365
348
|
static tty(): TypoSupport {
|
|
366
349
|
return new TypoSupport("tty");
|
|
367
350
|
}
|
|
368
351
|
/**
|
|
369
|
-
*
|
|
370
|
-
* Style flags appear as suffixes: `{text}@color`, `{text}+` (bold), `{text}-` (dim),
|
|
371
|
-
* `{text}*` (italic), `{text}_` (underline), `{text}~` (strikethrough).
|
|
352
|
+
* Deterministic textual styling for snapshot tests.
|
|
372
353
|
*/
|
|
373
354
|
static mock(): TypoSupport {
|
|
374
355
|
return new TypoSupport("mock");
|
|
375
356
|
}
|
|
376
357
|
/**
|
|
377
|
-
* Auto-detects styling mode from the process environment.
|
|
378
|
-
* `FORCE_COLOR=0` / `NO_COLOR` → none; `FORCE_COLOR` (truthy) / `isTTY` → tty; else → none.
|
|
379
|
-
* Falls back to none if `process` is unavailable.
|
|
358
|
+
* Auto-detects styling mode from the process environment on best-effort basis.
|
|
380
359
|
*/
|
|
381
|
-
static
|
|
382
|
-
if (!process) {
|
|
360
|
+
static inferFromEnv(): TypoSupport {
|
|
361
|
+
if (!process || !process.env) {
|
|
383
362
|
return TypoSupport.none();
|
|
384
363
|
}
|
|
385
|
-
|
|
386
|
-
if (process.env
|
|
387
|
-
return
|
|
388
|
-
}
|
|
389
|
-
if (process.env["FORCE_COLOR"]) {
|
|
390
|
-
return TypoSupport.tty();
|
|
391
|
-
}
|
|
392
|
-
if ("NO_COLOR" in process.env) {
|
|
393
|
-
return TypoSupport.none();
|
|
364
|
+
function readEnvVar(name: string) {
|
|
365
|
+
if (!(name in process.env)) {
|
|
366
|
+
return undefined;
|
|
394
367
|
}
|
|
368
|
+
return process.env[name];
|
|
395
369
|
}
|
|
396
|
-
|
|
397
|
-
|
|
370
|
+
const envForceColor = readEnvVar("FORCE_COLOR");
|
|
371
|
+
if (envForceColor === "0") {
|
|
372
|
+
return TypoSupport.none();
|
|
373
|
+
}
|
|
374
|
+
if (envForceColor !== undefined) {
|
|
375
|
+
TypoSupport.tty();
|
|
376
|
+
}
|
|
377
|
+
if (readEnvVar("NO_COLOR") !== undefined) {
|
|
378
|
+
return TypoSupport.none();
|
|
398
379
|
}
|
|
399
|
-
|
|
380
|
+
if (readEnvVar("MOCK_COLOR") !== undefined) {
|
|
381
|
+
return TypoSupport.mock();
|
|
382
|
+
}
|
|
383
|
+
return TypoSupport.tty();
|
|
400
384
|
}
|
|
401
385
|
/**
|
|
402
386
|
* Applies `typoStyle` to `value` according to the current mode.
|
|
@@ -406,8 +390,15 @@ export class TypoSupport {
|
|
|
406
390
|
* @returns Styled string.
|
|
407
391
|
*/
|
|
408
392
|
computeStyledString(value: string, typoStyle: TypoStyle): string {
|
|
393
|
+
let styledValue = value;
|
|
394
|
+
if (typoStyle.case === "upper") {
|
|
395
|
+
styledValue = styledValue.toUpperCase();
|
|
396
|
+
}
|
|
397
|
+
if (typoStyle.case === "lower") {
|
|
398
|
+
styledValue = styledValue.toLowerCase();
|
|
399
|
+
}
|
|
409
400
|
if (this.#kind === "none") {
|
|
410
|
-
return
|
|
401
|
+
return styledValue;
|
|
411
402
|
}
|
|
412
403
|
if (this.#kind === "tty") {
|
|
413
404
|
const fgColorCode = typoStyle.fgColor
|
|
@@ -423,12 +414,12 @@ export class TypoSupport {
|
|
|
423
414
|
const strikethroughCode = typoStyle.strikethrough
|
|
424
415
|
? ttyCodeStrikethrough
|
|
425
416
|
: "";
|
|
426
|
-
return `${fgColorCode}${bgColorCode}${boldCode}${dimCode}${italicCode}${underlineCode}${strikethroughCode}${
|
|
417
|
+
return `${fgColorCode}${bgColorCode}${boldCode}${dimCode}${italicCode}${underlineCode}${strikethroughCode}${styledValue}${ttyCodeReset}`;
|
|
427
418
|
}
|
|
428
419
|
if (this.#kind === "mock") {
|
|
429
420
|
const fgColorPart = typoStyle.fgColor
|
|
430
|
-
? `{${
|
|
431
|
-
:
|
|
421
|
+
? `{${styledValue}}@${typoStyle.fgColor}`
|
|
422
|
+
: styledValue;
|
|
432
423
|
const bgColorPart = typoStyle.bgColor
|
|
433
424
|
? `{${fgColorPart}}#${typoStyle.bgColor}`
|
|
434
425
|
: fgColorPart;
|