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