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/src/errors.ts
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime error.
|
|
3
|
+
* Represents errors that occur while parsing input arguments.
|
|
4
|
+
* These errors are triggered by invalid or unexpected user input,
|
|
5
|
+
* such as unknown options, missing values, or values of the wrong type.
|
|
6
|
+
* Runtime errors are produced during the execution of the `.parse()`
|
|
7
|
+
* method and depend on the provided input rather than the parser
|
|
8
|
+
* configuration.
|
|
9
|
+
*/
|
|
10
|
+
export class RuntimeError extends Error {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Configuration error.
|
|
14
|
+
* Represents errors caused by an invalid parser configuration.
|
|
15
|
+
* These errors occur when defining or initializing the parser,
|
|
16
|
+
* such as using unsupported option types or referencing invalid
|
|
17
|
+
* alias targets.
|
|
18
|
+
* Configuration errors are independent of user input and are
|
|
19
|
+
* typically raised before any parsing takes place.
|
|
20
|
+
*/
|
|
21
|
+
export class ConfigError extends Error {
|
|
22
|
+
private _errors: Error[] = [];
|
|
23
|
+
|
|
24
|
+
public push(error: Error): void {
|
|
25
|
+
this._errors.push(error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get errors(): Error[] {
|
|
29
|
+
return this._errors;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Invalid integer argument error.
|
|
35
|
+
* Raised when an option of type "int" is provided with an associated
|
|
36
|
+
* value that cannot be parsed as an integer.
|
|
37
|
+
* This error only applies when a value is present. Missing values are
|
|
38
|
+
* reported using NullIntError.
|
|
39
|
+
*/
|
|
40
|
+
export class InvalidIntError extends RuntimeError {
|
|
41
|
+
public value: string;
|
|
42
|
+
|
|
43
|
+
constructor(value: string) {
|
|
44
|
+
super(`'${value}' is not a valid integer`);
|
|
45
|
+
this.value = value;
|
|
46
|
+
Object.freeze(this);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Invalid float argument error.
|
|
52
|
+
* Raised when an option of type "float" is provided with an associated
|
|
53
|
+
* value that cannot be parsed as a number.
|
|
54
|
+
* This error only applies when a value is present. Missing values are
|
|
55
|
+
* reported using NullFloatError.
|
|
56
|
+
*/
|
|
57
|
+
export class InvalidFloatError extends RuntimeError {
|
|
58
|
+
public value: string;
|
|
59
|
+
|
|
60
|
+
constructor(value: string) {
|
|
61
|
+
super(`'${value}' is not a valid integer`);
|
|
62
|
+
this.value = value;
|
|
63
|
+
Object.freeze(this);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Unsafe integer error.
|
|
69
|
+
* Raised when a parsed integer cannot be safely represented
|
|
70
|
+
* by the underlying runtime.
|
|
71
|
+
* This error only applies to values that are successfully parsed
|
|
72
|
+
* as integers.
|
|
73
|
+
*/
|
|
74
|
+
export class UnsafeIntegerError extends RuntimeError {
|
|
75
|
+
public value: number;
|
|
76
|
+
|
|
77
|
+
constructor(value: number) {
|
|
78
|
+
super(`${value} is not a safe integer`);
|
|
79
|
+
this.value = value;
|
|
80
|
+
Object.freeze(this);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Missing argument error.
|
|
86
|
+
* Raised when an option that requires an associated value is provided
|
|
87
|
+
* without one.
|
|
88
|
+
* If the option is an alias, the error message includes both the
|
|
89
|
+
* alias name and its target.
|
|
90
|
+
*/
|
|
91
|
+
export class NullArgError extends RuntimeError {
|
|
92
|
+
public option: string;
|
|
93
|
+
public target?: string;
|
|
94
|
+
|
|
95
|
+
constructor(name: string, target?: string) {
|
|
96
|
+
const msg = target
|
|
97
|
+
? `option '${name}' (alias for '${target}') must take an argument`
|
|
98
|
+
: `option '${name}' must take an argument`;
|
|
99
|
+
super(msg);
|
|
100
|
+
this.option = name;
|
|
101
|
+
if (target) {
|
|
102
|
+
this.target = target;
|
|
103
|
+
}
|
|
104
|
+
Object.freeze(this);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Missing integer argument error.
|
|
110
|
+
* Raised when an option of type "int" is provided without an
|
|
111
|
+
* associated value.
|
|
112
|
+
* This error does not cover invalid integer values. Invalid values
|
|
113
|
+
* are reported using InvalidIntError.
|
|
114
|
+
* If the option is an alias, the error message includes both the
|
|
115
|
+
* alias name and its target.
|
|
116
|
+
*/
|
|
117
|
+
export class NullIntError extends RuntimeError {
|
|
118
|
+
public option: string;
|
|
119
|
+
public target?: string;
|
|
120
|
+
|
|
121
|
+
constructor(name: string, target?: string) {
|
|
122
|
+
const msg = target
|
|
123
|
+
? `option '${name}' (alias for '${target}') requires an integer number argument`
|
|
124
|
+
: `option '${name}' requires an integer number argument`;
|
|
125
|
+
super(msg);
|
|
126
|
+
this.option = name;
|
|
127
|
+
if (target) {
|
|
128
|
+
this.target = target;
|
|
129
|
+
}
|
|
130
|
+
Object.freeze(this);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Missing float argument error.
|
|
136
|
+
* Raised when an option of type "float" is provided without an
|
|
137
|
+
* associated value.
|
|
138
|
+
* This error does not cover invalid float values. Invalid values
|
|
139
|
+
* are reported using InvalidFloatError.
|
|
140
|
+
* If the option is an alias, the error message includes both the
|
|
141
|
+
* alias name and its target.
|
|
142
|
+
*/
|
|
143
|
+
export class NullFloatError extends RuntimeError {
|
|
144
|
+
public option: string;
|
|
145
|
+
public target?: string;
|
|
146
|
+
|
|
147
|
+
constructor(name: string, target?: string) {
|
|
148
|
+
const msg = target
|
|
149
|
+
? `option '${name}' (alias for '${target}') requires a numeric argument`
|
|
150
|
+
: `option '${name}' requires a numeric argument`;
|
|
151
|
+
super(msg);
|
|
152
|
+
this.option = name;
|
|
153
|
+
if (target) {
|
|
154
|
+
this.target = target;
|
|
155
|
+
}
|
|
156
|
+
Object.freeze(this);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Unknown option error.
|
|
162
|
+
* Raised when an input contains an option that is not defined in the
|
|
163
|
+
* parser configuration.
|
|
164
|
+
* This applies to both long and short options. The error is triggered
|
|
165
|
+
* as soon as the parser encounters an unrecognized option name.
|
|
166
|
+
*/
|
|
167
|
+
export class UnknownOptionError extends RuntimeError {
|
|
168
|
+
public option: string;
|
|
169
|
+
|
|
170
|
+
constructor(name: string) {
|
|
171
|
+
super(`option '${name}' is not supported`);
|
|
172
|
+
this.option = name;
|
|
173
|
+
Object.freeze(this);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Invalid option type error.
|
|
179
|
+
* Raised when an option is declared with a type that is not supported
|
|
180
|
+
* by the parser.
|
|
181
|
+
* This is a configuration error and typically occurs during parser
|
|
182
|
+
* initialization rather than at parse time.
|
|
183
|
+
*/
|
|
184
|
+
export class InvalidOptionTypeError extends ConfigError {
|
|
185
|
+
public type: string;
|
|
186
|
+
|
|
187
|
+
constructor(tag: string) {
|
|
188
|
+
super(`option type '${tag}' is not supported`);
|
|
189
|
+
this.type = tag;
|
|
190
|
+
Object.freeze(this);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Alias target not found error.
|
|
196
|
+
* Raised when an alias references a target option that does not exist
|
|
197
|
+
* in the parser configuration.
|
|
198
|
+
* This is a configuration error and indicates that the alias points
|
|
199
|
+
* to an undefined option.
|
|
200
|
+
*/
|
|
201
|
+
export class AliasTargetNotFoundError extends ConfigError {
|
|
202
|
+
public option: string;
|
|
203
|
+
public target: string;
|
|
204
|
+
|
|
205
|
+
constructor(name: string, target: string) {
|
|
206
|
+
super(`target value '${target}' for option '${name}' was not found`);
|
|
207
|
+
this.option = name;
|
|
208
|
+
this.target = target;
|
|
209
|
+
Object.freeze(this);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Invalid alias target error.
|
|
215
|
+
* Raised when an alias references a target that is not a valid option
|
|
216
|
+
* for aliasing.
|
|
217
|
+
* This may occur if the target is itself an alias (when alias chaining
|
|
218
|
+
* is not supported) or if the target cannot accept the alias due to
|
|
219
|
+
* type or configuration constraints.
|
|
220
|
+
*/
|
|
221
|
+
export class InvalidAliasTargetError extends ConfigError {
|
|
222
|
+
public option: string;
|
|
223
|
+
public target: string;
|
|
224
|
+
|
|
225
|
+
constructor(name: string, target: string) {
|
|
226
|
+
super(`cannot create an alias to another alias (${name} => ${target})`);
|
|
227
|
+
this.option = name;
|
|
228
|
+
this.target = target;
|
|
229
|
+
Object.freeze(this);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Missing option property error.
|
|
235
|
+
* Raised when an option entry in the parser configuration is missing
|
|
236
|
+
* one or more required properties.
|
|
237
|
+
* This is a configuration error and indicates that the option
|
|
238
|
+
* definition is incomplete or malformed.
|
|
239
|
+
* Implementations may include the option name and the missing
|
|
240
|
+
* property name to aid debugging.
|
|
241
|
+
*/
|
|
242
|
+
export class MissingOptionPropertyError extends ConfigError {
|
|
243
|
+
public option: string;
|
|
244
|
+
public property: string;
|
|
245
|
+
|
|
246
|
+
constructor(option: string, property: string) {
|
|
247
|
+
super(`option '${option}' is missing required property '${property}'`);
|
|
248
|
+
this.option = option;
|
|
249
|
+
this.property = property;
|
|
250
|
+
Object.freeze(this);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Missing option type error.
|
|
256
|
+
* Raised when a configuration entry does not define a "type" property.
|
|
257
|
+
* The "type" property is required for all option entries and determines
|
|
258
|
+
* how the option is parsed and validated. Without it, the entry cannot
|
|
259
|
+
* be interpreted by the parser.
|
|
260
|
+
* This is a configuration error and is typically raised during
|
|
261
|
+
* configuration validation.
|
|
262
|
+
*/
|
|
263
|
+
export class MissingOptionTypeError extends ConfigError {
|
|
264
|
+
public option: string;
|
|
265
|
+
|
|
266
|
+
constructor(name: string) {
|
|
267
|
+
const msg = `option '${name}' is missing required property 'type'`;
|
|
268
|
+
super(msg);
|
|
269
|
+
this.option = name;
|
|
270
|
+
Object.freeze(this);
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/mod.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argot CLI argument parser.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a schema-based command-line argument parser.
|
|
5
|
+
* The parser processes an array of strings and produces a structured
|
|
6
|
+
* result containing options, parameters, and operands.
|
|
7
|
+
*
|
|
8
|
+
* Parsing behavior is fully determined by a configuration object.
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import {
|
|
14
|
+
* ArgParser,
|
|
15
|
+
* ParserConfig,
|
|
16
|
+
* type ConfigEntries,
|
|
17
|
+
* } from '@argot-cli/argot';
|
|
18
|
+
*
|
|
19
|
+
* const entries: ConfigEntries = {
|
|
20
|
+
* verbose: { type: 'flag' },
|
|
21
|
+
* count: { type: 'int' },
|
|
22
|
+
* name: { type: 'text', default: 'world' },
|
|
23
|
+
* };
|
|
24
|
+
*
|
|
25
|
+
* const config = new ParserConfig(entries);
|
|
26
|
+
*
|
|
27
|
+
* const parser = new ArgParser(config);
|
|
28
|
+
*
|
|
29
|
+
* const result = parser.parse([
|
|
30
|
+
* '--verbose',
|
|
31
|
+
* '--count=3',
|
|
32
|
+
* 'mode=fast',
|
|
33
|
+
* 'file.txt',
|
|
34
|
+
* ]);
|
|
35
|
+
*
|
|
36
|
+
* console.log(result.options.get('verbose')); // true
|
|
37
|
+
* console.log(result.options.get('count')); // 3
|
|
38
|
+
* console.log(result.options.get('name')); // "world"
|
|
39
|
+
*
|
|
40
|
+
* console.log(result.parameters.get('mode')); // "fast"
|
|
41
|
+
*
|
|
42
|
+
* console.log(result.operands); // ["file.txt"]
|
|
43
|
+
* ```
|
|
44
|
+
* @module
|
|
45
|
+
*/
|
|
46
|
+
export { ArgParser } from './arg_parser.ts';
|
|
47
|
+
export { ParserConfig } from './parser_config.ts';
|
|
48
|
+
export { readJSONConfig } from './read_config.ts';
|
|
49
|
+
export type { ConfigEntry, ConfigEntries } from './types.ts';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConfigEntry,
|
|
3
|
+
ConfigEntries,
|
|
4
|
+
ParserConfigInput,
|
|
5
|
+
StrictParserOptions,
|
|
6
|
+
} from './types.ts';
|
|
7
|
+
import { validateEntries, validateEntriesAggregate } from './utils.ts';
|
|
8
|
+
|
|
9
|
+
function isObject(obj: unknown): obj is Record<string, unknown> {
|
|
10
|
+
if (Array.isArray(obj) || obj == null || typeof obj !== 'object')
|
|
11
|
+
return false;
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Immutable configuration for the argument parser.
|
|
17
|
+
* The configuration defines the set of supported options and their types.
|
|
18
|
+
* Entries are provided as an object mapping option names to configuration
|
|
19
|
+
* entries.
|
|
20
|
+
* The configuration is normalized and validated during construction.
|
|
21
|
+
* The resulting object is immutable.
|
|
22
|
+
*/
|
|
23
|
+
export class ParserConfig {
|
|
24
|
+
#entries: ConfigEntries;
|
|
25
|
+
#parser: StrictParserOptions;
|
|
26
|
+
#size: number;
|
|
27
|
+
|
|
28
|
+
constructor(input: ParserConfigInput) {
|
|
29
|
+
if (!isObject(input)) {
|
|
30
|
+
throw new TypeError('input value must be a plain object');
|
|
31
|
+
} else if (!isObject(input.options)) {
|
|
32
|
+
throw new TypeError('"options" value must be a plain object');
|
|
33
|
+
} else if (input.parser != null && !isObject(input.parser)) {
|
|
34
|
+
throw new TypeError('"parser" value must be a plain object');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { options, parser } = input;
|
|
38
|
+
|
|
39
|
+
this.#parser = {
|
|
40
|
+
allowUnknown: parser?.allowUnknown ?? false,
|
|
41
|
+
parseParameters: parser?.parseParameters ?? true,
|
|
42
|
+
aggregateErrors: parser?.aggregateErrors ?? false,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.#size = 0;
|
|
46
|
+
const entryMap = Object.create(null);
|
|
47
|
+
for (const [key, value] of Object.entries(options)) {
|
|
48
|
+
entryMap[key] = Object.freeze({ ...value });
|
|
49
|
+
this.#size += 1;
|
|
50
|
+
}
|
|
51
|
+
this.#entries = entryMap;
|
|
52
|
+
|
|
53
|
+
if (this.#parser.aggregateErrors)
|
|
54
|
+
validateEntriesAggregate(this.#entries);
|
|
55
|
+
else
|
|
56
|
+
validateEntries(this.#entries);
|
|
57
|
+
Object.freeze(this.#entries);
|
|
58
|
+
Object.freeze(this.#parser);
|
|
59
|
+
Object.freeze(this);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public entries(): Iterable<[string, ConfigEntry]> {
|
|
63
|
+
return Object.entries(this.#entries);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public get(key: string): ConfigEntry | undefined {
|
|
67
|
+
return this.#entries[key];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public has(key: string): boolean {
|
|
71
|
+
return Object.hasOwn(this.#entries, key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public keys(): Iterable<string> {
|
|
75
|
+
return Object.keys(this.#entries);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public values(): Iterable<ConfigEntry> {
|
|
79
|
+
return Object.values(this.#entries);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public toJSON(): ParserConfigInput {
|
|
83
|
+
return {
|
|
84
|
+
options: this.#entries,
|
|
85
|
+
parser: this.#parser,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get size(): number {
|
|
90
|
+
return this.#size;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get allowUnknown(): boolean {
|
|
94
|
+
return this.#parser.allowUnknown;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get parseParameters(): boolean {
|
|
98
|
+
return this.#parser.parseParameters;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get aggregateErrors(): boolean {
|
|
102
|
+
return this.#parser.aggregateErrors;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { ParserConfig } from './parser_config.ts';
|
|
3
|
+
import type { ParserConfigInput } from './types.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reads a parser configuration from a JSON file.
|
|
7
|
+
* The file must contain a valid configuration object that can be
|
|
8
|
+
* passed to the ParserConfig constructor.
|
|
9
|
+
*/
|
|
10
|
+
export function readJSONConfig(configFile: string): ParserConfig {
|
|
11
|
+
const content: string = fs.readFileSync(configFile, { encoding: 'utf8' });
|
|
12
|
+
const configObj: ParserConfigInput = JSON.parse(content);
|
|
13
|
+
return new ParserConfig(configObj);
|
|
14
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Option type.
|
|
3
|
+
* Defines the kind of value an option accepts and how it is parsed.
|
|
4
|
+
*/
|
|
5
|
+
export type OptionType =
|
|
6
|
+
| 'flag'
|
|
7
|
+
| 'text'
|
|
8
|
+
| 'int'
|
|
9
|
+
| 'float'
|
|
10
|
+
| 'count'
|
|
11
|
+
| 'list'
|
|
12
|
+
| 'alias';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Alias target type.
|
|
16
|
+
* Represents the set of option types that an alias may reference.
|
|
17
|
+
* Aliases cannot target other aliases.
|
|
18
|
+
*/
|
|
19
|
+
export type AliasType =
|
|
20
|
+
| 'flag'
|
|
21
|
+
| 'text'
|
|
22
|
+
| 'int'
|
|
23
|
+
| 'float'
|
|
24
|
+
| 'count'
|
|
25
|
+
| 'list';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Flag option entry.
|
|
29
|
+
* A flag is a boolean option that is set to true when present.
|
|
30
|
+
*/
|
|
31
|
+
export type FlagEntry = { type: 'flag' };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Text option entry.
|
|
35
|
+
* A text option accepts a string value. A default value may be
|
|
36
|
+
* defined for command-line options provided without an associated
|
|
37
|
+
* value.
|
|
38
|
+
*/
|
|
39
|
+
export type TextEntry = { type: 'text'; default?: string };
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Integer option entry.
|
|
43
|
+
* An integer option accepts a numeric value. The value must be a
|
|
44
|
+
* valid integer and must be a safe integer. A default value may
|
|
45
|
+
* be defined for command-line options provided without an
|
|
46
|
+
* associated value.
|
|
47
|
+
*/
|
|
48
|
+
export type IntEntry = { type: 'int'; default?: number };
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Float option entry.
|
|
52
|
+
* A float option takes any value that can be represented as a
|
|
53
|
+
* floating point number. Integer values are converted to floats.
|
|
54
|
+
*/
|
|
55
|
+
export type FloatEntry = { type: 'float', default?: number };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Count option entry.
|
|
59
|
+
* A count option increments a numeric counter each time the option
|
|
60
|
+
* is provided.
|
|
61
|
+
*/
|
|
62
|
+
export type CountEntry = { type: 'count' };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* List option entry.
|
|
66
|
+
* A list option accepts a string value and splits it into multiple
|
|
67
|
+
* entries using a separator.
|
|
68
|
+
* The separator defaults to a comma if not specified.
|
|
69
|
+
*/
|
|
70
|
+
export type ListEntry = { type: 'list'; sep?: string };
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Alias option entry.
|
|
74
|
+
* An alias forwards its value to another option.
|
|
75
|
+
* The target must reference a valid, non-alias option.
|
|
76
|
+
*/
|
|
77
|
+
export type AliasEntry = { type: 'alias'; target: string };
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A configuration entry representing a single command-line option.
|
|
81
|
+
*/
|
|
82
|
+
export type ConfigEntry = (
|
|
83
|
+
| FlagEntry
|
|
84
|
+
| TextEntry
|
|
85
|
+
| IntEntry
|
|
86
|
+
| FloatEntry
|
|
87
|
+
| CountEntry
|
|
88
|
+
| ListEntry
|
|
89
|
+
| AliasEntry
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* An object holding option configuration entries.
|
|
94
|
+
*/
|
|
95
|
+
export type ConfigEntries = Record<string, ConfigEntry>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* ParserConfig configuration flags that don't refer to a single
|
|
99
|
+
* command-line option.
|
|
100
|
+
*/
|
|
101
|
+
export type ParserOptions = {
|
|
102
|
+
allowUnknown?: boolean;
|
|
103
|
+
parseParameters?: boolean;
|
|
104
|
+
aggregateErrors?: boolean;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type StrictParserOptions = {
|
|
108
|
+
allowUnknown: boolean;
|
|
109
|
+
parseParameters: boolean;
|
|
110
|
+
aggregateErrors: boolean;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The input object for the ParserConfig constructor.
|
|
115
|
+
* Contains configuration for specific command-line options
|
|
116
|
+
* and general configuration options.
|
|
117
|
+
*/
|
|
118
|
+
export type ParserConfigInput = {
|
|
119
|
+
options: ConfigEntries;
|
|
120
|
+
parser?: ParserOptions;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Option value.
|
|
125
|
+
* Represents the possible values returned for parsed options.
|
|
126
|
+
*/
|
|
127
|
+
export type OptionValue = boolean | string | number | string[];
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Result mapping.
|
|
131
|
+
* A map-like structure used to store parsed key-value pairs.
|
|
132
|
+
* Instances can be frozen to prevent further modifications.
|
|
133
|
+
*/
|
|
134
|
+
abstract class ResultMapping<K, V> extends Map<K, V> {
|
|
135
|
+
protected isFrozen = false;
|
|
136
|
+
|
|
137
|
+
constructor(entries?: [K, V][]) {
|
|
138
|
+
super(entries);
|
|
139
|
+
|
|
140
|
+
// Prevent property from showing up in console.log.
|
|
141
|
+
Object.defineProperty(this, 'isFrozen', {
|
|
142
|
+
enumerable: false,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Prevent regular object property assignments.
|
|
146
|
+
Object.preventExtensions(this);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Sets a value for the given key.
|
|
151
|
+
* Throws if the mapping is frozen.
|
|
152
|
+
*/
|
|
153
|
+
override set(key: K, value: V): this {
|
|
154
|
+
if (this.isFrozen) {
|
|
155
|
+
const name = this.constructor.name.toLowerCase();
|
|
156
|
+
throw new TypeError(`you cannot modify parsed ${name}`);
|
|
157
|
+
}
|
|
158
|
+
return super.set(key, value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Deletes a key from the mapping.
|
|
163
|
+
* Throws if the mapping is frozen.
|
|
164
|
+
*/
|
|
165
|
+
override delete(key: K): boolean {
|
|
166
|
+
if (this.isFrozen) {
|
|
167
|
+
const name = this.constructor.name.toLowerCase();
|
|
168
|
+
throw new TypeError(`you cannot delete parsed ${name}`);
|
|
169
|
+
}
|
|
170
|
+
return super.delete(key);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Clears all entries from the mapping.
|
|
175
|
+
* Throws if the mapping is frozen.
|
|
176
|
+
*/
|
|
177
|
+
override clear(): void {
|
|
178
|
+
if (this.isFrozen) {
|
|
179
|
+
const name = this.constructor.name.toLowerCase();
|
|
180
|
+
throw new TypeError(`you cannot delete parsed ${name}`);
|
|
181
|
+
}
|
|
182
|
+
super.clear();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Freezes the mapping, preventing further modifications.
|
|
187
|
+
*/
|
|
188
|
+
public freeze(): void {
|
|
189
|
+
this.isFrozen = true;
|
|
190
|
+
Object.freeze(this);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Serializes the mapping to a plain object.
|
|
195
|
+
*/
|
|
196
|
+
public toJSON(): Record<string, V> {
|
|
197
|
+
return Object.fromEntries(this.entries());
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Result list.
|
|
203
|
+
* An array-like structure used to store ordered values.
|
|
204
|
+
* Only numeric indices and the length property may be modified.
|
|
205
|
+
* Assigning any other property will throw an error.
|
|
206
|
+
*/
|
|
207
|
+
abstract class ResultList<T> extends Array<T> {
|
|
208
|
+
constructor(args?: T[]) {
|
|
209
|
+
const length = (args as T[])?.length ?? 0;
|
|
210
|
+
super(length);
|
|
211
|
+
for (let i = 0; i < length; i++) {
|
|
212
|
+
this[i] = (args as T[])[i]!;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return new Proxy(this, {
|
|
216
|
+
set(target, prop, value) {
|
|
217
|
+
if (prop === 'length') {
|
|
218
|
+
target[prop] = value;
|
|
219
|
+
return true;
|
|
220
|
+
} else if (String(Number(prop)) === prop) {
|
|
221
|
+
target[Number(prop)] = value;
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
throw new TypeError(`Cannot add non-index property: ${String(prop)}`);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Parsed option values.
|
|
233
|
+
* Values are determined by the parser configuration. Repeated options
|
|
234
|
+
* either overwrite previous values or accumulate them, depending on
|
|
235
|
+
* their type.
|
|
236
|
+
*/
|
|
237
|
+
export class Options extends ResultMapping<string, OptionValue> {}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Parsed key/value assignments.
|
|
241
|
+
* Parameters are parsed from arguments of the form "key=value" and do
|
|
242
|
+
* not depend on the parser configuration.
|
|
243
|
+
*/
|
|
244
|
+
export class Parameters extends ResultMapping<string, string> {}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Positional arguments.
|
|
248
|
+
* Operands are arguments that are not parsed as options or parameters.
|
|
249
|
+
*/
|
|
250
|
+
export class Operands extends ResultList<string> {}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The result of parsing operations.
|
|
254
|
+
* - options: parsed option values
|
|
255
|
+
* - parameters: parsed named parameters
|
|
256
|
+
* - operands: positional arguments
|
|
257
|
+
*/
|
|
258
|
+
export interface ParseResult {
|
|
259
|
+
options: Options;
|
|
260
|
+
parameters?: Parameters;
|
|
261
|
+
operands: Operands;
|
|
262
|
+
}
|