clap-ts 0.2.0 → 0.3.0
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 +718 -102
- package/dist/argfile.d.ts +55 -0
- package/dist/argfile.js +155 -0
- package/dist/completions.d.ts +4 -1
- package/dist/completions.js +199 -16
- package/dist/config.d.ts +75 -0
- package/dist/config.js +134 -0
- package/dist/help.d.ts +4 -4
- package/dist/help.js +276 -81
- package/dist/index.d.ts +2 -3
- package/dist/index.js +3 -3
- package/dist/install.d.ts +55 -0
- package/dist/install.js +185 -0
- package/dist/log.d.ts +78 -0
- package/dist/log.js +164 -0
- package/dist/man.d.ts +28 -0
- package/dist/man.js +234 -0
- package/dist/markdown.d.ts +17 -0
- package/dist/markdown.js +165 -0
- package/dist/output.d.ts +111 -0
- package/dist/output.js +356 -0
- package/dist/parser.d.ts +40 -19
- package/dist/parser.js +789 -461
- package/dist/plugins.d.ts +58 -0
- package/dist/plugins.js +145 -0
- package/dist/progress.d.ts +89 -0
- package/dist/progress.js +205 -0
- package/dist/prompt.d.ts +99 -0
- package/dist/prompt.js +299 -0
- package/dist/runner.d.ts +5 -2
- package/dist/runner.js +341 -135
- package/dist/spec.d.ts +83 -0
- package/dist/spec.js +124 -0
- package/dist/testing.d.ts +59 -0
- package/dist/testing.js +113 -0
- package/dist/types.d.ts +279 -11
- package/dist/validation.js +191 -69
- package/package.json +63 -6
package/dist/parser.js
CHANGED
|
@@ -1,265 +1,297 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Argument parser
|
|
3
|
-
* then layers on: env fallback, type coercion, count/append actions,
|
|
4
|
-
* numArgs with defaultMissingValue, global args, kebab-to-camel mapping,
|
|
5
|
-
* subcommand detection, and default values.
|
|
2
|
+
* Argument parser.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Tokenizes argv directly against the command's own arg definitions. This
|
|
5
|
+
* replaced node:util parseArgs, which re-validates its entire `options` object
|
|
6
|
+
* on every call (~170ns per option, regardless of argv length) and cannot
|
|
7
|
+
* express multi-value options, value terminators, or subcommand boundaries.
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
9
|
+
* Handled here: long/short/clustered flags, attached and `=` values, boolean
|
|
10
|
+
* negation, count and append actions, multi-token numArgs, optional values via
|
|
11
|
+
* defaultMissingValue, value delimiters, hyphen and negative-number values,
|
|
12
|
+
* positional assignment, trailing var args, `--` escape, subcommand
|
|
13
|
+
* boundaries, env fallback, conditional and static defaults, and
|
|
14
|
+
* kebab-to-camel key mapping.
|
|
15
|
+
*
|
|
16
|
+
* Constraint checks that need the whole picture (conflicts, requires, groups,
|
|
17
|
+
* possible values) live in validation.ts.
|
|
19
18
|
*/
|
|
20
|
-
|
|
19
|
+
// ---- Error ----
|
|
20
|
+
export class CliParseError extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'CliParseError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
21
26
|
// ---- Helpers ----
|
|
27
|
+
const HYPHEN = 45;
|
|
28
|
+
const NEGATIVE_NUMBER = /^-\d/;
|
|
22
29
|
/** Convert kebab-case to camelCase: --config-path -> configPath */
|
|
23
|
-
function kebabToCamel(s) {
|
|
30
|
+
export function kebabToCamel(s) {
|
|
24
31
|
return s.replaceAll(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
25
32
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Get the raw argv slice (after the binary/script path). With `noBinaryName`
|
|
35
|
+
* the source is taken as-is, matching clap's Command::no_binary_name.
|
|
36
|
+
*/
|
|
37
|
+
export function getRawArgs(argv, noBinaryName = false) {
|
|
28
38
|
if (argv) {
|
|
29
39
|
return [...argv];
|
|
30
40
|
}
|
|
31
41
|
// Bun.argv includes [bun, script, ...args], same as process.argv
|
|
32
42
|
const source = globalThis.Bun === undefined ? process.argv : globalThis.Bun.argv;
|
|
33
|
-
return source.slice(2);
|
|
43
|
+
return noBinaryName ? [...source] : source.slice(2);
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
this.name = 'CliParseError';
|
|
40
|
-
}
|
|
45
|
+
function readEnv(name) {
|
|
46
|
+
return globalThis.Bun === undefined
|
|
47
|
+
? process.env[name]
|
|
48
|
+
: globalThis.Bun.env[name];
|
|
41
49
|
}
|
|
42
|
-
// ----
|
|
50
|
+
// ---- Subcommands ----
|
|
51
|
+
const resolvedSubCommands = new WeakMap();
|
|
43
52
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Registers both hidden aliases and visible aliases.
|
|
53
|
+
* The command's subcommands, building any lazy ones on first use and caching
|
|
54
|
+
* the result so the thunk runs at most once per command.
|
|
47
55
|
*/
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
else {
|
|
68
|
-
longMap.set(alias, { key, def, negated: false });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// Register visible aliases (also work as real aliases at parse time)
|
|
73
|
-
if (def.visibleAlias) {
|
|
74
|
-
for (const alias of def.visibleAlias) {
|
|
75
|
-
if (alias.length === 1) {
|
|
76
|
-
shortMap.set(alias, { key, def });
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
longMap.set(alias, { key, def, negated: false });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (def.short && def.short.length === 1) {
|
|
84
|
-
shortMap.set(def.short, { key, def });
|
|
85
|
-
}
|
|
56
|
+
export function subCommandsOf(command) {
|
|
57
|
+
if (command.lazySubCommands === undefined) {
|
|
58
|
+
return command.subCommands ?? {};
|
|
59
|
+
}
|
|
60
|
+
let resolved = resolvedSubCommands.get(command);
|
|
61
|
+
if (resolved === undefined) {
|
|
62
|
+
resolved = { ...command.lazySubCommands(), ...command.subCommands };
|
|
63
|
+
resolvedSubCommands.set(command, resolved);
|
|
64
|
+
}
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
/** Whether the command has any subcommand, without building the lazy ones. */
|
|
68
|
+
export function hasSubCommands(command) {
|
|
69
|
+
if (command.lazySubCommands !== undefined) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const subs = command.subCommands;
|
|
73
|
+
if (subs === undefined) {
|
|
74
|
+
return false;
|
|
86
75
|
}
|
|
87
|
-
|
|
76
|
+
for (const _key in subs) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
88
80
|
}
|
|
89
|
-
// ----
|
|
81
|
+
// ---- Possible Values ----
|
|
82
|
+
const possibleValueCache = new WeakMap();
|
|
90
83
|
/**
|
|
91
|
-
*
|
|
84
|
+
* Normalize an arg's allowed values to PossibleValue records. Plain strings and
|
|
85
|
+
* PossibleValue objects can be mixed in the same list.
|
|
92
86
|
*/
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
options[longName] = { type: 'string' };
|
|
113
|
-
}
|
|
114
|
-
if (def.numArgs && def.numArgs.min === 0 && def.type !== 'boolean') {
|
|
115
|
-
optionalValueFlags.add(longName);
|
|
116
|
-
}
|
|
117
|
-
if (def.short && def.short.length === 1) {
|
|
118
|
-
options[longName].short = def.short;
|
|
119
|
-
}
|
|
120
|
-
// Register hidden and visible aliases
|
|
121
|
-
const registerAliases = (aliases) => {
|
|
122
|
-
const opt = options[longName];
|
|
123
|
-
for (const alias of aliases) {
|
|
124
|
-
if (alias.length > 1 || opt.short) {
|
|
125
|
-
options[alias] = { type: opt.type, multiple: opt.multiple };
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
opt.short = alias;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
if (def.alias) {
|
|
133
|
-
registerAliases(def.alias);
|
|
134
|
-
}
|
|
135
|
-
if (def.visibleAlias) {
|
|
136
|
-
registerAliases(def.visibleAlias);
|
|
87
|
+
export function possibleValues(def) {
|
|
88
|
+
const parser = def.valueParser;
|
|
89
|
+
if (parser === undefined || typeof parser === 'function') {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const cached = possibleValueCache.get(parser);
|
|
93
|
+
if (cached !== undefined) {
|
|
94
|
+
return cached;
|
|
95
|
+
}
|
|
96
|
+
const normalized = parser.map((v) => (typeof v === 'string' ? { name: v } : v));
|
|
97
|
+
possibleValueCache.set(parser, normalized);
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
/** Whether a raw value matches this possible value, by name or alias. */
|
|
101
|
+
export function matchesPossibleValue(candidate, value, ignoreCase) {
|
|
102
|
+
if (ignoreCase) {
|
|
103
|
+
const lowered = value.toLowerCase();
|
|
104
|
+
if (candidate.name.toLowerCase() === lowered) {
|
|
105
|
+
return true;
|
|
137
106
|
}
|
|
107
|
+
return candidate.aliases?.some((a) => a.toLowerCase() === lowered) ?? false;
|
|
138
108
|
}
|
|
139
|
-
|
|
140
|
-
options['help'] = { type: 'boolean', short: 'h' };
|
|
141
|
-
options['version'] = { type: 'boolean', short: 'V' };
|
|
142
|
-
return { options, optionalValueFlags };
|
|
143
|
-
}
|
|
144
|
-
// ---- Pre-scan / Preprocessing ----
|
|
145
|
-
/** Check if the next token looks like a flag (not a value). */
|
|
146
|
-
function isNextTokenAFlag(nextToken) {
|
|
147
|
-
return (nextToken === undefined ||
|
|
148
|
-
(nextToken.startsWith('-') && nextToken !== '-' && nextToken !== '--'));
|
|
109
|
+
return candidate.name === value || (candidate.aliases?.includes(value) ?? false);
|
|
149
110
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
111
|
+
const specCache = new WeakMap();
|
|
112
|
+
/** Actions that stand alone: the flag carries no value of its own. */
|
|
113
|
+
const VALUELESS_ACTIONS = new Set([
|
|
114
|
+
'count',
|
|
115
|
+
'setTrue',
|
|
116
|
+
'setFalse',
|
|
117
|
+
'help',
|
|
118
|
+
'helpShort',
|
|
119
|
+
'helpLong',
|
|
120
|
+
'version',
|
|
121
|
+
]);
|
|
122
|
+
function valueCounts(def) {
|
|
123
|
+
if (def.numArgs) {
|
|
124
|
+
return { min: def.numArgs.min, max: def.numArgs.max };
|
|
125
|
+
}
|
|
126
|
+
if (def.type === 'boolean' || (def.action !== undefined && VALUELESS_ACTIONS.has(def.action))) {
|
|
127
|
+
return { min: 0, max: 0 };
|
|
128
|
+
}
|
|
129
|
+
return { min: 1, max: 1 };
|
|
153
130
|
}
|
|
154
|
-
/**
|
|
155
|
-
function
|
|
156
|
-
if (
|
|
157
|
-
return undefined;
|
|
158
|
-
}
|
|
159
|
-
const flagName = token.slice(2);
|
|
160
|
-
if (!optionalValueFlags.has(flagName) || !isNextTokenAFlag(nextToken)) {
|
|
131
|
+
/** The stderr notice for a deprecated arg, or undefined when it is current. */
|
|
132
|
+
function deprecationNotice(key, def) {
|
|
133
|
+
if (def.deprecated === undefined || def.deprecated === false) {
|
|
161
134
|
return undefined;
|
|
162
135
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
return { key: entry.key, defaultVal: entry.def.defaultMissingValue ?? true };
|
|
136
|
+
const label = def.type === 'positional' ? `<${def.valueName ?? key}>` : `--${def.long ?? key}`;
|
|
137
|
+
const reason = typeof def.deprecated === 'string' ? `: ${def.deprecated}` : '';
|
|
138
|
+
const instead = def.replacedBy === undefined ? '' : `; use '--${def.replacedBy}' instead`;
|
|
139
|
+
return `'${label}' is deprecated${reason}${instead}`;
|
|
168
140
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
141
|
+
function makeSpec(key, def, argsOverrideSelf) {
|
|
142
|
+
const { min, max } = valueCounts(def);
|
|
143
|
+
const isAppend = def.action === 'append';
|
|
144
|
+
const forced = def.action === 'setTrue' ? true : def.action === 'setFalse' ? false : undefined;
|
|
145
|
+
const triggers = def.action === 'help' || def.action === 'helpShort' || def.action === 'helpLong'
|
|
146
|
+
? def.action
|
|
147
|
+
: def.action === 'version'
|
|
148
|
+
? 'version'
|
|
149
|
+
: undefined;
|
|
150
|
+
return {
|
|
151
|
+
forced,
|
|
152
|
+
triggers,
|
|
153
|
+
deprecation: deprecationNotice(key, def),
|
|
154
|
+
key,
|
|
155
|
+
def,
|
|
156
|
+
long: def.long ?? key,
|
|
157
|
+
camel: kebabToCamel(key),
|
|
158
|
+
repeatIsError: !isAppend && !argsOverrideSelf && def.overridesWith === undefined,
|
|
159
|
+
isBool: def.type === 'boolean' && !VALUELESS_ACTIONS.has(def.action ?? ''),
|
|
160
|
+
isCount: def.action === 'count',
|
|
161
|
+
isAppend,
|
|
162
|
+
min,
|
|
163
|
+
max,
|
|
164
|
+
};
|
|
184
165
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
*/
|
|
189
|
-
function resolveFlag(token, longMap, shortMap) {
|
|
190
|
-
if (token.startsWith('--') && token.length > 2) {
|
|
191
|
-
const flagName = token.includes('=') ? token.slice(2, token.indexOf('=')) : token.slice(2);
|
|
192
|
-
const entry = longMap.get(flagName);
|
|
193
|
-
return entry ? { key: entry.key, def: entry.def } : undefined;
|
|
166
|
+
function registerAliases(aliases, spec, longs, shorts) {
|
|
167
|
+
if (!aliases) {
|
|
168
|
+
return;
|
|
194
169
|
}
|
|
195
|
-
|
|
196
|
-
|
|
170
|
+
for (const alias of aliases) {
|
|
171
|
+
if (alias.length === 1) {
|
|
172
|
+
shorts.set(alias, spec);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
longs.set(alias, { spec, negated: false });
|
|
176
|
+
}
|
|
197
177
|
}
|
|
198
|
-
return undefined;
|
|
199
178
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
179
|
+
function buildSpec(command) {
|
|
180
|
+
const argsDef = command.args ?? {};
|
|
181
|
+
const longs = new Map();
|
|
182
|
+
const shorts = new Map();
|
|
183
|
+
const positionals = [];
|
|
184
|
+
const all = [];
|
|
185
|
+
const byKey = {};
|
|
186
|
+
let allowsNegative = false;
|
|
187
|
+
const cmdAllowHyphen = command.meta.allowHyphenValues === true;
|
|
188
|
+
const argsOverrideSelf = command.meta.argsOverrideSelf === true;
|
|
189
|
+
let hasOverrides = false;
|
|
190
|
+
if (command.meta.allowNegativeNumbers) {
|
|
191
|
+
allowsNegative = true;
|
|
192
|
+
}
|
|
193
|
+
for (const key of Object.keys(argsDef)) {
|
|
194
|
+
const def = argsDef[key];
|
|
195
|
+
if (def.allowNegativeNumbers) {
|
|
196
|
+
allowsNegative = true;
|
|
197
|
+
}
|
|
198
|
+
if (def.overridesWith !== undefined) {
|
|
199
|
+
hasOverrides = true;
|
|
200
|
+
}
|
|
201
|
+
const spec = makeSpec(key, def, argsOverrideSelf);
|
|
202
|
+
all.push(spec);
|
|
203
|
+
byKey[key] = spec;
|
|
204
|
+
if (def.type === 'positional') {
|
|
205
|
+
positionals.push(spec);
|
|
220
206
|
continue;
|
|
221
207
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (stripped) {
|
|
226
|
-
optionalDefaults.set(stripped.key, stripped.defaultVal);
|
|
227
|
-
continue;
|
|
208
|
+
longs.set(spec.long, { spec, negated: false });
|
|
209
|
+
if (spec.isBool) {
|
|
210
|
+
longs.set(`no-${spec.long}`, { spec, negated: true });
|
|
228
211
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (strippedShort) {
|
|
232
|
-
optionalDefaults.set(strippedShort.key, strippedShort.defaultVal);
|
|
233
|
-
continue;
|
|
212
|
+
if (def.short && def.short.length === 1) {
|
|
213
|
+
shorts.set(def.short, spec);
|
|
234
214
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
215
|
+
registerAliases(def.alias, spec, longs, shorts);
|
|
216
|
+
registerAliases(def.visibleAlias, spec, longs, shorts);
|
|
217
|
+
}
|
|
218
|
+
// Built-in flags never displace a user-defined arg of the same name.
|
|
219
|
+
const helpSpec = {
|
|
220
|
+
key: 'help', def: { type: 'boolean' }, long: 'help', camel: 'help',
|
|
221
|
+
repeatIsError: false,
|
|
222
|
+
isBool: true, isCount: false, isAppend: false, min: 0, max: 0, builtin: 'help',
|
|
223
|
+
};
|
|
224
|
+
const versionSpec = {
|
|
225
|
+
key: 'version', def: { type: 'boolean' }, long: 'version', camel: 'version',
|
|
226
|
+
repeatIsError: false,
|
|
227
|
+
isBool: true, isCount: false, isAppend: false, min: 0, max: 0, builtin: 'version',
|
|
228
|
+
};
|
|
229
|
+
if (command.meta.disableHelpFlag !== true) {
|
|
230
|
+
if (!longs.has('help')) {
|
|
231
|
+
longs.set('help', { spec: helpSpec, negated: false });
|
|
232
|
+
}
|
|
233
|
+
if (!shorts.has('h')) {
|
|
234
|
+
shorts.set('h', helpSpec);
|
|
251
235
|
}
|
|
252
|
-
processedArgs.push(token);
|
|
253
236
|
}
|
|
254
|
-
|
|
237
|
+
// clap only offers --version where a version exists, either on this command
|
|
238
|
+
// or propagated down from an ancestor.
|
|
239
|
+
if (command.meta.disableVersionFlag !== true && command.meta.version !== undefined) {
|
|
240
|
+
if (!longs.has('version')) {
|
|
241
|
+
longs.set('version', { spec: versionSpec, negated: false });
|
|
242
|
+
}
|
|
243
|
+
if (!shorts.has('V')) {
|
|
244
|
+
shorts.set('V', versionSpec);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (command.meta.helpExpected) {
|
|
248
|
+
const undocumented = all
|
|
249
|
+
.filter((spec) => !spec.def.hidden && !spec.def.description && !spec.def.longDescription)
|
|
250
|
+
.map((spec) => spec.key);
|
|
251
|
+
if (undocumented.length > 0) {
|
|
252
|
+
throw new CliParseError(`helpExpected is set but these arguments have no description: ${undocumented.join(', ')}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// An explicit index overrides declaration order; unindexed positionals keep
|
|
256
|
+
// their relative order after the indexed ones are placed.
|
|
257
|
+
if (positionals.some((spec) => spec.def.index !== undefined)) {
|
|
258
|
+
positionals.sort((a, b) => (a.def.index ?? Number.MAX_SAFE_INTEGER) - (b.def.index ?? Number.MAX_SAFE_INTEGER));
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
longs,
|
|
262
|
+
shorts,
|
|
263
|
+
positionals,
|
|
264
|
+
indexedPositionals: positionals.reduce((n, spec) => (spec.def.last ? n : n + 1), 0),
|
|
265
|
+
all,
|
|
266
|
+
byKey,
|
|
267
|
+
command,
|
|
268
|
+
anySubcommands: hasSubCommands(command),
|
|
269
|
+
inferSubcommands: command.meta.inferSubcommands === true,
|
|
270
|
+
allowExternal: command.meta.allowExternalSubcommands === true,
|
|
271
|
+
subcommandPrecedence: command.meta.subcommandPrecedenceOverArg === true,
|
|
272
|
+
allowMissingPositional: command.meta.allowMissingPositional === true,
|
|
273
|
+
argsOverrideSelf,
|
|
274
|
+
ignoreErrors: command.meta.ignoreErrors === true,
|
|
275
|
+
dontDelimitTrailingValues: command.meta.dontDelimitTrailingValues === true,
|
|
276
|
+
hasOverrides,
|
|
277
|
+
cmdAllowHyphen,
|
|
278
|
+
allowsNegative,
|
|
279
|
+
inferLong: command.meta.inferLongArgs === true,
|
|
280
|
+
};
|
|
255
281
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
282
|
+
function getSpec(command) {
|
|
283
|
+
let spec = specCache.get(command);
|
|
284
|
+
if (spec === undefined) {
|
|
285
|
+
spec = buildSpec(command);
|
|
286
|
+
specCache.set(command, spec);
|
|
287
|
+
}
|
|
288
|
+
return spec;
|
|
289
|
+
}
|
|
290
|
+
// ---- Value Coercion ----
|
|
291
|
+
export function coerceValue(value, def, argName) {
|
|
259
292
|
if (typeof def.valueParser === 'function') {
|
|
260
293
|
try {
|
|
261
294
|
const parsed = def.valueParser(value);
|
|
262
|
-
// Ensure the result is a valid ParseResult value type
|
|
263
295
|
if (typeof parsed === 'string' ||
|
|
264
296
|
typeof parsed === 'number' ||
|
|
265
297
|
typeof parsed === 'boolean') {
|
|
@@ -291,225 +323,488 @@ function coerceValue(value, def, argName) {
|
|
|
291
323
|
}
|
|
292
324
|
}
|
|
293
325
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
*/
|
|
299
|
-
function
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
}
|
|
326
|
+
/** Human-readable name for this arg in error messages. */
|
|
327
|
+
function displayName(spec) {
|
|
328
|
+
return spec.def.type === 'positional' ? `<${spec.def.valueName ?? spec.key}>` : `--${spec.long}`;
|
|
329
|
+
}
|
|
330
|
+
/** Record that an arg was explicitly given, keeping the occurrence order. */
|
|
331
|
+
function markSet(state, key, spec) {
|
|
332
|
+
state.explicitlySet.add(key);
|
|
333
|
+
state.valueSources.set(key, 'cli');
|
|
334
|
+
state.order?.set(key, state.seq++);
|
|
335
|
+
if (spec?.deprecation !== undefined && !state.warned.has(key)) {
|
|
336
|
+
state.warned.add(key);
|
|
337
|
+
state.warnings.push(spec.deprecation);
|
|
308
338
|
}
|
|
309
|
-
return map;
|
|
310
339
|
}
|
|
311
|
-
/**
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
function detectSubcommand(positionals, command) {
|
|
316
|
-
if (!command.subCommands) {
|
|
317
|
-
return undefined;
|
|
340
|
+
/** Whether a token can serve as a value for this arg rather than starting a new flag. */
|
|
341
|
+
function isValueToken(token, spec, cmdSpec) {
|
|
342
|
+
if (token.length === 0 || token.charCodeAt(0) !== HYPHEN) {
|
|
343
|
+
return true;
|
|
318
344
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
345
|
+
if (token === '-') {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
if (token === '--') {
|
|
349
|
+
return false;
|
|
325
350
|
}
|
|
326
|
-
|
|
351
|
+
if (spec.def.allowHyphenValues || cmdSpec.cmdAllowHyphen) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
return ((spec.def.allowNegativeNumbers || cmdSpec.allowsNegative) && NEGATIVE_NUMBER.test(token));
|
|
355
|
+
}
|
|
356
|
+
/** Record a flag occurrence that carries no value: booleans and counts. */
|
|
357
|
+
function applyFlagOnly(spec, negated, state) {
|
|
358
|
+
if (spec.builtin === 'help' || spec.triggers === 'help' || spec.triggers === 'helpLong') {
|
|
359
|
+
state.helpRequested = true;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (spec.triggers === 'helpShort') {
|
|
363
|
+
state.helpRequested = true;
|
|
364
|
+
state.helpIsShort = true;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (spec.builtin === 'version' || spec.triggers === 'version') {
|
|
368
|
+
state.versionRequested = true;
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (spec.forced !== undefined) {
|
|
372
|
+
state.result[spec.key] = negated ? !spec.forced : spec.forced;
|
|
373
|
+
markSet(state, spec.key, spec);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (spec.isCount) {
|
|
377
|
+
const previous = state.result[spec.key];
|
|
378
|
+
state.result[spec.key] = (typeof previous === 'number' ? previous : 0) + 1;
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
state.result[spec.key] = !negated;
|
|
382
|
+
}
|
|
383
|
+
markSet(state, spec.key, spec);
|
|
384
|
+
}
|
|
385
|
+
/** Store one or more parsed values for an arg, honouring the append action. */
|
|
386
|
+
function applyValues(spec, values, state) {
|
|
387
|
+
const name = displayName(spec);
|
|
388
|
+
// A second occurrence of a single-value arg is an error in clap unless the
|
|
389
|
+
// command opted into argsOverrideSelf. Reading result is cheaper than the
|
|
390
|
+
// Set, and defaults have not been applied yet, so a value here means a
|
|
391
|
+
// previous occurrence.
|
|
392
|
+
if (spec.repeatIsError && state.result[spec.key] !== undefined) {
|
|
393
|
+
throw new CliParseError(`the argument '${name}' cannot be used multiple times`);
|
|
394
|
+
}
|
|
395
|
+
if (spec.isAppend) {
|
|
396
|
+
const previous = state.result[spec.key];
|
|
397
|
+
const list = Array.isArray(previous) ? previous : [];
|
|
398
|
+
for (const value of values) {
|
|
399
|
+
list.push(String(coerceValue(value, spec.def, name)));
|
|
400
|
+
}
|
|
401
|
+
state.result[spec.key] = list;
|
|
402
|
+
}
|
|
403
|
+
else if (values.length > 1) {
|
|
404
|
+
state.result[spec.key] = values.map((v) => String(coerceValue(v, spec.def, name)));
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
state.result[spec.key] = coerceValue(values[0], spec.def, name);
|
|
408
|
+
}
|
|
409
|
+
markSet(state, spec.key, spec);
|
|
410
|
+
}
|
|
411
|
+
/** Apply defaultMissingValue for a flag whose value was omitted (numArgs.min === 0). */
|
|
412
|
+
function applyMissingValue(spec, state) {
|
|
413
|
+
if (spec.def.defaultMissingValues !== undefined) {
|
|
414
|
+
state.result[spec.key] = [...spec.def.defaultMissingValues];
|
|
415
|
+
markSet(state, spec.key, spec);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const fallback = spec.def.defaultMissingValue ?? true;
|
|
419
|
+
if (spec.isAppend) {
|
|
420
|
+
const previous = state.result[spec.key];
|
|
421
|
+
const list = Array.isArray(previous) ? previous : [];
|
|
422
|
+
list.push(String(fallback));
|
|
423
|
+
state.result[spec.key] = list;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
state.result[spec.key] = fallback;
|
|
427
|
+
}
|
|
428
|
+
markSet(state, spec.key, spec);
|
|
327
429
|
}
|
|
328
|
-
// ---- Separate Rest Args ----
|
|
329
430
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
431
|
+
* Consume up to `spec.max` values for a flag starting at argv[from].
|
|
432
|
+
* Returns the index of the last token consumed.
|
|
332
433
|
*/
|
|
333
|
-
function
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
434
|
+
function consumeValues(spec, argv, from, cmdSpec, state) {
|
|
435
|
+
const values = [];
|
|
436
|
+
let i = from;
|
|
437
|
+
while (values.length < spec.max && i < argv.length) {
|
|
438
|
+
const token = argv[i];
|
|
439
|
+
if (spec.def.valueTerminator !== undefined && token === spec.def.valueTerminator) {
|
|
440
|
+
i++;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
if (!isValueToken(token, spec, cmdSpec)) {
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
if (cmdSpec.subcommandPrecedence &&
|
|
447
|
+
values.length >= spec.min &&
|
|
448
|
+
resolveSubcommand(cmdSpec, token) !== undefined) {
|
|
338
449
|
break;
|
|
339
450
|
}
|
|
451
|
+
values.push(token);
|
|
452
|
+
i++;
|
|
340
453
|
}
|
|
341
|
-
if (
|
|
342
|
-
|
|
454
|
+
if (values.length < spec.min) {
|
|
455
|
+
if (values.length === 0 && spec.min === 0) {
|
|
456
|
+
applyMissingValue(spec, state);
|
|
457
|
+
return i - 1;
|
|
458
|
+
}
|
|
459
|
+
throw new CliParseError(spec.min === 1
|
|
460
|
+
? `a value is required for '${displayName(spec)}' but none was supplied`
|
|
461
|
+
: `the argument '${displayName(spec)}' requires at least ${String(spec.min)} values but ${String(values.length)} were provided`);
|
|
343
462
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
463
|
+
if (values.length === 0) {
|
|
464
|
+
applyMissingValue(spec, state);
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
applyValues(spec, values, state);
|
|
468
|
+
}
|
|
469
|
+
return i - 1;
|
|
351
470
|
}
|
|
352
|
-
// ---- Infer Long Args ----
|
|
353
471
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
472
|
+
* Resolve an unknown long flag by unique prefix match (inferLongArgs).
|
|
473
|
+
* Negated (`--no-x`) entries are excluded so `--n` cannot silently negate.
|
|
356
474
|
*/
|
|
357
|
-
function
|
|
358
|
-
|
|
359
|
-
for (const [
|
|
360
|
-
if (
|
|
361
|
-
|
|
475
|
+
function inferLongEntry(cmdSpec, name) {
|
|
476
|
+
let match;
|
|
477
|
+
for (const [candidate, entry] of cmdSpec.longs) {
|
|
478
|
+
if (entry.negated || !candidate.startsWith(name)) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (match !== undefined) {
|
|
482
|
+
return undefined;
|
|
362
483
|
}
|
|
484
|
+
match = entry;
|
|
363
485
|
}
|
|
364
|
-
return
|
|
486
|
+
return match;
|
|
365
487
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
for (const
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
488
|
+
/** Build the name and flag lookup maps, once, on the first token that needs them. */
|
|
489
|
+
function buildSubcommandMaps(cmdSpec) {
|
|
490
|
+
const names = new Map();
|
|
491
|
+
const flags = new Map();
|
|
492
|
+
const subs = subCommandsOf(cmdSpec.command);
|
|
493
|
+
for (const name of Object.keys(subs)) {
|
|
494
|
+
names.set(name, name);
|
|
495
|
+
const subMeta = subs[name].meta;
|
|
496
|
+
for (const alias of [...(subMeta.aliases ?? []), ...(subMeta.hiddenAliases ?? [])]) {
|
|
497
|
+
names.set(alias, name);
|
|
498
|
+
}
|
|
499
|
+
for (const form of [
|
|
500
|
+
...(subMeta.shortFlag === undefined ? [] : [subMeta.shortFlag]),
|
|
501
|
+
...(subMeta.shortFlagAliases ?? []),
|
|
502
|
+
...(subMeta.visibleShortFlagAliases ?? []),
|
|
503
|
+
]) {
|
|
504
|
+
flags.set(`-${form}`, name);
|
|
505
|
+
}
|
|
506
|
+
for (const form of [
|
|
507
|
+
...(subMeta.longFlag === undefined ? [] : [subMeta.longFlag]),
|
|
508
|
+
...(subMeta.longFlagAliases ?? []),
|
|
509
|
+
...(subMeta.visibleLongFlagAliases ?? []),
|
|
510
|
+
]) {
|
|
511
|
+
flags.set(`--${form}`, name);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
cmdSpec.subcommands = names;
|
|
515
|
+
cmdSpec.subcommandFlags = flags;
|
|
516
|
+
}
|
|
517
|
+
/** The subcommand named by a flag form like `-S`, or undefined. */
|
|
518
|
+
function subcommandForFlag(cmdSpec, token) {
|
|
519
|
+
if (!cmdSpec.anySubcommands) {
|
|
520
|
+
return undefined;
|
|
378
521
|
}
|
|
379
|
-
|
|
522
|
+
if (cmdSpec.subcommandFlags === undefined) {
|
|
523
|
+
buildSubcommandMaps(cmdSpec);
|
|
524
|
+
}
|
|
525
|
+
return cmdSpec.subcommandFlags.get(token);
|
|
380
526
|
}
|
|
381
|
-
// ---- Main Parser ----
|
|
382
527
|
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* Uses node:util parseArgs for core tokenizing, then layers on all clap-ts features.
|
|
528
|
+
* Resolve a bare token to a canonical subcommand name, by exact match on the
|
|
529
|
+
* name or an alias, then by unique prefix when inferSubcommands is on.
|
|
386
530
|
*/
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
catch (error) {
|
|
407
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
408
|
-
throw new CliParseError(msg);
|
|
409
|
-
}
|
|
410
|
-
const { values, positionals: rawPositionals } = parsed;
|
|
411
|
-
const result = {};
|
|
412
|
-
const unknown = [];
|
|
413
|
-
const explicitlySet = new Set();
|
|
414
|
-
// Extract help/version and detect short vs long help
|
|
415
|
-
const helpRequested = values['help'] === true;
|
|
416
|
-
const helpIsShort = helpRequested && rawArgs.some((a) => a === '-h');
|
|
417
|
-
const versionRequested = values['version'] === true;
|
|
418
|
-
// Process each parsed value back through our ArgDef system
|
|
419
|
-
for (const [parsedKey, rawValue] of Object.entries(values)) {
|
|
420
|
-
if (parsedKey === 'help' || parsedKey === 'version') {
|
|
531
|
+
function resolveSubcommand(cmdSpec, token) {
|
|
532
|
+
if (!cmdSpec.anySubcommands) {
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
if (cmdSpec.subcommands === undefined) {
|
|
536
|
+
buildSubcommandMaps(cmdSpec);
|
|
537
|
+
}
|
|
538
|
+
const map = cmdSpec.subcommands;
|
|
539
|
+
const exact = map.get(token);
|
|
540
|
+
if (exact !== undefined) {
|
|
541
|
+
return exact;
|
|
542
|
+
}
|
|
543
|
+
if (!cmdSpec.inferSubcommands) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
let match;
|
|
547
|
+
for (const [candidate, canonical] of map) {
|
|
548
|
+
if (!candidate.startsWith(token)) {
|
|
421
549
|
continue;
|
|
422
550
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
551
|
+
if (match !== undefined && match !== canonical) {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
match = canonical;
|
|
555
|
+
}
|
|
556
|
+
return match;
|
|
557
|
+
}
|
|
558
|
+
/** Handle a `--long`, `--long=value` or `--no-long` token. Returns the last index consumed. */
|
|
559
|
+
function handleLong(token, argv, i, cmdSpec, state) {
|
|
560
|
+
const eq = token.indexOf('=');
|
|
561
|
+
const name = eq === -1 ? token.slice(2) : token.slice(2, eq);
|
|
562
|
+
let entry = cmdSpec.longs.get(name);
|
|
563
|
+
if (entry === undefined && cmdSpec.inferLong) {
|
|
564
|
+
entry = inferLongEntry(cmdSpec, name);
|
|
565
|
+
}
|
|
566
|
+
if (entry === undefined) {
|
|
567
|
+
// Only now is it worth building the subcommand maps: an unrecognised flag
|
|
568
|
+
// may still be a flag-invoked subcommand like `pacman --sync`.
|
|
569
|
+
const flagSub = subcommandForFlag(cmdSpec, token);
|
|
570
|
+
if (flagSub !== undefined) {
|
|
571
|
+
state.subCommand = flagSub;
|
|
572
|
+
state.subCommandArgs = argv.slice(i + 1);
|
|
573
|
+
return i;
|
|
574
|
+
}
|
|
575
|
+
state.unknown.push(`--${name}`);
|
|
576
|
+
return i;
|
|
577
|
+
}
|
|
578
|
+
const { spec, negated } = entry;
|
|
579
|
+
if (eq !== -1) {
|
|
580
|
+
const inline = token.slice(eq + 1);
|
|
581
|
+
if (spec.builtin !== undefined || spec.isCount) {
|
|
582
|
+
applyFlagOnly(spec, negated, state);
|
|
583
|
+
return i;
|
|
584
|
+
}
|
|
585
|
+
if (spec.isBool && negated) {
|
|
586
|
+
// `--no-verbose=true` means "negate", so invert whatever was supplied.
|
|
587
|
+
state.result[spec.key] = coerceValue(inline, spec.def, `--${name}`) === false;
|
|
588
|
+
markSet(state, spec.key, spec);
|
|
589
|
+
return i;
|
|
590
|
+
}
|
|
591
|
+
applyValues(spec, [inline], state);
|
|
592
|
+
return i;
|
|
593
|
+
}
|
|
594
|
+
if (spec.max === 0) {
|
|
595
|
+
applyFlagOnly(spec, negated, state);
|
|
596
|
+
return i;
|
|
597
|
+
}
|
|
598
|
+
if (spec.def.requireEquals) {
|
|
599
|
+
if (spec.min === 0) {
|
|
600
|
+
applyMissingValue(spec, state);
|
|
601
|
+
return i;
|
|
602
|
+
}
|
|
603
|
+
throw new CliParseError(`equal sign is needed when assigning values to '${displayName(spec)}'`);
|
|
604
|
+
}
|
|
605
|
+
return consumeValues(spec, argv, i + 1, cmdSpec, state);
|
|
606
|
+
}
|
|
607
|
+
/** Handle a `-abc`, `-p80`, `-p=80` or `-p 80` token. Returns the last index consumed. */
|
|
608
|
+
function handleShort(token, argv, i, cmdSpec, state) {
|
|
609
|
+
for (let c = 1; c < token.length; c++) {
|
|
610
|
+
const flag = token[c];
|
|
611
|
+
const spec = cmdSpec.shorts.get(flag);
|
|
612
|
+
if (spec === undefined) {
|
|
613
|
+
const flagSub = subcommandForFlag(cmdSpec, token);
|
|
614
|
+
if (flagSub !== undefined) {
|
|
615
|
+
state.subCommand = flagSub;
|
|
616
|
+
state.subCommandArgs = argv.slice(i + 1);
|
|
617
|
+
return i;
|
|
431
618
|
}
|
|
619
|
+
state.unknown.push(`-${flag}`);
|
|
620
|
+
return i;
|
|
432
621
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
applyValue(entry.key, entry.def, rawValue, result, explicitlySet);
|
|
622
|
+
if (spec.builtin === 'help') {
|
|
623
|
+
state.helpRequested = true;
|
|
624
|
+
state.helpIsShort = true;
|
|
437
625
|
continue;
|
|
438
626
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
applyValue(shortEntry.key, shortEntry.def, rawValue, result, explicitlySet);
|
|
627
|
+
if (spec.builtin === 'version') {
|
|
628
|
+
state.versionRequested = true;
|
|
629
|
+
state.versionIsShort = true;
|
|
443
630
|
continue;
|
|
444
631
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
632
|
+
if (spec.max === 0) {
|
|
633
|
+
applyFlagOnly(spec, false, state);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
// A value-taking short flag takes the rest of the cluster, or the next token.
|
|
637
|
+
if (c + 1 < token.length) {
|
|
638
|
+
// clap strips a single leading '=' so `-o=v` and `-ov` agree.
|
|
639
|
+
const attached = token.charCodeAt(c + 1) === 61 ? token.slice(c + 2) : token.slice(c + 1);
|
|
640
|
+
applyValues(spec, [attached], state);
|
|
641
|
+
return i;
|
|
642
|
+
}
|
|
643
|
+
if (spec.def.requireEquals) {
|
|
644
|
+
if (spec.min === 0) {
|
|
645
|
+
applyMissingValue(spec, state);
|
|
646
|
+
return i;
|
|
451
647
|
}
|
|
648
|
+
throw new CliParseError(`equal sign is needed when assigning values to '${displayName(spec)}'`);
|
|
452
649
|
}
|
|
453
|
-
|
|
650
|
+
return consumeValues(spec, argv, i + 1, cmdSpec, state);
|
|
454
651
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
652
|
+
return i;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Consume one token: a flag-invoked subcommand, a long or short flag, a
|
|
656
|
+
* subcommand boundary, or a positional. Returns the last index consumed.
|
|
657
|
+
*/
|
|
658
|
+
function handleToken(token, rawArgs, i, cmdSpec, state) {
|
|
659
|
+
if (token.length > 1 && token.charCodeAt(0) === HYPHEN) {
|
|
660
|
+
if (token.charCodeAt(1) === HYPHEN) {
|
|
661
|
+
return handleLong(token, rawArgs, i, cmdSpec, state);
|
|
662
|
+
}
|
|
663
|
+
// A negative number is a value, not a flag cluster, when nothing claims the
|
|
664
|
+
// leading digit as a short flag.
|
|
665
|
+
const isNegativeValue = cmdSpec.allowsNegative && NEGATIVE_NUMBER.test(token) && !cmdSpec.shorts.has(token[1]);
|
|
666
|
+
if (!isNegativeValue) {
|
|
667
|
+
return handleShort(token, rawArgs, i, cmdSpec, state);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (state.positionals.length === 0) {
|
|
671
|
+
const canonical = resolveSubcommand(cmdSpec, token);
|
|
672
|
+
if (canonical !== undefined) {
|
|
673
|
+
state.subCommand = canonical;
|
|
674
|
+
state.subCommandArgs = rawArgs.slice(i + 1);
|
|
675
|
+
return i;
|
|
676
|
+
}
|
|
677
|
+
if (cmdSpec.allowExternal) {
|
|
678
|
+
state.subCommand = token;
|
|
679
|
+
state.subCommandIsExternal = true;
|
|
680
|
+
state.subCommandArgs = rawArgs.slice(i + 1);
|
|
681
|
+
return i;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
state.positionals.push(token);
|
|
685
|
+
return i;
|
|
686
|
+
}
|
|
687
|
+
// ---- Positional Assignment ----
|
|
688
|
+
function assignPositionals(cmdSpec, state) {
|
|
689
|
+
let index = 0;
|
|
690
|
+
// Definitions still waiting for a value, so a leading optional one can be
|
|
691
|
+
// skipped when there are not enough values to go round.
|
|
692
|
+
let remainingDefs = cmdSpec.indexedPositionals;
|
|
693
|
+
for (const spec of cmdSpec.positionals) {
|
|
694
|
+
if (!spec.def.last) {
|
|
695
|
+
remainingDefs--;
|
|
696
|
+
if (cmdSpec.allowMissingPositional &&
|
|
697
|
+
!spec.def.required &&
|
|
698
|
+
state.positionals.length - index <= remainingDefs) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
460
701
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const positionals = [];
|
|
469
|
-
for (let i = 0; i < allPositionals.length; i++) {
|
|
470
|
-
if (subCmdResult && i === subCmdResult.index) {
|
|
702
|
+
if (spec.def.last) {
|
|
703
|
+
// `last` positionals are only fed from tokens after `--`.
|
|
704
|
+
if (state.rest.length > 0) {
|
|
705
|
+
state.result[spec.key] = state.rest[0];
|
|
706
|
+
state.fromRest.add(spec.key);
|
|
707
|
+
markSet(state, spec.key, spec);
|
|
708
|
+
}
|
|
471
709
|
continue;
|
|
472
710
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
711
|
+
if (spec.def.trailingVarArg) {
|
|
712
|
+
if (index < state.positionals.length) {
|
|
713
|
+
state.result[spec.key] = state.positionals.slice(index);
|
|
714
|
+
markSet(state, spec.key, spec);
|
|
715
|
+
index = state.positionals.length;
|
|
716
|
+
}
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (index >= state.positionals.length) {
|
|
720
|
+
continue;
|
|
480
721
|
}
|
|
722
|
+
if (spec.max > 1) {
|
|
723
|
+
const take = Math.min(spec.max, state.positionals.length - index);
|
|
724
|
+
const values = state.positionals.slice(index, index + take);
|
|
725
|
+
state.result[spec.key] = values.map((v) => String(coerceValue(v, spec.def, spec.key)));
|
|
726
|
+
markSet(state, spec.key, spec);
|
|
727
|
+
index += take;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
state.result[spec.key] = coerceValue(state.positionals[index], spec.def, spec.key);
|
|
731
|
+
markSet(state, spec.key, spec);
|
|
732
|
+
index++;
|
|
481
733
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
734
|
+
}
|
|
735
|
+
// ---- Overrides ----
|
|
736
|
+
/**
|
|
737
|
+
* Drop args displaced by a later `overridesWith`. Only command-line
|
|
738
|
+
* occurrences take part, so an env or default value is never overridden.
|
|
739
|
+
*/
|
|
740
|
+
function applyOverrides(cmdSpec, state) {
|
|
741
|
+
const order = state.order;
|
|
742
|
+
if (order === undefined) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
for (const spec of cmdSpec.all) {
|
|
746
|
+
const targets = spec.def.overridesWith;
|
|
747
|
+
if (targets === undefined || !state.explicitlySet.has(spec.key)) {
|
|
490
748
|
continue;
|
|
491
749
|
}
|
|
492
|
-
|
|
493
|
-
if (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
750
|
+
const mine = order.get(spec.key);
|
|
751
|
+
if (mine === undefined) {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
for (const target of targets) {
|
|
755
|
+
if (target === spec.key) {
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const theirs = order.get(target);
|
|
759
|
+
if (theirs === undefined || theirs > mine) {
|
|
760
|
+
continue;
|
|
497
761
|
}
|
|
498
|
-
|
|
762
|
+
delete state.result[target];
|
|
763
|
+
state.explicitlySet.delete(target);
|
|
764
|
+
order.delete(target);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Copy each deprecated arg's value to the arg that replaced it, unless that one
|
|
770
|
+
* was given directly. Lets a rename keep working without the handler caring.
|
|
771
|
+
*/
|
|
772
|
+
function applyReplacements(cmdSpec, state) {
|
|
773
|
+
for (const spec of cmdSpec.all) {
|
|
774
|
+
const target = spec.def.replacedBy;
|
|
775
|
+
if (target === undefined || !state.explicitlySet.has(spec.key)) {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (state.explicitlySet.has(target) || !(target in cmdSpec.byKey)) {
|
|
779
|
+
continue;
|
|
499
780
|
}
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
explicitlySet.add(key);
|
|
781
|
+
const value = state.result[spec.key];
|
|
782
|
+
if (value === undefined) {
|
|
783
|
+
continue;
|
|
504
784
|
}
|
|
785
|
+
state.result[target] = value;
|
|
786
|
+
state.explicitlySet.add(target);
|
|
787
|
+
state.valueSources.set(target, state.valueSources.get(spec.key) ?? 'cli');
|
|
505
788
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
789
|
+
}
|
|
790
|
+
// ---- Defaults, Env, Delimiters ----
|
|
791
|
+
function splitByDelimiter(value, delimiter) {
|
|
792
|
+
if (Array.isArray(value)) {
|
|
793
|
+
const out = [];
|
|
794
|
+
for (const v of value) {
|
|
795
|
+
out.push(...v.split(delimiter));
|
|
796
|
+
}
|
|
797
|
+
return out;
|
|
798
|
+
}
|
|
799
|
+
return value.split(delimiter);
|
|
800
|
+
}
|
|
801
|
+
function applyFallbacks(cmdSpec, state) {
|
|
802
|
+
const { result, explicitlySet } = state;
|
|
803
|
+
for (const spec of cmdSpec.all) {
|
|
804
|
+
const { key, def } = spec;
|
|
510
805
|
if (explicitlySet.has(key)) {
|
|
511
|
-
|
|
512
|
-
|
|
806
|
+
if (def.valueDelimiter &&
|
|
807
|
+
!(cmdSpec.dontDelimitTrailingValues && state.fromRest.has(key))) {
|
|
513
808
|
const value = result[key];
|
|
514
809
|
if (value !== undefined && (typeof value === 'string' || Array.isArray(value))) {
|
|
515
810
|
result[key] = splitByDelimiter(value, def.valueDelimiter);
|
|
@@ -517,103 +812,136 @@ export function parseArgs(rawArgs, command) {
|
|
|
517
812
|
}
|
|
518
813
|
continue;
|
|
519
814
|
}
|
|
520
|
-
// Env var fallback
|
|
521
815
|
if (def.env) {
|
|
522
|
-
const envValue =
|
|
523
|
-
? process.env[def.env]
|
|
524
|
-
: globalThis.Bun.env[def.env];
|
|
816
|
+
const envValue = readEnv(def.env);
|
|
525
817
|
if (envValue !== undefined && envValue !== '') {
|
|
526
818
|
result[key] = def.valueDelimiter
|
|
527
819
|
? envValue.split(def.valueDelimiter)
|
|
528
820
|
: coerceValue(envValue, def, `env:${def.env}`);
|
|
529
821
|
explicitlySet.add(key);
|
|
822
|
+
state.valueSources.set(key, 'env');
|
|
530
823
|
continue;
|
|
531
824
|
}
|
|
532
825
|
}
|
|
533
|
-
// Conditional default (defaultValueIf)
|
|
534
826
|
if (def.defaultValueIf) {
|
|
535
827
|
const [otherKey, otherValue, conditionalDefault] = def.defaultValueIf;
|
|
536
828
|
if (String(result[otherKey]) === String(otherValue)) {
|
|
537
829
|
result[key] = conditionalDefault;
|
|
830
|
+
state.valueSources.set(key, 'default');
|
|
538
831
|
continue;
|
|
539
832
|
}
|
|
540
833
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
result[
|
|
834
|
+
if (def.defaultValueIfs) {
|
|
835
|
+
let matched = false;
|
|
836
|
+
for (const [otherKey, otherValue, conditionalDefault] of def.defaultValueIfs) {
|
|
837
|
+
if (String(result[otherKey]) === String(otherValue)) {
|
|
838
|
+
result[key] = conditionalDefault;
|
|
839
|
+
state.valueSources.set(key, 'default');
|
|
840
|
+
matched = true;
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
545
843
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
typeof def.default === 'boolean') {
|
|
549
|
-
result[key] = def.default;
|
|
844
|
+
if (matched) {
|
|
845
|
+
continue;
|
|
550
846
|
}
|
|
551
847
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
camelResult[camelKey] = value;
|
|
558
|
-
if (key !== camelKey) {
|
|
559
|
-
camelResult[key] = value;
|
|
848
|
+
if (def.default !== undefined) {
|
|
849
|
+
result[key] = Array.isArray(def.default)
|
|
850
|
+
? [...def.default]
|
|
851
|
+
: def.default;
|
|
852
|
+
state.valueSources.set(key, 'default');
|
|
560
853
|
}
|
|
561
854
|
}
|
|
562
|
-
return {
|
|
563
|
-
args: camelResult,
|
|
564
|
-
positionals,
|
|
565
|
-
rest,
|
|
566
|
-
subCommand,
|
|
567
|
-
helpRequested,
|
|
568
|
-
helpIsShort,
|
|
569
|
-
versionRequested,
|
|
570
|
-
unknown,
|
|
571
|
-
explicitlySet,
|
|
572
|
-
};
|
|
573
855
|
}
|
|
574
|
-
// ----
|
|
856
|
+
// ---- Main Parser ----
|
|
575
857
|
/**
|
|
576
|
-
*
|
|
577
|
-
*
|
|
858
|
+
* Parse raw argument tokens against a command definition.
|
|
859
|
+
*
|
|
860
|
+
* Stops at the first bare token matching a subcommand name or alias; the
|
|
861
|
+
* remaining tokens are returned as `subCommandArgs` for the caller to parse
|
|
862
|
+
* against that subcommand.
|
|
578
863
|
*/
|
|
579
|
-
function
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
864
|
+
export function parseArgs(rawArgs, command) {
|
|
865
|
+
const cmdSpec = getSpec(command);
|
|
866
|
+
const state = {
|
|
867
|
+
result: {},
|
|
868
|
+
explicitlySet: new Set(),
|
|
869
|
+
valueSources: new Map(),
|
|
870
|
+
errors: [],
|
|
871
|
+
warnings: [],
|
|
872
|
+
warned: new Set(),
|
|
873
|
+
fromRest: new Set(),
|
|
874
|
+
order: cmdSpec.hasOverrides ? new Map() : undefined,
|
|
875
|
+
seq: 0,
|
|
876
|
+
unknown: [],
|
|
877
|
+
positionals: [],
|
|
878
|
+
rest: [],
|
|
879
|
+
helpRequested: false,
|
|
880
|
+
helpIsShort: false,
|
|
881
|
+
versionRequested: false,
|
|
882
|
+
versionIsShort: false,
|
|
883
|
+
subCommandIsExternal: false,
|
|
884
|
+
subCommandArgs: [],
|
|
885
|
+
};
|
|
886
|
+
let i = 0;
|
|
887
|
+
for (; i < rawArgs.length; i++) {
|
|
888
|
+
const token = rawArgs[i];
|
|
889
|
+
if (token === '--') {
|
|
890
|
+
for (let r = i + 1; r < rawArgs.length; r++) {
|
|
891
|
+
state.rest.push(rawArgs[r]);
|
|
892
|
+
}
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
// ignoreErrors keeps going after a bad token, collecting the message, the
|
|
896
|
+
// way clap's Command::ignore_errors does.
|
|
897
|
+
if (cmdSpec.ignoreErrors) {
|
|
898
|
+
try {
|
|
899
|
+
i = handleToken(token, rawArgs, i, cmdSpec, state);
|
|
900
|
+
}
|
|
901
|
+
catch (error) {
|
|
902
|
+
state.errors.push(error instanceof Error ? error.message : String(error));
|
|
903
|
+
}
|
|
583
904
|
}
|
|
584
905
|
else {
|
|
585
|
-
|
|
906
|
+
i = handleToken(token, rawArgs, i, cmdSpec, state);
|
|
907
|
+
}
|
|
908
|
+
if (state.subCommand !== undefined) {
|
|
909
|
+
break;
|
|
586
910
|
}
|
|
587
|
-
explicitlySet.add(key);
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
if (def.type === 'boolean') {
|
|
591
|
-
result[key] = rawValue === true;
|
|
592
|
-
explicitlySet.add(key);
|
|
593
|
-
return;
|
|
594
911
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
912
|
+
assignPositionals(cmdSpec, state);
|
|
913
|
+
applyOverrides(cmdSpec, state);
|
|
914
|
+
applyReplacements(cmdSpec, state);
|
|
915
|
+
applyFallbacks(cmdSpec, state);
|
|
916
|
+
// Expose both the declared key and its camelCase form.
|
|
917
|
+
const args = {};
|
|
918
|
+
for (const spec of cmdSpec.all) {
|
|
919
|
+
const value = state.result[spec.key];
|
|
920
|
+
if (value === undefined) {
|
|
921
|
+
continue;
|
|
599
922
|
}
|
|
600
|
-
|
|
601
|
-
|
|
923
|
+
args[spec.camel] = value;
|
|
924
|
+
if (spec.key !== spec.camel) {
|
|
925
|
+
args[spec.key] = value;
|
|
602
926
|
}
|
|
603
|
-
explicitlySet.add(key);
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
// Single string/number/enum value
|
|
607
|
-
if (typeof rawValue === 'string') {
|
|
608
|
-
result[key] = coerceValue(rawValue, def, `--${def.long ?? key}`);
|
|
609
|
-
explicitlySet.add(key);
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
// Boolean value for a string-typed arg (edge case from parseArgs)
|
|
613
|
-
if (typeof rawValue === 'boolean' && rawValue) {
|
|
614
|
-
result[key] = def.defaultMissingValue ?? true;
|
|
615
|
-
explicitlySet.add(key);
|
|
616
927
|
}
|
|
928
|
+
return {
|
|
929
|
+
args,
|
|
930
|
+
positionals: state.positionals,
|
|
931
|
+
rest: state.rest,
|
|
932
|
+
subCommand: state.subCommand,
|
|
933
|
+
subCommandIsExternal: state.subCommandIsExternal,
|
|
934
|
+
subCommandArgs: state.subCommandArgs,
|
|
935
|
+
helpRequested: state.helpRequested,
|
|
936
|
+
helpIsShort: state.helpIsShort,
|
|
937
|
+
versionRequested: state.versionRequested,
|
|
938
|
+
versionIsShort: state.versionIsShort,
|
|
939
|
+
unknown: state.unknown,
|
|
940
|
+
errors: state.errors,
|
|
941
|
+
warnings: state.warnings,
|
|
942
|
+
explicitlySet: state.explicitlySet,
|
|
943
|
+
valueSources: state.valueSources,
|
|
944
|
+
};
|
|
617
945
|
}
|
|
618
946
|
// ---- Global Args ----
|
|
619
947
|
/**
|