cli-kiss 0.2.7 → 0.2.8

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.
Files changed (39) hide show
  1. package/dist/index.d.ts +127 -137
  2. package/dist/index.js +2 -2
  3. package/dist/index.js.map +1 -1
  4. package/docs/.vitepress/config.mts +1 -1
  5. package/docs/.vitepress/theme/Layout.vue +16 -0
  6. package/docs/.vitepress/theme/index.ts +5 -1
  7. package/docs/.vitepress/theme/style.css +5 -1
  8. package/docs/guide/02_commands.md +1 -1
  9. package/docs/guide/03_options.md +11 -11
  10. package/docs/guide/05_input_types.md +9 -10
  11. package/docs/guide/06_run_as_cli.md +1 -1
  12. package/docs/index.md +2 -2
  13. package/docs/public/favicon.ico +0 -0
  14. package/docs/public/logo.png +0 -0
  15. package/package.json +1 -1
  16. package/src/index.ts +1 -1
  17. package/src/lib/Command.ts +45 -39
  18. package/src/lib/Operation.ts +28 -20
  19. package/src/lib/Option.ts +196 -127
  20. package/src/lib/Positional.ts +44 -23
  21. package/src/lib/Reader.ts +194 -226
  22. package/src/lib/Run.ts +19 -8
  23. package/src/lib/Suggest.ts +78 -0
  24. package/src/lib/Type.ts +36 -37
  25. package/src/lib/Typo.ts +58 -55
  26. package/src/lib/Usage.ts +12 -12
  27. package/tests/unit.Reader.commons.ts +92 -116
  28. package/tests/unit.Reader.parsings.ts +14 -26
  29. package/tests/unit.Reader.shortBig.ts +81 -96
  30. package/tests/unit.command.aliases.ts +100 -0
  31. package/tests/unit.command.execute.ts +1 -1
  32. package/tests/unit.command.usage.ts +12 -6
  33. package/tests/unit.fuzzed.alternatives.ts +35 -26
  34. package/tests/unit.runner.colors.ts +8 -33
  35. package/tests/unit.runner.cycle.ts +118 -146
  36. package/tests/unit.runner.errors.ts +25 -22
  37. package/docs/public/hero.png +0 -0
  38. package/src/lib/Similarity.ts +0 -41
  39. package/tests/unit.Reader.aliases.ts +0 -62
