argot-cli 0.0.0-stage → 0.4.0-beta
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 +14 -0
- package/LICENSE +13 -0
- package/README.md +2 -2
- package/package.json +23 -4
- package/src/arg_parser.ts +586 -0
- package/src/errors.ts +272 -0
- package/src/mod.ts +49 -0
- package/src/parser_config.ts +104 -0
- package/src/read_config.ts +14 -0
- package/src/types.ts +262 -0
- package/src/utils.ts +219 -0
- package/tsconfig.json +35 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.3.0 -- 2026-04-23
|
|
4
|
+
- Added new error types for clearer distinction between runtime and
|
|
5
|
+
configuration errors.
|
|
6
|
+
- Deprecated `LabeledEntry`; option names should now be defined as object keys
|
|
7
|
+
in configuration.
|
|
8
|
+
|
|
9
|
+
## v0.2.0 -- 2026-02-08
|
|
10
|
+
- Added filesystem support for Node.js and Bun runtimes.
|
|
11
|
+
|
|
12
|
+
## v0.1.0 -- 2026-02-04
|
|
13
|
+
- Implemented `argot-cli`, a command-line interface argument parser in
|
|
14
|
+
TypeScript. As of its first publication, it is only compatible with Deno.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2026 Lucas L. S. Haine
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
9
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
11
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
12
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
13
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# argot-cli
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This is a command-line argument parser with a simple configuration format.
|
package/package.json
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argot-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0-beta",
|
|
4
|
+
"description": "ArgotCLI is a command-line argument parser with a simple configuration format.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/llucasls/argot-ts.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/llucasls/argot-ts/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/llucasls/argot-ts#readme",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^26.6.4",
|
|
20
|
+
"typescript": "^7.0.2"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./dist/mod.js"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AliasEntry,
|
|
3
|
+
AliasType,
|
|
4
|
+
ConfigEntry,
|
|
5
|
+
FloatEntry,
|
|
6
|
+
IntEntry,
|
|
7
|
+
ListEntry,
|
|
8
|
+
OptionType,
|
|
9
|
+
OptionValue,
|
|
10
|
+
ParseResult,
|
|
11
|
+
TextEntry,
|
|
12
|
+
} from './types.ts';
|
|
13
|
+
import { ParserConfig } from './parser_config.ts';
|
|
14
|
+
import { Options, Parameters, Operands } from './types.ts';
|
|
15
|
+
import {
|
|
16
|
+
NullArgError,
|
|
17
|
+
NullFloatError,
|
|
18
|
+
NullIntError,
|
|
19
|
+
UnknownOptionError,
|
|
20
|
+
} from './errors.ts';
|
|
21
|
+
import { parseFloatStrict, parseIntStrict } from './utils.ts';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Command-line argument parser.
|
|
25
|
+
* The parser processes an array of strings according to a
|
|
26
|
+
* configuration and produces a structured result.
|
|
27
|
+
* Parsing follows UNIX-style short options and GNU-style long
|
|
28
|
+
* options.
|
|
29
|
+
*/
|
|
30
|
+
export class ArgParser {
|
|
31
|
+
private longOptExp: RegExp = /^--/;
|
|
32
|
+
private shortOptExp: RegExp = /^-[^-]/;
|
|
33
|
+
private assignmentExp: RegExp = /^--([^=]+)=(.+)?/d;
|
|
34
|
+
private parameterExp: RegExp = /^([^=]+)=(.+)?/d;
|
|
35
|
+
private configs: ParserConfig;
|
|
36
|
+
|
|
37
|
+
constructor(configs: ParserConfig) {
|
|
38
|
+
if (!(configs instanceof ParserConfig)) {
|
|
39
|
+
throw new TypeError('input value must be an instance of ParserConfig');
|
|
40
|
+
}
|
|
41
|
+
this.configs = configs;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse an array of command-line arguments.
|
|
46
|
+
* Arguments are processed from left to right. Each argument is
|
|
47
|
+
* classified as one of:
|
|
48
|
+
* - option: matches a configured short or long option
|
|
49
|
+
* - parameter: of the form "key=value"
|
|
50
|
+
* - operand: any other argument
|
|
51
|
+
* Parsing rules:
|
|
52
|
+
* - The literal "--" stops option parsing. All subsequent
|
|
53
|
+
* arguments are treated as operands.
|
|
54
|
+
* - Short options may be combined (e.g. "-abc").
|
|
55
|
+
* - If a short option that accepts an associated value is not the
|
|
56
|
+
* last character in a group, the remainder of the argument is
|
|
57
|
+
* used as its value (e.g. "-n10").
|
|
58
|
+
* - If a short option that accepts an associated value appears as
|
|
59
|
+
* the last character:
|
|
60
|
+
* - If the option has a default value, no additional argument
|
|
61
|
+
* is consumed.
|
|
62
|
+
* - Otherwise, the next argument is used as its value.
|
|
63
|
+
* - Long options must be provided in the form "--name" if they do
|
|
64
|
+
* not accept an associated value, or "--name=value" if they do.
|
|
65
|
+
* - Long options that accept an associated value never consume
|
|
66
|
+
* the next argument.
|
|
67
|
+
* - Parameters are parsed from arguments matching "key=value" and
|
|
68
|
+
* are stored separately from options.
|
|
69
|
+
* - If an option is repeated:
|
|
70
|
+
* - count and list options accumulate values
|
|
71
|
+
* - other options overwrite previous values
|
|
72
|
+
* - Alias options store their values under the target option's
|
|
73
|
+
* name.
|
|
74
|
+
* Returns:
|
|
75
|
+
* - options: parsed option values
|
|
76
|
+
* - parameters: key/value assignments
|
|
77
|
+
* - operands: positional arguments
|
|
78
|
+
* Throws:
|
|
79
|
+
* - UnknownOptionError if an unrecognized option is encountered
|
|
80
|
+
* - NullArgError or NullIntError if a required value is missing
|
|
81
|
+
* - InvalidIntError or UnsafeIntegerError for invalid numeric values
|
|
82
|
+
*/
|
|
83
|
+
public parse(argList: string[]): Readonly<ParseResult> {
|
|
84
|
+
// options: parsed option values
|
|
85
|
+
const options = new Options();
|
|
86
|
+
|
|
87
|
+
// parameters: name=value variable assignments
|
|
88
|
+
const parameters = new Parameters();
|
|
89
|
+
|
|
90
|
+
// operands: command-line positional arguments
|
|
91
|
+
const operands = new Operands();
|
|
92
|
+
|
|
93
|
+
if (!Array.isArray(argList)) {
|
|
94
|
+
throw new TypeError(`argList must be an array of strings`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let i = 0, n = argList.length; i < n; i++) {
|
|
98
|
+
if (typeof argList[i] !== 'string') {
|
|
99
|
+
throw new TypeError(`argList must be an array of strings`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let stopParsing = false;
|
|
104
|
+
|
|
105
|
+
for (let i = 0, n = argList.length; i < n; i++) {
|
|
106
|
+
const arg: string = argList[i]!;
|
|
107
|
+
|
|
108
|
+
if (stopParsing) {
|
|
109
|
+
operands.push(arg);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (arg === '--') {
|
|
114
|
+
stopParsing = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
if (this.longOptExp.test(arg)) {
|
|
120
|
+
const {
|
|
121
|
+
name,
|
|
122
|
+
value,
|
|
123
|
+
shouldSkip
|
|
124
|
+
} = this.parseLongOption(arg, argList[i+1] ?? null);
|
|
125
|
+
if (this.configs.get(name)?.type === 'count') {
|
|
126
|
+
const oldValue = options.get(name) as number ?? 0;
|
|
127
|
+
const newValue = value as number;
|
|
128
|
+
options.set(name, oldValue + newValue);
|
|
129
|
+
} else if (this.configs.get(name)?.type === 'list') {
|
|
130
|
+
const oldValue = options.get(name) as string[] ?? [];
|
|
131
|
+
const newValue = value as string[];
|
|
132
|
+
options.set(name, oldValue.concat(newValue));
|
|
133
|
+
} else {
|
|
134
|
+
options.set(name, value);
|
|
135
|
+
}
|
|
136
|
+
if (shouldSkip)
|
|
137
|
+
i += 1;
|
|
138
|
+
} else if (this.shortOptExp.test(arg)) {
|
|
139
|
+
const [
|
|
140
|
+
shouldSkip,
|
|
141
|
+
pairs,
|
|
142
|
+
] = this.parseShortOption(arg, argList[i+1] ?? null);
|
|
143
|
+
for (const [name, value] of Object.entries(pairs)) {
|
|
144
|
+
if (this.configs.get(name)?.type === 'count') {
|
|
145
|
+
const oldValue = options.get(name) as number ?? 0;
|
|
146
|
+
const newValue = value as number;
|
|
147
|
+
options.set(name, oldValue + newValue);
|
|
148
|
+
} else if (this.configs.get(name)?.type === 'list') {
|
|
149
|
+
const oldValue = options.get(name) as string[] ?? [];
|
|
150
|
+
const newValue = value as string[];
|
|
151
|
+
options.set(name, oldValue.concat(newValue));
|
|
152
|
+
} else {
|
|
153
|
+
options.set(name, value);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (shouldSkip)
|
|
157
|
+
i += 1;
|
|
158
|
+
} else if (this.configs.parseParameters && this.parameterExp.test(arg)) {
|
|
159
|
+
const [, name, value] = this.parameterExp.exec(arg)!;
|
|
160
|
+
parameters.set(name!, value ?? '');
|
|
161
|
+
} else {
|
|
162
|
+
operands.push(arg);
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (!(err instanceof UnknownOptionError) || !this.configs.allowUnknown)
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
options.freeze();
|
|
171
|
+
parameters.freeze();
|
|
172
|
+
Object.freeze(operands);
|
|
173
|
+
|
|
174
|
+
if (this.configs.parseParameters) {
|
|
175
|
+
const result = { options, parameters, operands } as const;
|
|
176
|
+
return Object.freeze(result);
|
|
177
|
+
} else {
|
|
178
|
+
const result = { options, operands } as const;
|
|
179
|
+
return Object.freeze(result);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public slice(argList: string[], index: number): [string[], string[]] {
|
|
184
|
+
let i: number;
|
|
185
|
+
let operands = 0;
|
|
186
|
+
let stopParsing = false;
|
|
187
|
+
for (i = 0; i < argList.length; i++) {
|
|
188
|
+
if (operands >= index)
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
const arg: string = argList[i]!;
|
|
192
|
+
if (arg === '--' && !stopParsing) {
|
|
193
|
+
stopParsing = true;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (stopParsing) {
|
|
198
|
+
operands += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
if (this.longOptExp.test(arg)) {
|
|
204
|
+
const { shouldSkip } = this.parseLongOption(arg, argList[i+1] ?? null);
|
|
205
|
+
if (shouldSkip)
|
|
206
|
+
i += 1;
|
|
207
|
+
} else if (this.shortOptExp.test(arg)) {
|
|
208
|
+
const [shouldSkip,] = this.parseShortOption(arg, argList[i+1] ?? null);
|
|
209
|
+
if (shouldSkip)
|
|
210
|
+
i += 1;
|
|
211
|
+
} else if (this.configs.parseParameters && this.parameterExp.test(arg)) {
|
|
212
|
+
continue;
|
|
213
|
+
} else {
|
|
214
|
+
operands += 1;
|
|
215
|
+
}
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (!(err instanceof UnknownOptionError) || !this.configs.allowUnknown)
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return [argList.slice(0, i), argList.slice(i)];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private parseLongOption(arg: string, nextArg: string | null): {
|
|
225
|
+
name: string;
|
|
226
|
+
value: OptionValue;
|
|
227
|
+
shouldSkip: boolean;
|
|
228
|
+
} {
|
|
229
|
+
const offset = 2;
|
|
230
|
+
|
|
231
|
+
let name: string;
|
|
232
|
+
let value: string | null;
|
|
233
|
+
try {
|
|
234
|
+
const [, _name, _value] = this.assignmentExp.exec(arg)!;
|
|
235
|
+
name = _name!;
|
|
236
|
+
value = _value ?? '';
|
|
237
|
+
} catch {
|
|
238
|
+
name = arg.slice(offset);
|
|
239
|
+
value = null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const entry: ConfigEntry = this.configs.get(name)!;
|
|
243
|
+
if (entry == null) {
|
|
244
|
+
throw new UnknownOptionError(name);
|
|
245
|
+
}
|
|
246
|
+
const tag: OptionType = entry.type;
|
|
247
|
+
|
|
248
|
+
switch (tag) {
|
|
249
|
+
case 'flag': {
|
|
250
|
+
return { name, value: true, shouldSkip: false };
|
|
251
|
+
}
|
|
252
|
+
case 'text': {
|
|
253
|
+
if (value != null) {
|
|
254
|
+
return { name, value, shouldSkip: false };
|
|
255
|
+
} else if (Object.hasOwn(entry, 'default')) {
|
|
256
|
+
const { default: value } = entry as TextEntry;
|
|
257
|
+
return { name, value: value!, shouldSkip: false };
|
|
258
|
+
} else if (nextArg != null) {
|
|
259
|
+
return { name, value: nextArg, shouldSkip: true };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
throw new NullArgError(name);
|
|
263
|
+
}
|
|
264
|
+
case 'int': {
|
|
265
|
+
if (value != null) {
|
|
266
|
+
return { name, value: parseIntStrict(value), shouldSkip: false };
|
|
267
|
+
} else if (Object.hasOwn(entry, 'default')) {
|
|
268
|
+
const { default: value } = entry as IntEntry;
|
|
269
|
+
return { name, value: value!, shouldSkip: false };
|
|
270
|
+
} else if (nextArg != null) {
|
|
271
|
+
return { name, value: parseIntStrict(nextArg), shouldSkip: true };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
throw new NullIntError(name);
|
|
275
|
+
}
|
|
276
|
+
case 'float': {
|
|
277
|
+
if (value != null) {
|
|
278
|
+
return { name, value: parseFloatStrict(value), shouldSkip: false };
|
|
279
|
+
} else if (Object.hasOwn(entry, 'default')) {
|
|
280
|
+
const { default: value } = entry as FloatEntry;
|
|
281
|
+
return { name, value: value!, shouldSkip: false };
|
|
282
|
+
} else if (nextArg != null) {
|
|
283
|
+
return { name, value: parseFloatStrict(nextArg), shouldSkip: true };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
throw new NullFloatError(name);
|
|
287
|
+
}
|
|
288
|
+
case 'count': {
|
|
289
|
+
if (value != null)
|
|
290
|
+
return { name, value: parseIntStrict(value), shouldSkip: false };
|
|
291
|
+
return { name, value: 1, shouldSkip: false };
|
|
292
|
+
}
|
|
293
|
+
case 'list': {
|
|
294
|
+
if (value === '') {
|
|
295
|
+
return { name, value: [], shouldSkip: false };
|
|
296
|
+
} else if (value != null) {
|
|
297
|
+
const { sep } = entry as ListEntry;
|
|
298
|
+
return { name, value: value.split(sep ?? ','), shouldSkip: false };
|
|
299
|
+
} else if (nextArg != null) {
|
|
300
|
+
const { sep } = entry as ListEntry;
|
|
301
|
+
return { name, value: nextArg.split(sep ?? ','), shouldSkip: true };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
throw new NullArgError(name);
|
|
305
|
+
}
|
|
306
|
+
case 'alias': {
|
|
307
|
+
const { target } = entry as AliasEntry;
|
|
308
|
+
const targetEntry: ConfigEntry = this.configs.get(target)!;
|
|
309
|
+
const targetType = targetEntry.type as AliasType;
|
|
310
|
+
|
|
311
|
+
switch (targetType) {
|
|
312
|
+
case 'flag': {
|
|
313
|
+
return { name: target, value: true, shouldSkip: false };
|
|
314
|
+
}
|
|
315
|
+
case 'text': {
|
|
316
|
+
if (value != null) {
|
|
317
|
+
return { name: target, value, shouldSkip: false };
|
|
318
|
+
} else if (Object.hasOwn(targetEntry, 'default')) {
|
|
319
|
+
const { default: value } = targetEntry as TextEntry;
|
|
320
|
+
return { name: target, value: value!, shouldSkip: false };
|
|
321
|
+
} else if (nextArg != null) {
|
|
322
|
+
return { name: target, value: nextArg, shouldSkip: true };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
throw new NullArgError(name, target);
|
|
326
|
+
}
|
|
327
|
+
case 'int': {
|
|
328
|
+
if (value != null) {
|
|
329
|
+
return {
|
|
330
|
+
name: target,
|
|
331
|
+
value: parseIntStrict(value),
|
|
332
|
+
shouldSkip: false,
|
|
333
|
+
};
|
|
334
|
+
} else if (Object.hasOwn(targetEntry, 'default')) {
|
|
335
|
+
const { default: value } = targetEntry as IntEntry;
|
|
336
|
+
return { name: target, value: value!, shouldSkip: false };
|
|
337
|
+
} else if (nextArg != null) {
|
|
338
|
+
return {
|
|
339
|
+
name: target,
|
|
340
|
+
value: parseIntStrict(nextArg),
|
|
341
|
+
shouldSkip: true
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
throw new NullIntError(name, target);
|
|
346
|
+
}
|
|
347
|
+
case 'float': {
|
|
348
|
+
if (value != null) {
|
|
349
|
+
return {
|
|
350
|
+
name: target,
|
|
351
|
+
value: parseFloatStrict(value),
|
|
352
|
+
shouldSkip: false,
|
|
353
|
+
};
|
|
354
|
+
} else if (Object.hasOwn(targetEntry, 'default')) {
|
|
355
|
+
const { default: value } = targetEntry as FloatEntry;
|
|
356
|
+
return { name: target, value: value!, shouldSkip: false };
|
|
357
|
+
} else if (nextArg != null) {
|
|
358
|
+
return {
|
|
359
|
+
name: target,
|
|
360
|
+
value: parseFloatStrict(nextArg),
|
|
361
|
+
shouldSkip: true,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
throw new NullFloatError(name, target);
|
|
366
|
+
}
|
|
367
|
+
case 'count': {
|
|
368
|
+
if (value != null)
|
|
369
|
+
return {
|
|
370
|
+
name: target,
|
|
371
|
+
value: parseIntStrict(value),
|
|
372
|
+
shouldSkip: false
|
|
373
|
+
};
|
|
374
|
+
return { name: target, value: 1, shouldSkip: false };
|
|
375
|
+
}
|
|
376
|
+
case 'list': {
|
|
377
|
+
if (value === '') {
|
|
378
|
+
return { name: target, value: [], shouldSkip: false };
|
|
379
|
+
} else if (value != null) {
|
|
380
|
+
const { sep } = targetEntry as ListEntry;
|
|
381
|
+
return {
|
|
382
|
+
name: target,
|
|
383
|
+
value: value.split(sep ?? ','),
|
|
384
|
+
shouldSkip: false
|
|
385
|
+
};
|
|
386
|
+
} else if (nextArg != null) {
|
|
387
|
+
const { sep } = targetEntry as ListEntry;
|
|
388
|
+
return {
|
|
389
|
+
name: target,
|
|
390
|
+
value: nextArg.split(sep ?? ','),
|
|
391
|
+
shouldSkip: true
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
throw new NullArgError(name, target);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private parseShortOption(
|
|
403
|
+
arg: string,
|
|
404
|
+
nextArg: string | null,
|
|
405
|
+
): [boolean, Record<string, OptionValue>] {
|
|
406
|
+
let name: string;
|
|
407
|
+
let value: string | null;
|
|
408
|
+
let entry: ConfigEntry;
|
|
409
|
+
|
|
410
|
+
const pairs: Record<string, OptionValue> = Object.create(null);
|
|
411
|
+
|
|
412
|
+
for (let i = 1, n = arg.length; i < n; i++) {
|
|
413
|
+
name = arg[i] as string;
|
|
414
|
+
value = nextArg ?? null;
|
|
415
|
+
entry = this.configs.get(name)!;
|
|
416
|
+
if (entry == null && this.configs.allowUnknown) {
|
|
417
|
+
/* silently skip unknown options */
|
|
418
|
+
continue;
|
|
419
|
+
} else if (entry == null) {
|
|
420
|
+
throw new UnknownOptionError(name);
|
|
421
|
+
}
|
|
422
|
+
const tag: OptionType = entry.type;
|
|
423
|
+
|
|
424
|
+
switch (tag) {
|
|
425
|
+
case 'flag': {
|
|
426
|
+
pairs[name] = true;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
case 'text': {
|
|
430
|
+
if (i < n - 1) {
|
|
431
|
+
value = arg.slice(i + 1, n);
|
|
432
|
+
pairs[name] = value;
|
|
433
|
+
return [false, pairs];
|
|
434
|
+
} else if (Object.hasOwn(entry, 'default')) {
|
|
435
|
+
const { default: value } = entry as TextEntry;
|
|
436
|
+
pairs[name] = value!;
|
|
437
|
+
return [false, pairs];
|
|
438
|
+
} else if (value != null) {
|
|
439
|
+
pairs[name] = value;
|
|
440
|
+
return [true, pairs];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
throw new NullArgError(name);
|
|
444
|
+
}
|
|
445
|
+
case 'int': {
|
|
446
|
+
if (i < n - 1) {
|
|
447
|
+
value = arg.slice(i + 1, n);
|
|
448
|
+
pairs[name] = parseIntStrict(value);
|
|
449
|
+
return [false, pairs];
|
|
450
|
+
} else if (Object.hasOwn(entry, 'default')) {
|
|
451
|
+
const { default: value } = entry as IntEntry;
|
|
452
|
+
pairs[name] = value!;
|
|
453
|
+
return [false, pairs];
|
|
454
|
+
} else if (value != null) {
|
|
455
|
+
pairs[name] = parseIntStrict(value);
|
|
456
|
+
return [true, pairs];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
throw new NullIntError(name);
|
|
460
|
+
}
|
|
461
|
+
case 'float': {
|
|
462
|
+
if (i < n - 1) {
|
|
463
|
+
value = arg.slice(i + 1, n);
|
|
464
|
+
pairs[name] = parseFloatStrict(value);
|
|
465
|
+
return [false, pairs];
|
|
466
|
+
} else if (Object.hasOwn(entry, 'default')) {
|
|
467
|
+
const { default: value } = entry as FloatEntry;
|
|
468
|
+
pairs[name] = value!;
|
|
469
|
+
return [false, pairs];
|
|
470
|
+
} else if (value != null) {
|
|
471
|
+
pairs[name] = parseFloatStrict(value);
|
|
472
|
+
return [true, pairs];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
throw new NullFloatError(name);
|
|
476
|
+
}
|
|
477
|
+
case 'count': {
|
|
478
|
+
const oldValue = pairs[name] as number ?? 0;
|
|
479
|
+
pairs[name] = oldValue + 1;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
case 'list': {
|
|
483
|
+
if (i < n - 1) {
|
|
484
|
+
value = arg.slice(i + 1, n);
|
|
485
|
+
const { sep } = entry as ListEntry;
|
|
486
|
+
pairs[name] = value.split(sep ?? ',');
|
|
487
|
+
return [false, pairs];
|
|
488
|
+
} else if (value === '') {
|
|
489
|
+
pairs[name] = [];
|
|
490
|
+
return [true, pairs];
|
|
491
|
+
} else if (value != null) {
|
|
492
|
+
const { sep } = entry as ListEntry;
|
|
493
|
+
pairs[name] = value.split(sep ?? ',');
|
|
494
|
+
return [true, pairs];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
throw new NullArgError(name);
|
|
498
|
+
}
|
|
499
|
+
case 'alias': {
|
|
500
|
+
const { target } = entry as AliasEntry;
|
|
501
|
+
const targetEntry: ConfigEntry = this.configs.get(target)!;
|
|
502
|
+
const targetType = targetEntry.type as AliasType;
|
|
503
|
+
|
|
504
|
+
switch (targetType) {
|
|
505
|
+
case 'flag': {
|
|
506
|
+
pairs[target] = true;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
case 'text': {
|
|
510
|
+
if (i < n - 1) {
|
|
511
|
+
value = arg.slice(i + 1, n);
|
|
512
|
+
pairs[target] = value;
|
|
513
|
+
return [false, pairs];
|
|
514
|
+
} else if (Object.hasOwn(targetEntry, 'default')) {
|
|
515
|
+
const { default: value } = targetEntry as TextEntry;
|
|
516
|
+
pairs[target] = value!;
|
|
517
|
+
return [false, pairs];
|
|
518
|
+
} else if (value != null) {
|
|
519
|
+
pairs[target] = value;
|
|
520
|
+
return [true, pairs];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
throw new NullArgError(name, target);
|
|
524
|
+
}
|
|
525
|
+
case 'int': {
|
|
526
|
+
if (i < n - 1) {
|
|
527
|
+
value = arg.slice(i + 1, n);
|
|
528
|
+
pairs[target] = parseIntStrict(value);
|
|
529
|
+
return [false, pairs];
|
|
530
|
+
} else if (Object.hasOwn(targetEntry, 'default')) {
|
|
531
|
+
const { default: value } = targetEntry as IntEntry;
|
|
532
|
+
pairs[target] = value!;
|
|
533
|
+
return [false, pairs];
|
|
534
|
+
} else if (value != null) {
|
|
535
|
+
pairs[target] = parseIntStrict(value);
|
|
536
|
+
return [true, pairs];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
throw new NullIntError(name, target);
|
|
540
|
+
}
|
|
541
|
+
case 'float': {
|
|
542
|
+
if (i < n - 1) {
|
|
543
|
+
value = arg.slice(i + 1, n);
|
|
544
|
+
pairs[target] = parseFloatStrict(value);
|
|
545
|
+
return [false, pairs];
|
|
546
|
+
} else if (Object.hasOwn(targetEntry, 'default')) {
|
|
547
|
+
const { default: value } = targetEntry as FloatEntry;
|
|
548
|
+
pairs[target] = value!;
|
|
549
|
+
return [false, pairs];
|
|
550
|
+
} else if (value != null) {
|
|
551
|
+
pairs[target] = parseFloatStrict(value);
|
|
552
|
+
return [true, pairs];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
throw new NullFloatError(name, target);
|
|
556
|
+
}
|
|
557
|
+
case 'count': {
|
|
558
|
+
const oldValue = pairs[target] as number ?? 0;
|
|
559
|
+
pairs[target] = oldValue + 1;
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case 'list': {
|
|
563
|
+
if (i < n - 1) {
|
|
564
|
+
value = arg.slice(i + 1, n);
|
|
565
|
+
const { sep } = targetEntry as ListEntry;
|
|
566
|
+
pairs[target] = value.split(sep ?? ',');
|
|
567
|
+
return [false, pairs];
|
|
568
|
+
} else if (value === '') {
|
|
569
|
+
pairs[target] = [];
|
|
570
|
+
return [true, pairs];
|
|
571
|
+
} else if (value != null) {
|
|
572
|
+
const { sep } = targetEntry as ListEntry;
|
|
573
|
+
pairs[target] = value.split(sep ?? ',');
|
|
574
|
+
return [true, pairs];
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
throw new NullArgError(name, target);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return [false, pairs];
|
|
585
|
+
}
|
|
586
|
+
}
|