package/src/lib/Reader.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { similaritySort } from "./Similarity";
1
+ import { suggestTextPushMessage } from "./Suggest";
2
2
  import {
3
3
  TypoError,
4
4
  TypoString,
@@ -8,54 +8,55 @@ import {
8
8
  } from "./Typo";
9
9
 
10
10
  /**
11
- * Opaque key returned by {@link ReaderArgs.registerOption}.
12
11
  */
13
- export type ReaderOptionKey = (string | { __brand: "ReaderOptionKey" }) & {
14
- __brand: "ReaderOptionKey";
12
+ export type ReaderOptionLongSpec = {
13
+ key: string;
14
+ nextGuard: ReaderOptionNextGuard;
15
15
  };
16
16
 
17
17
  /**
18
- * Parsing behaviour for a registered option, passed to {@link ReaderArgs.registerOption}.
19
18
  */
20
- export type ReaderOptionParsing = {
21
- consumeShortGroup: boolean; // TODO - this doesnt matter when no short option
22
- consumeNextArg: (
23
- inlined: string | null,
24
- separated: Array<string>,
25
- nextArg: string | undefined,
26
- ) => boolean;
19
+ export type ReaderOptionShortSpec = {
20
+ key: string;
21
+ restGuard: ReaderOptionRestGuard;
22
+ nextGuard: ReaderOptionNextGuard;
23
+ };
24
+
25
+ /**
26
+ */
27
+ export type ReaderOptionRestGuard = (rest: string) => boolean;
28
+
29
+ /**
30
+ */
31
+ export type ReaderOptionNextGuard = (
32
+ value: ReaderOptionValue,
33
+ next: string | undefined,
34
+ ) => boolean;
35
+
36
+ /**
37
+ */
38
+ export type ReaderOptionResult = {
39
+ identifier: string;
40
+ values: ReadonlyArray<ReaderOptionValue>;
27
41
  };
28
42
 
29
43
  /**
30
- * Result of parsing an option, including its inlined value and any following separated values.
31
44
  */
32
45
  export type ReaderOptionValue = {
33
46
  inlined: string | null;
34
- separated: Array<string>;
47
+ separated: ReadonlyArray<string>;
35
48
  };
36
49
 
50
+ /**
51
+ */
52
+ export type ReaderOptionGetter = () => ReaderOptionResult;
53
+
37
54
  /**
38
55
  * Option registration/query interface. Subset of {@link ReaderArgs}.
39
56
  */
40
57
  export type ReaderOptions = {
41
- /**
42
- * Registers an option; all `longs` and `shorts` share the same key.
43
- *
44
- * @param definition.longs - Long-form names (without `--`).
45
- * @param definition.shorts - Short-form names (without `-`).
46
- * @param definition.parsing - Parsing behaviour.
47
- * @returns A {@link ReaderOptionKey} for later retrieval.
48
- * @throws `Error` if a name is already registered or short names overlap.
49
- */
50
- registerOption(definition: {
51
- longs: Array<string>;
52
- shorts: Array<string>;
53
- parsing: ReaderOptionParsing;
54
- }): ReaderOptionKey;
55
- /**
56
- * Returns all values collected for `key`.
57
- */
58
- getOptionValues(key: ReaderOptionKey): Array<ReaderOptionValue>;
58
+ registerOptionLong(longSpec: ReaderOptionLongSpec): ReaderOptionGetter;
59
+ registerOptionShort(shortSpec: ReaderOptionShortSpec): ReaderOptionGetter;
59
60
  };
60
61
 
61
62
  /**
@@ -66,7 +67,7 @@ export type ReaderPositionals = {
66
67
  * Returns the next positional token, parsing intervening options as side-effects.
67
68
  *
68
69
  * @returns The next positional, or `undefined` when exhausted.
69
- * @throws {@link TypoError} on an unrecognised option.
70
+ * @throws on an unrecognised option.
70
71
  */
71
72
  consumePositional(): string | undefined;
72
73
  };
@@ -81,102 +82,74 @@ export type ReaderPositionals = {
81
82
  * Created internally by {@link runAndExit}; exposed for advanced / custom runners.
82
83
  */
83
84
  export class ReaderArgs {
84
- #args: ReadonlyArray<string>;
85
+ #tokens: ReadonlyArray<string>;
85
86
  #parsedIndex: number;
86
87
  #parsedDouble: boolean;
87
- #optionContextByLong: Map<string, ReaderOptionContext>;
88
- #optionContextByShort: Map<string, ReaderOptionContext>;
89
- #optionContextByKey: Map<ReaderOptionKey, ReaderOptionContext>;
88
+ #optionLongContextByIdentifier: Map<string, Context<ReaderOptionLongSpec>>;
89
+ #optionShortContextByIdentifier: Map<string, Context<ReaderOptionShortSpec>>;
90
90
 
91
91
  /**
92
- * @param args - Raw CLI tokens (e.g. `process.argv.slice(2)`). Not mutated.
92
+ * @param tokens - Raw CLI tokens (e.g. `process.argv.slice(2)`).
93
93
  */
94
- constructor(args: ReadonlyArray<string>) {
95
- this.#args = args;
94
+ constructor(tokens: ReadonlyArray<string>) {
95
+ this.#tokens = tokens;
96
96
  this.#parsedIndex = 0;
97
97
  this.#parsedDouble = false;
98
- this.#optionContextByLong = new Map();
99
- this.#optionContextByShort = new Map();
100
- this.#optionContextByKey = new Map();
98
+ this.#optionLongContextByIdentifier = new Map();
99
+ this.#optionShortContextByIdentifier = new Map();
101
100
  }
102
101
 
103
102
  /**
104
- * Registers an option; all `longs` and `shorts` share the same key.
105
- * Short names must not be prefixes of one another.
106
- *
107
- * @param definition.longs - Long-form names (without `--`).
108
- * @param definition.shorts - Short-form names (without `-`).
109
- * @param definition.parsing - Parsing behaviour.
110
- * @returns A {@link ReaderOptionKey} for {@link ReaderArgs.getOptionValues}.
111
- * @throws `Error` if any name is already registered or short names overlap.
112
103
  */
113
- registerOption(definition: {
114
- longs: Array<string>;
115
- shorts: Array<string>;
116
- parsing: ReaderOptionParsing;
117
- }) {
118
- const key = [
119
- ...definition.longs.map((long) => `--${long}`),
120
- ...definition.shorts.map((short) => `-${short}`),
121
- ].join(", ") as ReaderOptionKey;
122
- for (const long of definition.longs) {
123
- if (!this.#isValidOptionName(long)) {
124
- throw new Error(`Invalid option name: --${long}`);
125
- }
126
- if (this.#optionContextByLong.has(long)) {
127
- throw new Error(`Option already registered: --${long}`);
128
- }
104
+ registerOptionLong(longSpec: ReaderOptionLongSpec): ReaderOptionGetter {
105
+ const identifier = `--${longSpec.key}`;
106
+ if (!isValidOptionKey(longSpec.key)) {
107
+ throw new Error(`Option identifier is invalid: ${identifier}.`);
129
108
  }
130
- for (const short of definition.shorts) {
131
- if (!this.#isValidOptionName(short)) {
132
- throw new Error(`Invalid option name: -${short}`);
133
- }
134
- if (this.#optionContextByShort.has(short)) {
135
- throw new Error(`Option already registered: -${short}`);
136
- }
137
- for (let i = 0; i < short.length; i++) {
138
- const shortSlice = short.slice(0, i);
139
- if (this.#optionContextByShort.has(shortSlice)) {
140
- throw new Error(
141
- `Option -${short} overlap with shorter option: -${shortSlice}`,
142
- );
143
- }
144
- }
145
- for (const shortOther of this.#optionContextByShort.keys()) {
146
- if (shortOther.startsWith(short)) {
147
- throw new Error(
148
- `Option -${short} overlap with longer option: -${shortOther}`,
149
- );
150
- }
151
- }
109
+ if (this.#optionLongContextByIdentifier.has(identifier)) {
110
+ throw new Error(`Option already registered: ${identifier}.`);
152
111
  }
153
- const optionContext = {
154
- parsing: definition.parsing,
155
- results: new Array<ReaderOptionValue>(),
156
- };
157
- for (const long of definition.longs) {
158
- this.#optionContextByLong.set(long, optionContext);
159
- }
160
- for (const short of definition.shorts) {
161
- this.#optionContextByShort.set(short, optionContext);
162
- }
163
- this.#optionContextByKey.set(key, optionContext);
164
- return key;
112
+ const values = new Array<ReaderOptionValue>();
113
+ this.#optionLongContextByIdentifier.set(identifier, {
114
+ identifier,
115
+ spec: longSpec,
116
+ values,
117
+ });
118
+ return () => ({ identifier, values });
165
119
  }
166
120
 
167
121
  /**
168
- * Returns all values collected for `key`.
169
- *
170
- * @param key - Key from {@link ReaderArgs.registerOption}.
171
- * @returns One entry per occurrence.
172
- * @throws `Error` if `key` was not registered.
173
122
  */
174
- getOptionValues(key: ReaderOptionKey): Array<ReaderOptionValue> {
175
- const optionContext = this.#optionContextByKey.get(key);
176
- if (optionContext === undefined) {
177
- throw new Error(`Unregistered option: ${key}`);
123
+ registerOptionShort(shortSpec: ReaderOptionShortSpec): ReaderOptionGetter {
124
+ const identifier = `-${shortSpec.key}`;
125
+ if (!isValidOptionKey(shortSpec.key)) {
126
+ throw new Error(`Option identifier is invalid: ${identifier}.`);
178
127
  }
179
- return optionContext.results;
128
+ if (this.#optionShortContextByIdentifier.has(identifier)) {
129
+ throw new Error(`Option already registered: ${identifier}.`);
130
+ }
131
+ for (let i = 0; i < identifier.length; i++) {
132
+ const slicedIdentifier = identifier.slice(0, 1 + i);
133
+ if (this.#optionShortContextByIdentifier.has(slicedIdentifier)) {
134
+ throw new Error(
135
+ `Option ${identifier} overlap with shorter option: ${slicedIdentifier}.`,
136
+ );
137
+ }
138
+ }
139
+ for (const otherIdentifier of this.#optionShortContextByIdentifier.keys()) {
140
+ if (otherIdentifier.startsWith(identifier)) {
141
+ throw new Error(
142
+ `Option ${identifier} overlap with longer option: ${otherIdentifier}.`,
143
+ );
144
+ }
145
+ }
146
+ const values = new Array<ReaderOptionValue>();
147
+ this.#optionShortContextByIdentifier.set(identifier, {
148
+ identifier,
149
+ spec: shortSpec,
150
+ values,
151
+ });
152
+ return () => ({ identifier, values });
180
153
  }
181
154
 
182
155
  /**
@@ -184,186 +157,181 @@ export class ReaderArgs {
184
157
  * All tokens after `--` are treated as positionals.
185
158
  *
186
159
  * @returns The next positional, or `undefined` when exhausted.
187
- * @throws {@link TypoError} on an unrecognised option.
160
+ * @throws on an unrecognised option.
188
161
  */
189
162
  consumePositional(): string | undefined {
190
163
  while (true) {
191
- const arg = this.#consumeArg();
192
- if (arg === undefined) {
164
+ const token = this.#consumeToken();
165
+ if (token === undefined) {
193
166
  return undefined;
194
167
  }
195
- if (!this.#tryConsumeAsOption(arg)) {
196
- return arg;
168
+ if (!this.#tryConsumeAsOption(token)) {
169
+ return token;
197
170
  }
198
171
  }
199
172
  }
200
173
 
201
- #consumeArg(): string | undefined {
202
- const arg = this.#args[this.#parsedIndex];
203
- if (arg === undefined) {
174
+ #consumeToken(): string | undefined {
175
+ const token = this.#tokens[this.#parsedIndex];
176
+ if (token === undefined) {
204
177
  return undefined;
205
178
  }
206
179
  this.#parsedIndex++;
207
180
  if (!this.#parsedDouble) {
208
- if (arg === "--") {
181
+ if (token === "--") {
209
182
  this.#parsedDouble = true;
210
- return this.#consumeArg();
183
+ return this.#consumeToken();
211
184
  }
212
185
  }
213
- return arg;
186
+ return token;
214
187
  }
215
188
 
216
- #tryConsumeAsOption(arg: string): boolean {
189
+ #tryConsumeAsOption(token: string): boolean {
217
190
  if (this.#parsedDouble) {
218
191
  return false;
219
192
  }
220
- if (arg.startsWith("--")) {
221
- const valueIndexStart = arg.indexOf("=");
193
+ if (token.startsWith("--")) {
194
+ const valueIndexStart = token.indexOf("=");
222
195
  if (valueIndexStart === -1) {
223
- this.#consumeOptionLong(arg.slice(2), null);
196
+ this.#consumeOptionLong(token, null);
224
197
  } else {
225
198
  this.#consumeOptionLong(
226
- arg.slice(2, valueIndexStart),
227
- arg.slice(valueIndexStart + 1),
199
+ token.slice(0, valueIndexStart),
200
+ token.slice(valueIndexStart + 1),
228
201
  );
229
202
  }
230
203
  return true;
231
204
  }
232
- if (arg.startsWith("-")) {
205
+ if (token.startsWith("-")) {
233
206
  let shortIndexStart = 1;
234
207
  let shortIndexEnd = 2;
235
- while (shortIndexEnd <= arg.length) {
236
- const short = arg.slice(shortIndexStart, shortIndexEnd);
237
- const optionContext = this.#optionContextByShort.get(short);
238
- if (optionContext !== undefined) {
239
- const rest = arg.slice(shortIndexEnd);
240
- if (this.#tryConsumeOptionShort(optionContext, short, rest)) {
208
+ while (shortIndexEnd <= token.length) {
209
+ const identifier = `-${token.slice(shortIndexStart, shortIndexEnd)}`;
210
+ const shortContext =
211
+ this.#optionShortContextByIdentifier.get(identifier);
212
+ if (shortContext !== undefined) {
213
+ const tokenRest = token.slice(shortIndexEnd);
214
+ if (this.#tryConsumeOptionShort(shortContext, tokenRest)) {
241
215
  return true;
242
216
  }
243
217
  shortIndexStart = shortIndexEnd;
244
218
  }
245
219
  shortIndexEnd++;
246
220
  }
247
- this.#throwUnknownOptionError(`-${arg.slice(shortIndexStart)}`);
221
+ this.#throwUnknownOptionError(`-${token.slice(shortIndexStart)}`);
248
222
  }
249
223
  return false;
250
224
  }
251
225
 
252
- #consumeOptionLong(long: string, inlined: string | null): void {
253
- const constant = `--${long}`;
254
- const optionContext = this.#optionContextByLong.get(long);
255
- if (optionContext !== undefined) {
256
- return this.#consumeOptionValues(optionContext, constant, inlined);
226
+ #consumeOptionLong(identifier: string, valueInlined: string | null): void {
227
+ const longContext = this.#optionLongContextByIdentifier.get(identifier);
228
+ if (longContext !== undefined) {
229
+ return this.#consumeOptionValues(longContext, valueInlined);
257
230
  }
258
- this.#throwUnknownOptionError(constant);
231
+ this.#throwUnknownOptionError(identifier);
259
232
  }
260
233
 
261
234
  #tryConsumeOptionShort(
262
- optionContext: ReaderOptionContext,
263
- short: string,
264
- rest: string,
235
+ shortContext: Context<ReaderOptionShortSpec>,
236
+ tokenRest: string,
265
237
  ): boolean {
266
- const constant = `-${short}`;
267
- if (rest.startsWith("=")) {
268
- this.#consumeOptionValues(optionContext, constant, rest.slice(1));
238
+ if (tokenRest.startsWith("=")) {
239
+ this.#consumeOptionValues(shortContext, tokenRest.slice(1));
269
240
  return true;
270
241
  }
271
- if (rest.length === 0) {
272
- this.#consumeOptionValues(optionContext, constant, null);
242
+ if (tokenRest.length === 0) {
243
+ this.#consumeOptionValues(shortContext, null);
273
244
  return true;
274
245
  }
275
- if (optionContext.parsing.consumeShortGroup) {
276
- this.#consumeOptionValues(optionContext, constant, rest);
246
+ if (shortContext.spec.restGuard(tokenRest)) {
247
+ this.#consumeOptionValues(shortContext, tokenRest);
277
248
  return true;
278
249
  }
279
- this.#consumeOptionValues(optionContext, constant, null);
250
+ this.#consumeOptionValues(shortContext, null);
280
251
  return false;
281
252
  }
282
253
 
283
254
  #consumeOptionValues(
284
- optionContext: ReaderOptionContext,
285
- constant: string,
286
- inlined: string | null,
255
+ context: Context<{ nextGuard: ReaderOptionNextGuard }>,
256
+ valueInlined: string | null,
287
257
  ) {
288
- const separated = new Array<string>();
289
- while (
290
- optionContext.parsing.consumeNextArg(
291
- inlined,
292
- separated,
293
- this.#args[this.#parsedIndex],
294
- )
295
- ) {
296
- separated.push(this.#consumeOptionValue(constant));
258
+ const value = { inlined: valueInlined, separated: new Array<string>() };
259
+ const { identifier, values, spec } = context;
260
+ values.push(value);
261
+ while (true) {
262
+ const nextToken = this.#tokens[this.#parsedIndex];
263
+ if (!spec.nextGuard(value, nextToken)) {
264
+ return;
265
+ }
266
+ const token = this.#consumeToken();
267
+ if (this.#parsedDouble) {
268
+ throw new TypoError(
269
+ new TypoText(
270
+ new TypoString(identifier, typoStyleConstants),
271
+ new TypoString(`: Requires a value but got: `),
272
+ new TypoString(`"--"`, typoStyleQuote),
273
+ new TypoString(`.`),
274
+ ),
275
+ );
276
+ }
277
+ // TODO - should we allow consuming the EOF token ?
278
+ if (token === undefined) {
279
+ throw new TypoError(
280
+ new TypoText(
281
+ new TypoString(identifier, typoStyleConstants),
282
+ new TypoString(`: Requires a value, but got end of input.`), // TODO - hint at option value syntax ?
283
+ ),
284
+ );
285
+ }
286
+ // TODO - is that weird, could a valid value start with dash ?
287
+ if (token.startsWith("-")) {
288
+ throw new TypoError(
289
+ new TypoText(
290
+ new TypoString(identifier, typoStyleConstants),
291
+ new TypoString(`: Requires a value, but got: `),
292
+ new TypoString(`"${token}"`, typoStyleQuote),
293
+ new TypoString(`.`),
294
+ ),
295
+ );
296
+ }
297
+ value.separated.push(token);
297
298
  }
298
- optionContext.results.push({ inlined, separated });
299
299
  }
300
300
 
301
- #consumeOptionValue(constant: string): string {
302
- const arg = this.#consumeArg();
303
- if (arg === undefined) {
304
- throw new TypoError(
305
- new TypoText(
306
- new TypoString(constant, typoStyleConstants),
307
- new TypoString(`: Requires a value, but got end of input`),
308
- ),
309
- );
301
+ #throwUnknownOptionError(inputIdentifier: string): never {
302
+ const candidatesIdentifiers = [];
303
+ for (const identifier of this.#optionLongContextByIdentifier.keys()) {
304
+ candidatesIdentifiers.push(identifier);
310
305
  }
311
- if (this.#parsedDouble) {
312
- throw new TypoError(
313
- new TypoText(
314
- new TypoString(constant, typoStyleConstants),
315
- new TypoString(`: Requires a value before `),
316
- new TypoString(`"--"`, typoStyleQuote),
317
- ),
318
- );
306
+ for (const identifier of this.#optionShortContextByIdentifier.keys()) {
307
+ candidatesIdentifiers.push(identifier);
319
308
  }
320
- // TODO - is that weird, could a valid value start with dash ?
321
- if (arg.startsWith("-")) {
322
- throw new TypoError(
323
- new TypoText(
324
- new TypoString(constant, typoStyleConstants),
325
- new TypoString(`: Requires a value, but got: `),
326
- new TypoString(`"${arg}"`, typoStyleQuote),
327
- ),
309
+ const errorText = new TypoText();
310
+ errorText.push(new TypoString(`Unknown option: `));
311
+ errorText.push(new TypoString(`"${inputIdentifier}"`, typoStyleQuote));
312
+ if (candidatesIdentifiers.length === 0) {
313
+ errorText.push(new TypoString(`, no options are registered.`));
314
+ } else {
315
+ errorText.push(new TypoString(`.`));
316
+ suggestTextPushMessage(
317
+ errorText,
318
+ inputIdentifier,
319
+ candidatesIdentifiers.map((candidateIdentifier) => ({
320
+ reference: candidateIdentifier,
321
+ hint: new TypoString(candidateIdentifier, typoStyleConstants),
322
+ })),
328
323
  );
329
324
  }
330
- return arg;
331
- }
332
-
333
- #isValidOptionName(name: string): boolean {
334
- return name.length > 0 && !name.includes("=") && !name.includes("\0");
325
+ throw new TypoError(errorText);
335
326
  }
327
+ }
336
328
 
337
- #throwUnknownOptionError(constant: string): never {
338
- const candidatesConstants = [];
339
- for (const optionLong of this.#optionContextByLong.keys()) {
340
- candidatesConstants.push(`--${optionLong}`);
341
- }
342
- for (const optionShort of this.#optionContextByShort.keys()) {
343
- candidatesConstants.push(`-${optionShort}`);
344
- }
345
- const text = new TypoText();
346
- text.push(new TypoString(`Unknown option: `));
347
- text.push(new TypoString(`"${constant}"`, typoStyleQuote));
348
- if (candidatesConstants.length > 0) {
349
- const suggestionsConstants = similaritySort(
350
- constant,
351
- candidatesConstants.map((candidateConstant) => ({
352
- key: candidateConstant,
353
- value: new TypoString(candidateConstant, typoStyleConstants),
354
- })),
355
- ).slice(0, 3);
356
- text.push(new TypoString(`: did you mean: `));
357
- text.push(TypoText.join(suggestionsConstants, new TypoString(`, `)));
358
- text.push(new TypoString(` ?`));
359
- } else {
360
- text.push(new TypoString(`, no options are registered`));
361
- }
362
- throw new TypoError(text);
363
- }
329
+ function isValidOptionKey(name: string): boolean {
330
+ return name.length > 0 && !name.includes("=") && !name.includes("\0");
364
331
  }
365
332
 
366
- type ReaderOptionContext = {
367
- parsing: ReaderOptionParsing;
368
- results: Array<ReaderOptionValue>;
333
+ type Context<Spec> = {
334
+ identifier: string;
335
+ spec: Spec;
336
+ values: Array<ReaderOptionValue>;
369
337
  };
package/src/lib/Run.ts CHANGED
@@ -75,9 +75,9 @@ export async function runAndExit<Context>(
75
75
  if (colorSetup === "flag") {
76
76
  const colorOption = optionSingleValue<"auto" | RunColorMode>({
77
77
  long: "color",
78
- type: typeChoice("color-mode", ["auto", "always", "never", "mock"]),
79
- defaultIfNotSpecified: () => "auto",
80
- valueIfNothingInlined: () => "always",
78
+ type: typeChoice("color-mode", ["auto", "always", "never"]),
79
+ fallbackValueIfAbsent: () => "auto",
80
+ impliedValueIfNotInlined: () => "always",
81
81
  }).registerAndMakeDecoder(readerArgs);
82
82
  preprocessors.push(() => {
83
83
  try {
@@ -103,7 +103,7 @@ export async function runAndExit<Context>(
103
103
  return 0;
104
104
  });
105
105
  }
106
- if (options?.buildVersion) {
106
+ if (options?.buildVersion !== undefined) {
107
107
  const versionOption = optionFlag({
108
108
  long: "version",
109
109
  }).registerAndMakeDecoder(readerArgs);
@@ -115,6 +115,8 @@ export async function runAndExit<Context>(
115
115
  return 0;
116
116
  });
117
117
  }
118
+ // TODO - the lifecycle of this function should be improved
119
+ // TODO - how to pass the color information to the command logic ?
118
120
  /*
119
121
  // TODO - handle completions ?
120
122
  readerArgs.registerFlag({
@@ -145,27 +147,36 @@ export async function runAndExit<Context>(
145
147
  await commandInterpreter.executeWithContext(context);
146
148
  return onExit(0);
147
149
  } catch (executionError) {
148
- handleError(options?.onError, executionError, typoSupport);
150
+ handleError(cliName, options?.onError, executionError, typoSupport);
149
151
  return onExit(1);
150
152
  }
151
153
  } catch (parsingError) {
152
154
  if (options?.usageOnError ?? true) {
153
155
  console.error(computeUsageString(cliName, commandDecoder, typoSupport));
154
156
  }
155
- handleError(options?.onError, parsingError, typoSupport);
157
+ handleError(cliName, options?.onError, parsingError, typoSupport);
156
158
  return onExit(1);
157
159
  }
158
160
  }
159
161
 
160
162
  function handleError(
163
+ _cliName: string,
161
164
  onError: ((error: unknown) => void) | undefined,
162
165
  error: unknown,
163
166
  typoSupport: TypoSupport,
164
167
  ) {
168
+ // TODO - should the cliName be part of the error message for logs ?
169
+ const finalError = error;
170
+ /*
171
+ const finalError = new TypoError(
172
+ new TypoText(new TypoString(cliName, typoStyleConstants)),
173
+ error,
174
+ );
175
+ */
165
176
  if (onError !== undefined) {
166
- onError(error);
177
+ onError(finalError);
167
178
  } else {
168
- console.error(typoSupport.computeStyledErrorMessage(error));
179
+ console.error(typoSupport.computeStyledErrorMessage(finalError));
169
180
  }
170
181
  }
171
182