@guanghechen/commander 3.2.0 → 4.0.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/CHANGELOG.md +19 -0
- package/README.md +2 -5
- package/lib/cjs/index.cjs +501 -597
- package/lib/esm/index.mjs +501 -597
- package/lib/types/index.d.ts +152 -79
- package/package.json +1 -1
package/lib/esm/index.mjs
CHANGED
|
@@ -29,48 +29,147 @@ class DefaultReporter {
|
|
|
29
29
|
console.error(message, ...args);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
const LONG_OPTION_REGEX = /^--[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
33
|
+
const NEGATIVE_OPTION_REGEX = /^--no-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
34
|
+
function kebabToCamelCase(str) {
|
|
35
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
function camelToKebabCase$1(str) {
|
|
38
|
+
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
39
|
+
}
|
|
40
|
+
function tokenizeLongOption(arg, commandPath) {
|
|
41
|
+
const eqIdx = arg.indexOf('=');
|
|
42
|
+
const namePart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
|
|
43
|
+
const valuePart = eqIdx !== -1 ? arg.slice(eqIdx) : '';
|
|
44
|
+
if (namePart.includes('_')) {
|
|
45
|
+
throw new CommanderError('InvalidOptionFormat', `invalid option "${arg}": use '-' instead of '_'`, commandPath);
|
|
46
|
+
}
|
|
47
|
+
const lowerName = namePart.toLowerCase();
|
|
48
|
+
if (lowerName === '--no' || lowerName === '--no-') {
|
|
49
|
+
throw new CommanderError('InvalidNegativeOption', `invalid negative option syntax "${arg}"`, commandPath);
|
|
50
|
+
}
|
|
51
|
+
if (lowerName.startsWith('--no-')) {
|
|
52
|
+
if (valuePart !== '') {
|
|
53
|
+
throw new CommanderError('NegativeOptionWithValue', `"${namePart}" does not accept a value`, commandPath);
|
|
54
|
+
}
|
|
55
|
+
if (!NEGATIVE_OPTION_REGEX.test(lowerName)) {
|
|
56
|
+
throw new CommanderError('InvalidOptionFormat', `invalid option format "${arg}"`, commandPath);
|
|
57
|
+
}
|
|
58
|
+
const camelName = kebabToCamelCase(lowerName.slice(5));
|
|
59
|
+
return {
|
|
60
|
+
original: arg,
|
|
61
|
+
resolved: `--${camelName}=false`,
|
|
62
|
+
name: camelName,
|
|
63
|
+
type: 'long',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (!LONG_OPTION_REGEX.test(lowerName)) {
|
|
67
|
+
throw new CommanderError('InvalidOptionFormat', `invalid option format "${arg}"`, commandPath);
|
|
68
|
+
}
|
|
69
|
+
const camelName = kebabToCamelCase(lowerName.slice(2));
|
|
70
|
+
return {
|
|
71
|
+
original: arg,
|
|
72
|
+
resolved: `--${camelName}${valuePart}`,
|
|
73
|
+
name: camelName,
|
|
74
|
+
type: 'long',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function tokenizeShortOptions(arg, commandPath) {
|
|
78
|
+
if (arg.includes('=')) {
|
|
79
|
+
throw new CommanderError('UnsupportedShortSyntax', `"${arg}" is not supported. Use "-${arg[1]} ${arg.slice(3)}" instead`, commandPath);
|
|
80
|
+
}
|
|
81
|
+
const flags = arg.slice(1);
|
|
82
|
+
return flags.split('').map(flag => ({
|
|
83
|
+
original: `-${flag}`,
|
|
84
|
+
resolved: `-${flag}`,
|
|
85
|
+
name: flag,
|
|
86
|
+
type: 'short',
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
function tokenize(argv, commandPath) {
|
|
90
|
+
const optionTokens = [];
|
|
91
|
+
const restArgs = [];
|
|
92
|
+
let passThrough = false;
|
|
93
|
+
for (const arg of argv) {
|
|
94
|
+
if (arg === '--') {
|
|
95
|
+
passThrough = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (passThrough) {
|
|
99
|
+
restArgs.push(arg);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (arg.startsWith('--')) {
|
|
103
|
+
optionTokens.push(tokenizeLongOption(arg, commandPath));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (arg.startsWith('-') && arg.length > 1) {
|
|
107
|
+
optionTokens.push(...tokenizeShortOptions(arg, commandPath));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
optionTokens.push({
|
|
111
|
+
original: arg,
|
|
112
|
+
resolved: arg,
|
|
113
|
+
name: '',
|
|
114
|
+
type: 'none',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return { optionTokens, restArgs };
|
|
118
|
+
}
|
|
32
119
|
const BUILTIN_HELP_OPTION = {
|
|
33
120
|
long: 'help',
|
|
34
121
|
short: 'h',
|
|
35
122
|
type: 'boolean',
|
|
36
|
-
|
|
123
|
+
args: 'none',
|
|
124
|
+
desc: 'Show help information',
|
|
37
125
|
};
|
|
38
126
|
const BUILTIN_VERSION_OPTION = {
|
|
39
127
|
long: 'version',
|
|
40
128
|
short: 'V',
|
|
41
129
|
type: 'boolean',
|
|
42
|
-
|
|
130
|
+
args: 'none',
|
|
131
|
+
desc: 'Show version number',
|
|
43
132
|
};
|
|
44
133
|
class Command {
|
|
45
134
|
#name;
|
|
46
|
-
#
|
|
135
|
+
#desc;
|
|
47
136
|
#version;
|
|
48
137
|
#helpSubcommandEnabled;
|
|
138
|
+
#reporter;
|
|
139
|
+
#parent;
|
|
49
140
|
#options = [];
|
|
50
141
|
#arguments = [];
|
|
51
|
-
#
|
|
52
|
-
#
|
|
142
|
+
#subcommandsList = [];
|
|
143
|
+
#subcommandsMap = new Map();
|
|
144
|
+
#action = undefined;
|
|
53
145
|
constructor(config) {
|
|
54
146
|
this.#name = config.name ?? '';
|
|
55
|
-
this.#
|
|
147
|
+
this.#desc = config.desc;
|
|
56
148
|
this.#version = config.version;
|
|
57
149
|
this.#helpSubcommandEnabled = config.help ?? false;
|
|
150
|
+
this.#reporter = config.reporter;
|
|
58
151
|
}
|
|
59
152
|
get name() {
|
|
60
|
-
return this.#name;
|
|
153
|
+
return this.#name || undefined;
|
|
61
154
|
}
|
|
62
155
|
get description() {
|
|
63
|
-
return this.#
|
|
156
|
+
return this.#desc;
|
|
64
157
|
}
|
|
65
158
|
get version() {
|
|
66
159
|
return this.#version;
|
|
67
160
|
}
|
|
161
|
+
get parent() {
|
|
162
|
+
return this.#parent;
|
|
163
|
+
}
|
|
68
164
|
get options() {
|
|
69
165
|
return [...this.#options];
|
|
70
166
|
}
|
|
71
167
|
get arguments() {
|
|
72
168
|
return [...this.#arguments];
|
|
73
169
|
}
|
|
170
|
+
get subcommands() {
|
|
171
|
+
return new Map(this.#subcommandsMap);
|
|
172
|
+
}
|
|
74
173
|
option(opt) {
|
|
75
174
|
this.#validateOptionConfig(opt);
|
|
76
175
|
this.#checkOptionUniqueness(opt);
|
|
@@ -90,13 +189,19 @@ class Command {
|
|
|
90
189
|
if (this.#helpSubcommandEnabled && name === 'help') {
|
|
91
190
|
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name when help subcommand is enabled', this.#getCommandPath());
|
|
92
191
|
}
|
|
93
|
-
|
|
192
|
+
if (cmd.#parent && cmd.#parent !== this) {
|
|
193
|
+
throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
|
|
194
|
+
}
|
|
195
|
+
const existing = this.#subcommandsList.find(e => e.command === cmd);
|
|
94
196
|
if (existing) {
|
|
95
197
|
existing.aliases.push(name);
|
|
198
|
+
this.#subcommandsMap.set(name, cmd);
|
|
96
199
|
}
|
|
97
200
|
else {
|
|
98
201
|
cmd.#name = name;
|
|
99
|
-
|
|
202
|
+
cmd.#parent = this;
|
|
203
|
+
this.#subcommandsList.push({ name, aliases: [], command: cmd });
|
|
204
|
+
this.#subcommandsMap.set(name, cmd);
|
|
100
205
|
}
|
|
101
206
|
return this;
|
|
102
207
|
}
|
|
@@ -104,46 +209,42 @@ class Command {
|
|
|
104
209
|
const { argv, envs, reporter } = params;
|
|
105
210
|
try {
|
|
106
211
|
const processedArgv = this.#processHelpSubcommand(argv);
|
|
107
|
-
const
|
|
212
|
+
const routeResult = this.#route(processedArgv);
|
|
213
|
+
const { chain, remaining } = routeResult;
|
|
108
214
|
const leafCommand = chain[chain.length - 1];
|
|
109
|
-
const
|
|
110
|
-
const
|
|
215
|
+
const rootCommand = chain[0];
|
|
216
|
+
const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
|
|
217
|
+
const { optionTokens, restArgs } = tokenizeResult;
|
|
111
218
|
const hasUserHelp = leafCommand.#options.some(o => o.long === 'help');
|
|
112
219
|
const hasUserVersion = leafCommand.#options.some(o => o.long === 'version');
|
|
113
|
-
if (!hasUserHelp &&
|
|
220
|
+
if (!hasUserHelp && this.#hasFlag(optionTokens, 'help', 'h')) {
|
|
114
221
|
console.log(leafCommand.formatHelp());
|
|
115
222
|
return;
|
|
116
223
|
}
|
|
117
|
-
if (!hasUserVersion && leafCommand.#
|
|
118
|
-
|
|
119
|
-
|
|
224
|
+
if (!hasUserVersion && leafCommand === rootCommand && leafCommand.#version) {
|
|
225
|
+
if (this.#hasFlag(optionTokens, 'version', 'V')) {
|
|
226
|
+
console.log(leafCommand.#version);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
120
229
|
}
|
|
121
|
-
const
|
|
230
|
+
const resolveResult = this.#resolve(chain, optionTokens);
|
|
122
231
|
const ctx = {
|
|
123
232
|
cmd: leafCommand,
|
|
124
233
|
envs,
|
|
125
|
-
reporter: reporter ?? new DefaultReporter(),
|
|
234
|
+
reporter: reporter ?? this.#reporter ?? new DefaultReporter(),
|
|
126
235
|
argv,
|
|
127
236
|
};
|
|
128
|
-
this.#
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
237
|
+
const parseResult = this.#parse(chain, resolveResult, ctx, restArgs);
|
|
238
|
+
const actionParams = {
|
|
239
|
+
ctx: parseResult.ctx,
|
|
240
|
+
opts: parseResult.opts,
|
|
241
|
+
args: parseResult.args,
|
|
242
|
+
rawArgs: parseResult.rawArgs,
|
|
243
|
+
};
|
|
132
244
|
if (leafCommand.#action) {
|
|
133
|
-
|
|
134
|
-
await leafCommand.#action(actionParams);
|
|
135
|
-
}
|
|
136
|
-
catch (err) {
|
|
137
|
-
if (err instanceof Error) {
|
|
138
|
-
console.error(`Error: ${err.message}`);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
console.error('Error: action failed');
|
|
142
|
-
}
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
245
|
+
await leafCommand.#runAction(actionParams);
|
|
145
246
|
}
|
|
146
|
-
else if (leafCommand.#
|
|
247
|
+
else if (leafCommand.#subcommandsList.length > 0) {
|
|
147
248
|
console.log(leafCommand.formatHelp());
|
|
148
249
|
}
|
|
149
250
|
else {
|
|
@@ -159,154 +260,33 @@ class Command {
|
|
|
159
260
|
throw err;
|
|
160
261
|
}
|
|
161
262
|
}
|
|
162
|
-
parse(
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const resolverOptions = allOptions.filter(o => o.resolver);
|
|
179
|
-
for (const opt of resolverOptions) {
|
|
180
|
-
const result = opt.resolver(remaining);
|
|
181
|
-
opts[opt.long] = result.value;
|
|
182
|
-
remaining = result.remaining;
|
|
183
|
-
}
|
|
184
|
-
const { optionByLong, optionByShort, booleanOptions } = this.#buildOptionMaps(allOptions, true);
|
|
185
|
-
remaining = this.#normalizeArgv(remaining, booleanOptions);
|
|
186
|
-
let i = 0;
|
|
187
|
-
while (i < remaining.length) {
|
|
188
|
-
const token = remaining[i];
|
|
189
|
-
if (token === '--') {
|
|
190
|
-
rawArgs.push(...remaining.slice(i + 1));
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
if (token.startsWith('--')) {
|
|
194
|
-
i = this.#parseLongOption(remaining, i, optionByLong, opts);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (token.startsWith('-') && token.length > 1) {
|
|
198
|
-
i = this.#parseShortOption(remaining, i, optionByShort, opts);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
rawArgs.push(token);
|
|
202
|
-
i += 1;
|
|
203
|
-
}
|
|
204
|
-
for (const opt of allOptions) {
|
|
205
|
-
if (opt.required && opts[opt.long] === undefined) {
|
|
206
|
-
throw new CommanderError('MissingRequired', `missing required option "--${opt.long}" for command "${this.#getCommandPath()}"`, this.#getCommandPath());
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
for (const opt of allOptions) {
|
|
210
|
-
if (opt.choices && opts[opt.long] !== undefined) {
|
|
211
|
-
const value = opts[opt.long];
|
|
212
|
-
const values = Array.isArray(value) ? value : [value];
|
|
213
|
-
const choices = opt.choices;
|
|
214
|
-
for (const v of values) {
|
|
215
|
-
if (!choices.includes(v)) {
|
|
216
|
-
throw new CommanderError('InvalidChoice', `invalid value "${v}" for option "--${opt.long}". Allowed: ${opt.choices.join(', ')}`, this.#getCommandPath());
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
const { args } = this.#parseArguments(rawArgs);
|
|
222
|
-
return { opts, args, rawArgs };
|
|
223
|
-
}
|
|
224
|
-
shift(tokens) {
|
|
225
|
-
return this.#shiftWithShadowed(tokens, new Set());
|
|
226
|
-
}
|
|
227
|
-
#shiftWithShadowed(tokens, shadowed) {
|
|
228
|
-
const allDirectOptions = this.#getMergedOptions();
|
|
229
|
-
const directOptions = allDirectOptions.filter(o => !shadowed.has(o.long));
|
|
230
|
-
const opts = {};
|
|
231
|
-
for (const opt of directOptions) {
|
|
232
|
-
if (opt.default !== undefined) {
|
|
233
|
-
opts[opt.long] = opt.default;
|
|
234
|
-
}
|
|
235
|
-
else if (opt.type === 'boolean') {
|
|
236
|
-
opts[opt.long] = false;
|
|
237
|
-
}
|
|
238
|
-
else if (opt.type === 'string[]' || opt.type === 'number[]') {
|
|
239
|
-
opts[opt.long] = [];
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
let remaining = [...tokens];
|
|
243
|
-
const resolverOptions = directOptions.filter(o => o.resolver);
|
|
244
|
-
for (const opt of resolverOptions) {
|
|
245
|
-
const result = opt.resolver(remaining);
|
|
246
|
-
opts[opt.long] = result.value;
|
|
247
|
-
remaining = result.remaining;
|
|
248
|
-
}
|
|
249
|
-
const { optionByLong, optionByShort, booleanOptions } = this.#buildOptionMaps(directOptions, true);
|
|
250
|
-
const normalizedTokens = this.#normalizeArgv(remaining, booleanOptions);
|
|
251
|
-
const finalRemaining = [];
|
|
252
|
-
let i = 0;
|
|
253
|
-
while (i < normalizedTokens.length) {
|
|
254
|
-
const token = normalizedTokens[i];
|
|
255
|
-
if (token.startsWith('--')) {
|
|
256
|
-
const consumed = this.#tryConsumeLongOption(normalizedTokens, i, optionByLong, opts);
|
|
257
|
-
if (consumed > 0) {
|
|
258
|
-
i += consumed;
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
finalRemaining.push(token);
|
|
262
|
-
i += 1;
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
if (token.startsWith('-') && token.length > 1) {
|
|
266
|
-
const result = this.#tryConsumeShortOption(normalizedTokens, i, optionByShort, opts);
|
|
267
|
-
if (result.consumed) {
|
|
268
|
-
i = result.nextIdx;
|
|
269
|
-
if (result.remainingToken) {
|
|
270
|
-
finalRemaining.push(result.remainingToken);
|
|
271
|
-
}
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
finalRemaining.push(token);
|
|
275
|
-
i += 1;
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
finalRemaining.push(token);
|
|
279
|
-
i += 1;
|
|
280
|
-
}
|
|
281
|
-
for (const opt of directOptions) {
|
|
282
|
-
if (opt.required && opts[opt.long] === undefined) {
|
|
283
|
-
throw new CommanderError('MissingRequired', `missing required option "--${opt.long}" for command "${this.#getCommandPath()}"`, this.#getCommandPath());
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
for (const opt of directOptions) {
|
|
287
|
-
if (opt.choices && opts[opt.long] !== undefined) {
|
|
288
|
-
const value = opts[opt.long];
|
|
289
|
-
const values = Array.isArray(value) ? value : [value];
|
|
290
|
-
const choices = opt.choices;
|
|
291
|
-
for (const v of values) {
|
|
292
|
-
if (!choices.includes(v)) {
|
|
293
|
-
throw new CommanderError('InvalidChoice', `invalid value "${v}" for option "--${opt.long}". Allowed: ${opt.choices.join(', ')}`, this.#getCommandPath());
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return { opts, remaining: finalRemaining };
|
|
263
|
+
parse(params) {
|
|
264
|
+
const { argv, envs, reporter } = params;
|
|
265
|
+
const processedArgv = this.#processHelpSubcommand(argv);
|
|
266
|
+
const routeResult = this.#route(processedArgv);
|
|
267
|
+
const { chain, remaining } = routeResult;
|
|
268
|
+
const leafCommand = chain[chain.length - 1];
|
|
269
|
+
const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
|
|
270
|
+
const { optionTokens, restArgs } = tokenizeResult;
|
|
271
|
+
const resolveResult = this.#resolve(chain, optionTokens);
|
|
272
|
+
const ctx = {
|
|
273
|
+
cmd: leafCommand,
|
|
274
|
+
envs,
|
|
275
|
+
reporter: reporter ?? this.#reporter ?? new DefaultReporter(),
|
|
276
|
+
argv,
|
|
277
|
+
};
|
|
278
|
+
return this.#parse(chain, resolveResult, ctx, restArgs);
|
|
299
279
|
}
|
|
300
280
|
formatHelp() {
|
|
301
281
|
const lines = [];
|
|
302
282
|
const allOptions = this.#getMergedOptions();
|
|
303
|
-
lines.push(this.#
|
|
283
|
+
lines.push(this.#desc);
|
|
304
284
|
lines.push('');
|
|
305
285
|
const commandPath = this.#getCommandPath();
|
|
306
286
|
let usage = `Usage: ${commandPath}`;
|
|
307
287
|
if (allOptions.length > 0)
|
|
308
288
|
usage += ' [options]';
|
|
309
|
-
if (this.#
|
|
289
|
+
if (this.#subcommandsList.length > 0)
|
|
310
290
|
usage += ' [command]';
|
|
311
291
|
for (const arg of this.#arguments) {
|
|
312
292
|
if (arg.kind === 'required') {
|
|
@@ -325,24 +305,24 @@ class Command {
|
|
|
325
305
|
lines.push('Options:');
|
|
326
306
|
const optLines = [];
|
|
327
307
|
for (const opt of allOptions) {
|
|
308
|
+
const kebabLong = camelToKebabCase$1(opt.long);
|
|
328
309
|
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
329
|
-
sig += `--${
|
|
330
|
-
|
|
331
|
-
if (effectiveType !== 'boolean') {
|
|
310
|
+
sig += `--${kebabLong}`;
|
|
311
|
+
if (opt.args !== 'none') {
|
|
332
312
|
sig += ' <value>';
|
|
333
313
|
}
|
|
334
|
-
let desc = opt.
|
|
335
|
-
if (opt.default !== undefined &&
|
|
314
|
+
let desc = opt.desc;
|
|
315
|
+
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
336
316
|
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
337
317
|
}
|
|
338
318
|
if (opt.choices) {
|
|
339
319
|
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
340
320
|
}
|
|
341
321
|
optLines.push({ sig, desc });
|
|
342
|
-
if (
|
|
322
|
+
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
343
323
|
optLines.push({
|
|
344
|
-
sig: ` --no-${
|
|
345
|
-
desc:
|
|
324
|
+
sig: ` --no-${kebabLong}`,
|
|
325
|
+
desc: `Negate --${kebabLong}`,
|
|
346
326
|
});
|
|
347
327
|
}
|
|
348
328
|
}
|
|
@@ -353,19 +333,19 @@ class Command {
|
|
|
353
333
|
}
|
|
354
334
|
lines.push('');
|
|
355
335
|
}
|
|
356
|
-
const showHelpSubcommand = this.#helpSubcommandEnabled && this.#
|
|
357
|
-
if (this.#
|
|
336
|
+
const showHelpSubcommand = this.#helpSubcommandEnabled && this.#subcommandsList.length > 0;
|
|
337
|
+
if (this.#subcommandsList.length > 0) {
|
|
358
338
|
lines.push('Commands:');
|
|
359
339
|
const cmdLines = [];
|
|
360
340
|
if (showHelpSubcommand) {
|
|
361
341
|
cmdLines.push({ name: 'help', desc: 'Show help for a command' });
|
|
362
342
|
}
|
|
363
|
-
for (const entry of this.#
|
|
343
|
+
for (const entry of this.#subcommandsList) {
|
|
364
344
|
let name = entry.name;
|
|
365
345
|
if (entry.aliases.length > 0) {
|
|
366
346
|
name += `, ${entry.aliases.join(', ')}`;
|
|
367
347
|
}
|
|
368
|
-
cmdLines.push({ name, desc: entry.command.#
|
|
348
|
+
cmdLines.push({ name, desc: entry.command.#desc });
|
|
369
349
|
}
|
|
370
350
|
const maxNameLen = Math.max(...cmdLines.map(l => l.name.length));
|
|
371
351
|
for (const { name, desc } of cmdLines) {
|
|
@@ -380,21 +360,20 @@ class Command {
|
|
|
380
360
|
const allOptions = this.#getMergedOptions();
|
|
381
361
|
const options = [];
|
|
382
362
|
for (const opt of allOptions) {
|
|
383
|
-
const effectiveType = opt.type ?? 'string';
|
|
384
363
|
options.push({
|
|
385
364
|
long: opt.long,
|
|
386
365
|
short: opt.short,
|
|
387
|
-
|
|
388
|
-
takesValue:
|
|
366
|
+
desc: opt.desc,
|
|
367
|
+
takesValue: opt.args !== 'none',
|
|
389
368
|
choices: opt.choices,
|
|
390
369
|
});
|
|
391
370
|
}
|
|
392
371
|
return {
|
|
393
372
|
name: this.#name,
|
|
394
|
-
|
|
373
|
+
desc: this.#desc,
|
|
395
374
|
aliases: [],
|
|
396
375
|
options,
|
|
397
|
-
subcommands: this.#
|
|
376
|
+
subcommands: this.#subcommandsList.map(entry => {
|
|
398
377
|
const subMeta = entry.command.getCompletionMeta();
|
|
399
378
|
return {
|
|
400
379
|
...subMeta,
|
|
@@ -405,21 +384,21 @@ class Command {
|
|
|
405
384
|
};
|
|
406
385
|
}
|
|
407
386
|
#processHelpSubcommand(argv) {
|
|
408
|
-
if (!this.#helpSubcommandEnabled
|
|
387
|
+
if (!this.#helpSubcommandEnabled)
|
|
409
388
|
return argv;
|
|
410
389
|
if (argv.length < 1 || argv[0] !== 'help')
|
|
411
390
|
return argv;
|
|
412
|
-
if (argv.length === 1) {
|
|
391
|
+
if (argv.length === 1 || this.#subcommandsList.length === 0) {
|
|
413
392
|
return ['--help'];
|
|
414
393
|
}
|
|
415
394
|
const subName = argv[1];
|
|
416
|
-
const entry = this.#
|
|
395
|
+
const entry = this.#subcommandsList.find(e => e.name === subName || e.aliases.includes(subName));
|
|
417
396
|
if (entry) {
|
|
418
397
|
return [subName, '--help', ...argv.slice(2)];
|
|
419
398
|
}
|
|
420
399
|
return argv;
|
|
421
400
|
}
|
|
422
|
-
#
|
|
401
|
+
#route(argv) {
|
|
423
402
|
const chain = [this];
|
|
424
403
|
let current = this;
|
|
425
404
|
let idx = 0;
|
|
@@ -427,7 +406,7 @@ class Command {
|
|
|
427
406
|
const token = argv[idx];
|
|
428
407
|
if (token.startsWith('-'))
|
|
429
408
|
break;
|
|
430
|
-
const entry = current.#
|
|
409
|
+
const entry = current.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
|
|
431
410
|
if (!entry)
|
|
432
411
|
break;
|
|
433
412
|
current = entry.command;
|
|
@@ -436,221 +415,244 @@ class Command {
|
|
|
436
415
|
}
|
|
437
416
|
return { chain, remaining: argv.slice(idx) };
|
|
438
417
|
}
|
|
439
|
-
#
|
|
440
|
-
const
|
|
441
|
-
if (ddIdx === -1) {
|
|
442
|
-
return { optionTokens: tokens, restArgs: [] };
|
|
443
|
-
}
|
|
444
|
-
return {
|
|
445
|
-
optionTokens: tokens.slice(0, ddIdx),
|
|
446
|
-
restArgs: tokens.slice(ddIdx + 1),
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
#shiftChain(chain, tokens) {
|
|
450
|
-
const optsMap = new Map();
|
|
418
|
+
#resolve(chain, tokens) {
|
|
419
|
+
const consumedTokens = new Map();
|
|
451
420
|
let remaining = [...tokens];
|
|
452
421
|
const shadowed = new Set();
|
|
453
422
|
for (let i = chain.length - 1; i >= 0; i--) {
|
|
454
423
|
const cmd = chain[i];
|
|
455
|
-
const
|
|
456
|
-
|
|
424
|
+
const includeVersion = i === 0;
|
|
425
|
+
const result = cmd.#shift(remaining, shadowed, includeVersion);
|
|
426
|
+
consumedTokens.set(cmd, result.consumed);
|
|
457
427
|
remaining = result.remaining;
|
|
458
428
|
for (const opt of cmd.#options) {
|
|
459
429
|
shadowed.add(opt.long);
|
|
460
430
|
}
|
|
461
431
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
throw new CommanderError('UnknownOption', `unknown option "${
|
|
432
|
+
const argTokens = [];
|
|
433
|
+
for (const token of remaining) {
|
|
434
|
+
if (token.type !== 'none') {
|
|
435
|
+
const leafCommand = chain[chain.length - 1];
|
|
436
|
+
throw new CommanderError('UnknownOption', `unknown option "${token.original}" for command "${leafCommand.#getCommandPath()}"`, leafCommand.#getCommandPath());
|
|
467
437
|
}
|
|
468
|
-
|
|
469
|
-
|
|
438
|
+
argTokens.push(token);
|
|
439
|
+
}
|
|
440
|
+
return { consumedTokens, argTokens };
|
|
441
|
+
}
|
|
442
|
+
#shift(tokens, shadowed, includeVersion) {
|
|
443
|
+
const allOptions = this.#getMergedOptions(includeVersion);
|
|
444
|
+
const effectiveOptions = allOptions.filter(o => !shadowed.has(o.long));
|
|
445
|
+
const optionByLong = new Map();
|
|
446
|
+
const optionByShort = new Map();
|
|
447
|
+
for (const opt of effectiveOptions) {
|
|
448
|
+
optionByLong.set(opt.long, opt);
|
|
449
|
+
if (opt.short) {
|
|
450
|
+
optionByShort.set(opt.short, opt);
|
|
470
451
|
}
|
|
471
452
|
}
|
|
472
|
-
|
|
453
|
+
const consumed = [];
|
|
454
|
+
const remaining = [];
|
|
455
|
+
let i = 0;
|
|
456
|
+
while (i < tokens.length) {
|
|
457
|
+
const token = tokens[i];
|
|
458
|
+
if (token.type === 'long') {
|
|
459
|
+
const opt = optionByLong.get(token.name);
|
|
460
|
+
if (opt) {
|
|
461
|
+
consumed.push(token);
|
|
462
|
+
if (opt.args === 'required') {
|
|
463
|
+
if (!token.resolved.includes('=') && i + 1 < tokens.length) {
|
|
464
|
+
i += 1;
|
|
465
|
+
consumed.push(tokens[i]);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else if (opt.args === 'variadic') {
|
|
469
|
+
if (!token.resolved.includes('=')) {
|
|
470
|
+
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
471
|
+
i += 1;
|
|
472
|
+
consumed.push(tokens[i]);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
i += 1;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
remaining.push(token);
|
|
480
|
+
i += 1;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (token.type === 'short') {
|
|
484
|
+
const opt = optionByShort.get(token.name);
|
|
485
|
+
if (opt) {
|
|
486
|
+
consumed.push(token);
|
|
487
|
+
if (opt.args === 'required') {
|
|
488
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
489
|
+
i += 1;
|
|
490
|
+
consumed.push(tokens[i]);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else if (opt.args === 'variadic') {
|
|
494
|
+
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
495
|
+
i += 1;
|
|
496
|
+
consumed.push(tokens[i]);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
i += 1;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
remaining.push(token);
|
|
503
|
+
i += 1;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
remaining.push(token);
|
|
507
|
+
i += 1;
|
|
508
|
+
}
|
|
509
|
+
return { consumed, remaining };
|
|
473
510
|
}
|
|
474
|
-
#
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
511
|
+
#parse(chain, resolveResult, ctx, restArgs) {
|
|
512
|
+
const { consumedTokens, argTokens } = resolveResult;
|
|
513
|
+
const leafCommand = chain[chain.length - 1];
|
|
514
|
+
this.#validateMergedShortOptions(chain);
|
|
515
|
+
const optsMap = new Map();
|
|
516
|
+
for (let i = 0; i < chain.length; i++) {
|
|
517
|
+
const cmd = chain[i];
|
|
518
|
+
const includeVersion = i === 0;
|
|
519
|
+
const tokens = consumedTokens.get(cmd) ?? [];
|
|
520
|
+
const opts = cmd.#parseOptions(tokens, includeVersion);
|
|
521
|
+
optsMap.set(cmd, opts);
|
|
522
|
+
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
478
523
|
if (opt.apply && opts[opt.long] !== undefined) {
|
|
479
524
|
opt.apply(opts[opt.long], ctx);
|
|
480
525
|
}
|
|
481
526
|
}
|
|
482
527
|
}
|
|
483
|
-
|
|
484
|
-
#mergeOpts(chain, optsMap) {
|
|
485
|
-
const merged = {};
|
|
528
|
+
const mergedOpts = {};
|
|
486
529
|
for (const cmd of chain) {
|
|
487
|
-
Object.assign(
|
|
530
|
+
Object.assign(mergedOpts, optsMap.get(cmd) ?? {});
|
|
488
531
|
}
|
|
489
|
-
|
|
532
|
+
const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
|
|
533
|
+
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
534
|
+
return { ctx, opts: mergedOpts, args, rawArgs };
|
|
490
535
|
}
|
|
491
|
-
#
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
optName = token.slice(2, eqIdx);
|
|
498
|
-
inlineValue = token.slice(eqIdx + 1);
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
optName = token.slice(2);
|
|
502
|
-
}
|
|
503
|
-
const opt = optionByLong.get(optName);
|
|
504
|
-
if (!opt) {
|
|
505
|
-
throw new CommanderError('UnknownOption', `unknown option "--${optName}" for command "${this.#getCommandPath()}"`, this.#getCommandPath());
|
|
506
|
-
}
|
|
507
|
-
if (opt.type === 'boolean') {
|
|
508
|
-
if (inlineValue !== undefined) {
|
|
509
|
-
if (inlineValue === 'true') {
|
|
510
|
-
opts[optName] = true;
|
|
511
|
-
}
|
|
512
|
-
else if (inlineValue === 'false') {
|
|
513
|
-
opts[optName] = false;
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
throw new CommanderError('InvalidBooleanValue', `invalid value "${inlineValue}" for boolean option "--${optName}". Use "true" or "false"`, this.#getCommandPath());
|
|
517
|
-
}
|
|
536
|
+
#parseOptions(tokens, includeVersion) {
|
|
537
|
+
const allOptions = this.#getMergedOptions(includeVersion);
|
|
538
|
+
const opts = {};
|
|
539
|
+
for (const opt of allOptions) {
|
|
540
|
+
if (opt.default !== undefined) {
|
|
541
|
+
opts[opt.long] = opt.default;
|
|
518
542
|
}
|
|
519
|
-
else {
|
|
520
|
-
opts[
|
|
543
|
+
else if (opt.type === 'boolean' && opt.args === 'none') {
|
|
544
|
+
opts[opt.long] = false;
|
|
545
|
+
}
|
|
546
|
+
else if (opt.args === 'variadic') {
|
|
547
|
+
opts[opt.long] = [];
|
|
521
548
|
}
|
|
522
|
-
return idx + 1;
|
|
523
|
-
}
|
|
524
|
-
let value;
|
|
525
|
-
let nextIdx = idx;
|
|
526
|
-
if (inlineValue !== undefined) {
|
|
527
|
-
value = inlineValue;
|
|
528
|
-
}
|
|
529
|
-
else if (idx + 1 < argv.length) {
|
|
530
|
-
value = argv[idx + 1];
|
|
531
|
-
nextIdx += 1;
|
|
532
549
|
}
|
|
533
|
-
|
|
534
|
-
|
|
550
|
+
const optionByLong = new Map();
|
|
551
|
+
const optionByShort = new Map();
|
|
552
|
+
for (const opt of allOptions) {
|
|
553
|
+
optionByLong.set(opt.long, opt);
|
|
554
|
+
if (opt.short) {
|
|
555
|
+
optionByShort.set(opt.short, opt);
|
|
556
|
+
}
|
|
535
557
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const token = argv[idx];
|
|
541
|
-
if (token.includes('=')) {
|
|
542
|
-
throw new CommanderError('UnsupportedShortSyntax', `"-${token.slice(1)}" is not supported. Use "-${token[1]} ${token.slice(3)}" instead`, this.#getCommandPath());
|
|
543
|
-
}
|
|
544
|
-
const flags = token.slice(1);
|
|
545
|
-
for (let j = 0; j < flags.length; j++) {
|
|
546
|
-
const flag = flags[j];
|
|
547
|
-
const opt = optionByShort.get(flag);
|
|
558
|
+
let i = 0;
|
|
559
|
+
while (i < tokens.length) {
|
|
560
|
+
const token = tokens[i];
|
|
561
|
+
const opt = token.type === 'long' ? optionByLong.get(token.name) : optionByShort.get(token.name);
|
|
548
562
|
if (!opt) {
|
|
549
|
-
|
|
563
|
+
i += 1;
|
|
564
|
+
continue;
|
|
550
565
|
}
|
|
551
|
-
|
|
552
|
-
|
|
566
|
+
const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
|
|
567
|
+
if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
|
|
568
|
+
throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$1(opt.long)}" can only be used with boolean options`, this.#getCommandPath());
|
|
569
|
+
}
|
|
570
|
+
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
571
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
572
|
+
if (eqIdx !== -1) {
|
|
573
|
+
const value = token.resolved.slice(eqIdx + 1);
|
|
574
|
+
if (value === 'true') {
|
|
575
|
+
opts[opt.long] = true;
|
|
576
|
+
}
|
|
577
|
+
else if (value === 'false') {
|
|
578
|
+
opts[opt.long] = false;
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--${camelToKebabCase$1(opt.long)}". Use "true" or "false"`, this.#getCommandPath());
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
opts[opt.long] = true;
|
|
586
|
+
}
|
|
587
|
+
i += 1;
|
|
553
588
|
continue;
|
|
554
589
|
}
|
|
555
|
-
if (
|
|
556
|
-
|
|
590
|
+
if (opt.args === 'required') {
|
|
591
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
592
|
+
let rawValue;
|
|
593
|
+
if (eqIdx !== -1) {
|
|
594
|
+
rawValue = token.resolved.slice(eqIdx + 1);
|
|
595
|
+
}
|
|
596
|
+
else if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
597
|
+
rawValue = tokens[i + 1].original;
|
|
598
|
+
i += 1;
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
throw new CommanderError('MissingValue', `option "--${camelToKebabCase$1(opt.long)}" requires a value`, this.#getCommandPath());
|
|
602
|
+
}
|
|
603
|
+
opts[opt.long] = this.#convertValue(opt, rawValue);
|
|
604
|
+
i += 1;
|
|
605
|
+
continue;
|
|
557
606
|
}
|
|
558
|
-
if (
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
607
|
+
if (opt.args === 'variadic') {
|
|
608
|
+
const values = Array.isArray(opts[opt.long]) ? opts[opt.long] : [];
|
|
609
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
610
|
+
if (eqIdx !== -1) {
|
|
611
|
+
values.push(this.#convertValue(opt, token.resolved.slice(eqIdx + 1)));
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
615
|
+
i += 1;
|
|
616
|
+
values.push(this.#convertValue(opt, tokens[i].original));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
opts[opt.long] = values;
|
|
620
|
+
i += 1;
|
|
621
|
+
continue;
|
|
562
622
|
}
|
|
563
|
-
|
|
623
|
+
i += 1;
|
|
564
624
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
let parsedValue = rawValue;
|
|
570
|
-
if (opt.coerce) {
|
|
571
|
-
parsedValue = opt.coerce(rawValue);
|
|
625
|
+
for (const opt of allOptions) {
|
|
626
|
+
if (opt.required && opts[opt.long] === undefined) {
|
|
627
|
+
throw new CommanderError('MissingRequired', `missing required option "--${camelToKebabCase$1(opt.long)}"`, this.#getCommandPath());
|
|
628
|
+
}
|
|
572
629
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const num = Number(rawValue);
|
|
582
|
-
if (Number.isNaN(num)) {
|
|
583
|
-
throw new CommanderError('InvalidType', `invalid number "${rawValue}" for option "--${opt.long}"`, this.#getCommandPath());
|
|
630
|
+
for (const opt of allOptions) {
|
|
631
|
+
if (opt.choices && opts[opt.long] !== undefined) {
|
|
632
|
+
const value = opts[opt.long];
|
|
633
|
+
const values = Array.isArray(value) ? value : [value];
|
|
634
|
+
const choices = opt.choices;
|
|
635
|
+
for (const v of values) {
|
|
636
|
+
if (!choices.includes(v)) {
|
|
637
|
+
throw new CommanderError('InvalidChoice', `invalid value "${v}" for option "--${camelToKebabCase$1(opt.long)}". Allowed: ${opt.choices.join(', ')}`, this.#getCommandPath());
|
|
584
638
|
}
|
|
585
|
-
parsedValue = num;
|
|
586
|
-
break;
|
|
587
639
|
}
|
|
588
640
|
}
|
|
589
641
|
}
|
|
590
|
-
|
|
591
|
-
const currentValue = opts[opt.long];
|
|
592
|
-
const current = Array.isArray(currentValue) ? currentValue : [];
|
|
593
|
-
opts[opt.long] = [...current, parsedValue];
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
opts[opt.long] = parsedValue;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
#getMergedOptions() {
|
|
600
|
-
const optionMap = new Map();
|
|
601
|
-
const hasUserHelp = this.#options.some(o => o.long === 'help');
|
|
602
|
-
const hasUserVersion = this.#options.some(o => o.long === 'version');
|
|
603
|
-
if (!hasUserHelp) {
|
|
604
|
-
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
605
|
-
}
|
|
606
|
-
if (!hasUserVersion) {
|
|
607
|
-
optionMap.set('version', BUILTIN_VERSION_OPTION);
|
|
608
|
-
}
|
|
609
|
-
for (const opt of this.#options) {
|
|
610
|
-
optionMap.set(opt.long, opt);
|
|
611
|
-
}
|
|
612
|
-
return Array.from(optionMap.values());
|
|
613
|
-
}
|
|
614
|
-
#validateOptionConfig(opt) {
|
|
615
|
-
if (opt.long.startsWith('no-')) {
|
|
616
|
-
throw new CommanderError('ConfigurationError', `option long name cannot start with "no-": "${opt.long}"`, this.#getCommandPath());
|
|
617
|
-
}
|
|
618
|
-
if (opt.required && opt.default !== undefined) {
|
|
619
|
-
throw new CommanderError('ConfigurationError', `option "--${opt.long}" cannot be both required and have a default value`, this.#getCommandPath());
|
|
620
|
-
}
|
|
621
|
-
if (opt.type === 'boolean' && opt.required) {
|
|
622
|
-
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, this.#getCommandPath());
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
#checkOptionUniqueness(opt) {
|
|
626
|
-
if (this.#options.some(o => o.long === opt.long)) {
|
|
627
|
-
throw new CommanderError('OptionConflict', `option "--${opt.long}" is already defined`, this.#getCommandPath());
|
|
628
|
-
}
|
|
629
|
-
if (opt.short && this.#options.some(o => o.short === opt.short)) {
|
|
630
|
-
throw new CommanderError('OptionConflict', `short option "-${opt.short}" is already defined`, this.#getCommandPath());
|
|
631
|
-
}
|
|
642
|
+
return opts;
|
|
632
643
|
}
|
|
633
|
-
#
|
|
634
|
-
if (
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
if (arg.kind === 'variadic') {
|
|
638
|
-
if (this.#arguments.some(a => a.kind === 'variadic')) {
|
|
639
|
-
throw new CommanderError('ConfigurationError', 'only one variadic argument is allowed', this.#getCommandPath());
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
if (this.#arguments.length > 0) {
|
|
643
|
-
const last = this.#arguments[this.#arguments.length - 1];
|
|
644
|
-
if (last.kind === 'variadic') {
|
|
645
|
-
throw new CommanderError('ConfigurationError', 'variadic argument must be the last argument', this.#getCommandPath());
|
|
646
|
-
}
|
|
644
|
+
#convertValue(opt, rawValue) {
|
|
645
|
+
if (opt.coerce) {
|
|
646
|
+
return opt.coerce(rawValue);
|
|
647
647
|
}
|
|
648
|
-
if (
|
|
649
|
-
const
|
|
650
|
-
if (
|
|
651
|
-
throw new CommanderError('
|
|
648
|
+
if (opt.type === 'number') {
|
|
649
|
+
const num = Number(rawValue);
|
|
650
|
+
if (Number.isNaN(num)) {
|
|
651
|
+
throw new CommanderError('InvalidType', `invalid number "${rawValue}" for option "--${camelToKebabCase$1(opt.long)}"`, this.#getCommandPath());
|
|
652
652
|
}
|
|
653
|
+
return num;
|
|
653
654
|
}
|
|
655
|
+
return rawValue;
|
|
654
656
|
}
|
|
655
657
|
#parseArguments(rawArgs) {
|
|
656
658
|
const argumentDefs = this.#arguments;
|
|
@@ -707,226 +709,164 @@ class Command {
|
|
|
707
709
|
}
|
|
708
710
|
return raw;
|
|
709
711
|
}
|
|
710
|
-
#
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
continue;
|
|
717
|
-
optionByLong.set(opt.long, opt);
|
|
718
|
-
if (opt.short) {
|
|
719
|
-
optionByShort.set(opt.short, opt);
|
|
720
|
-
}
|
|
721
|
-
if (opt.type === 'boolean') {
|
|
722
|
-
booleanOptions.add(opt.long);
|
|
723
|
-
}
|
|
712
|
+
#getMergedOptions(includeVersion = !this.#parent) {
|
|
713
|
+
const optionMap = new Map();
|
|
714
|
+
const hasUserHelp = this.#options.some(o => o.long === 'help');
|
|
715
|
+
const hasUserVersion = this.#options.some(o => o.long === 'version');
|
|
716
|
+
if (!hasUserHelp) {
|
|
717
|
+
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
724
718
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
#hasHelpFlag(argv, allOptions) {
|
|
728
|
-
return this.#hasBuiltinFlag(argv, 'help', 'h', allOptions);
|
|
729
|
-
}
|
|
730
|
-
#hasVersionFlag(argv, allOptions) {
|
|
731
|
-
return this.#hasBuiltinFlag(argv, 'version', 'V', allOptions);
|
|
732
|
-
}
|
|
733
|
-
#hasBuiltinFlag(argv, flagLong, flagShort, allOptions) {
|
|
734
|
-
const { optionByLong, optionByShort, booleanOptions } = this.#buildOptionMaps(allOptions);
|
|
735
|
-
const normalizedArgv = this.#normalizeArgv(argv, booleanOptions);
|
|
736
|
-
for (let i = 0; i < normalizedArgv.length; i++) {
|
|
737
|
-
const arg = normalizedArgv[i];
|
|
738
|
-
if (arg === '--') {
|
|
739
|
-
break;
|
|
740
|
-
}
|
|
741
|
-
if (arg === `--${flagLong}` || (flagShort && arg === `-${flagShort}`)) {
|
|
742
|
-
return true;
|
|
743
|
-
}
|
|
744
|
-
if (this.#optionConsumesNextValue(arg, optionByLong, optionByShort)) {
|
|
745
|
-
i += 1;
|
|
746
|
-
}
|
|
719
|
+
if (!hasUserVersion && includeVersion) {
|
|
720
|
+
optionMap.set('version', BUILTIN_VERSION_OPTION);
|
|
747
721
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
#optionConsumesNextValue(arg, optionByLong, optionByShort) {
|
|
751
|
-
if (arg.startsWith('--')) {
|
|
752
|
-
const eqIdx = arg.indexOf('=');
|
|
753
|
-
if (eqIdx !== -1) {
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
const optName = arg.slice(2);
|
|
757
|
-
const opt = optionByLong.get(optName);
|
|
758
|
-
if (!opt) {
|
|
759
|
-
return false;
|
|
760
|
-
}
|
|
761
|
-
const type = opt.type ?? 'string';
|
|
762
|
-
return type !== 'boolean';
|
|
722
|
+
for (const opt of this.#options) {
|
|
723
|
+
optionMap.set(opt.long, opt);
|
|
763
724
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
725
|
+
return Array.from(optionMap.values());
|
|
726
|
+
}
|
|
727
|
+
#validateMergedShortOptions(chain) {
|
|
728
|
+
const mergedByLong = new Map();
|
|
729
|
+
for (let i = 0; i < chain.length; i++) {
|
|
730
|
+
const cmd = chain[i];
|
|
731
|
+
const includeVersion = i === 0;
|
|
732
|
+
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
733
|
+
mergedByLong.set(opt.long, opt);
|
|
768
734
|
}
|
|
769
|
-
const type = opt.type ?? 'string';
|
|
770
|
-
return type !== 'boolean';
|
|
771
735
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const result = [];
|
|
776
|
-
let seenDoubleDash = false;
|
|
777
|
-
for (const arg of argv) {
|
|
778
|
-
if (arg === '--') {
|
|
779
|
-
seenDoubleDash = true;
|
|
780
|
-
result.push(arg);
|
|
736
|
+
const shortMap = new Map();
|
|
737
|
+
for (const opt of mergedByLong.values()) {
|
|
738
|
+
if (!opt.short)
|
|
781
739
|
continue;
|
|
740
|
+
const existingLong = shortMap.get(opt.short);
|
|
741
|
+
if (existingLong && existingLong !== opt.long) {
|
|
742
|
+
throw new CommanderError('OptionConflict', `short option "-${opt.short}" conflicts with "--${existingLong}"`, this.#getCommandPath());
|
|
782
743
|
}
|
|
783
|
-
|
|
784
|
-
const eqIdx = arg.indexOf('=');
|
|
785
|
-
if (eqIdx !== -1) {
|
|
786
|
-
const optName = arg.slice(5, eqIdx);
|
|
787
|
-
if (booleanOptions.has(optName)) {
|
|
788
|
-
throw new CommanderError('InvalidBooleanValue', `"--no-${optName}" does not accept a value`, this.#getCommandPath());
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
const optName = arg.slice(5);
|
|
793
|
-
if (booleanOptions.has(optName)) {
|
|
794
|
-
result.push(`--${optName}=false`);
|
|
795
|
-
continue;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
result.push(arg);
|
|
744
|
+
shortMap.set(opt.short, opt.long);
|
|
800
745
|
}
|
|
801
|
-
return result;
|
|
802
746
|
}
|
|
803
|
-
#
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
#tryConsumeLongOption(tokens, idx, optionByLong, opts) {
|
|
807
|
-
const token = tokens[idx];
|
|
808
|
-
const eqIdx = token.indexOf('=');
|
|
809
|
-
let optName;
|
|
810
|
-
let inlineValue;
|
|
811
|
-
if (eqIdx !== -1) {
|
|
812
|
-
optName = token.slice(2, eqIdx);
|
|
813
|
-
inlineValue = token.slice(eqIdx + 1);
|
|
747
|
+
#validateOptionConfig(opt) {
|
|
748
|
+
if (opt.type === 'boolean' && opt.args !== 'none') {
|
|
749
|
+
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
814
750
|
}
|
|
815
|
-
|
|
816
|
-
|
|
751
|
+
if ((opt.type === 'string' || opt.type === 'number') && opt.args === 'none') {
|
|
752
|
+
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required' or 'variadic'`, this.#getCommandPath());
|
|
817
753
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
return 0;
|
|
754
|
+
if (opt.long.startsWith('no')) {
|
|
755
|
+
throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, this.#getCommandPath());
|
|
821
756
|
}
|
|
822
|
-
if (opt.
|
|
823
|
-
|
|
824
|
-
if (inlineValue === 'true') {
|
|
825
|
-
opts[optName] = true;
|
|
826
|
-
}
|
|
827
|
-
else if (inlineValue === 'false') {
|
|
828
|
-
opts[optName] = false;
|
|
829
|
-
}
|
|
830
|
-
else {
|
|
831
|
-
throw new CommanderError('InvalidBooleanValue', `invalid value "${inlineValue}" for boolean option "--${optName}". Use "true" or "false"`, this.#getCommandPath());
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
else {
|
|
835
|
-
opts[optName] = true;
|
|
836
|
-
}
|
|
837
|
-
return 1;
|
|
757
|
+
if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
|
|
758
|
+
throw new CommanderError('ConfigurationError', `option long name must be camelCase: "${opt.long}"`, this.#getCommandPath());
|
|
838
759
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
if (inlineValue !== undefined) {
|
|
842
|
-
value = inlineValue;
|
|
760
|
+
if (opt.required && opt.default !== undefined) {
|
|
761
|
+
throw new CommanderError('ConfigurationError', `option "--${opt.long}" cannot be both required and have a default value`, this.#getCommandPath());
|
|
843
762
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
consumed = 2;
|
|
763
|
+
if (opt.type === 'boolean' && opt.required) {
|
|
764
|
+
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, this.#getCommandPath());
|
|
847
765
|
}
|
|
848
|
-
|
|
849
|
-
|
|
766
|
+
}
|
|
767
|
+
#checkOptionUniqueness(opt) {
|
|
768
|
+
if (this.#options.some(o => o.long === opt.long)) {
|
|
769
|
+
throw new CommanderError('OptionConflict', `option "--${opt.long}" is already defined`, this.#getCommandPath());
|
|
770
|
+
}
|
|
771
|
+
if (opt.short && this.#options.some(o => o.short === opt.short)) {
|
|
772
|
+
throw new CommanderError('OptionConflict', `short option "-${opt.short}" is already defined`, this.#getCommandPath());
|
|
850
773
|
}
|
|
851
|
-
this.#applyValue(opt, value, opts);
|
|
852
|
-
return consumed;
|
|
853
774
|
}
|
|
854
|
-
#
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
throw new CommanderError('UnsupportedShortSyntax', `"-${token.slice(1)}" is not supported. Use "-${token[1]} ${token.slice(3)}" instead`, this.#getCommandPath());
|
|
862
|
-
}
|
|
863
|
-
const flags = token.slice(1);
|
|
864
|
-
let j = 0;
|
|
865
|
-
const consumedFlags = [];
|
|
866
|
-
const unconsumedFlags = [];
|
|
867
|
-
let nextIdx = idx + 1;
|
|
868
|
-
while (j < flags.length) {
|
|
869
|
-
const flag = flags[j];
|
|
870
|
-
const opt = optionByShort.get(flag);
|
|
871
|
-
if (!opt) {
|
|
872
|
-
unconsumedFlags.push(...flags.slice(j).split(''));
|
|
873
|
-
break;
|
|
775
|
+
#validateArgumentConfig(arg) {
|
|
776
|
+
if (arg.kind === 'required' && arg.default !== undefined) {
|
|
777
|
+
throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot have a default value`, this.#getCommandPath());
|
|
778
|
+
}
|
|
779
|
+
if (arg.kind === 'variadic') {
|
|
780
|
+
if (this.#arguments.some(a => a.kind === 'variadic')) {
|
|
781
|
+
throw new CommanderError('ConfigurationError', 'only one variadic argument is allowed', this.#getCommandPath());
|
|
874
782
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
783
|
+
}
|
|
784
|
+
if (this.#arguments.length > 0) {
|
|
785
|
+
const last = this.#arguments[this.#arguments.length - 1];
|
|
786
|
+
if (last.kind === 'variadic') {
|
|
787
|
+
throw new CommanderError('ConfigurationError', 'variadic argument must be the last argument', this.#getCommandPath());
|
|
880
788
|
}
|
|
881
|
-
|
|
882
|
-
|
|
789
|
+
}
|
|
790
|
+
if (arg.kind === 'required') {
|
|
791
|
+
const hasOptional = this.#arguments.some(a => a.kind === 'optional' || a.kind === 'variadic');
|
|
792
|
+
if (hasOptional) {
|
|
793
|
+
throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot come after optional/variadic arguments`, this.#getCommandPath());
|
|
883
794
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async #runAction(params) {
|
|
798
|
+
if (!this.#action)
|
|
799
|
+
return;
|
|
800
|
+
try {
|
|
801
|
+
await this.#action(params);
|
|
802
|
+
}
|
|
803
|
+
catch (err) {
|
|
804
|
+
if (err instanceof Error) {
|
|
805
|
+
console.error(`Error: ${err.message}`);
|
|
888
806
|
}
|
|
889
807
|
else {
|
|
890
|
-
|
|
808
|
+
console.error('Error: action failed');
|
|
891
809
|
}
|
|
892
|
-
|
|
810
|
+
process.exit(1);
|
|
893
811
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
812
|
+
}
|
|
813
|
+
#hasFlag(tokens, longName, shortName) {
|
|
814
|
+
for (const token of tokens) {
|
|
815
|
+
if (token.type === 'long' && token.name === longName) {
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
if (token.type === 'short' && token.name === shortName) {
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
897
821
|
}
|
|
898
|
-
return
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
#getCommandPath() {
|
|
825
|
+
const parts = [];
|
|
826
|
+
let current = this;
|
|
827
|
+
while (current) {
|
|
828
|
+
if (current.#name) {
|
|
829
|
+
parts.unshift(current.#name);
|
|
830
|
+
}
|
|
831
|
+
current = current.#parent;
|
|
832
|
+
}
|
|
833
|
+
return parts.join(' ') || this.#name;
|
|
899
834
|
}
|
|
900
835
|
}
|
|
901
836
|
|
|
837
|
+
function camelToKebabCase(str) {
|
|
838
|
+
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
839
|
+
}
|
|
902
840
|
class CompletionCommand extends Command {
|
|
903
841
|
constructor(root, config) {
|
|
904
842
|
const paths = config.paths;
|
|
905
|
-
const programName = config.programName ?? root.name;
|
|
906
|
-
super({
|
|
907
|
-
description: 'Generate shell completion script',
|
|
908
|
-
});
|
|
843
|
+
const programName = config.programName ?? root.name ?? 'program';
|
|
844
|
+
super({ desc: 'Generate shell completion script' });
|
|
909
845
|
this.option({
|
|
910
846
|
long: 'bash',
|
|
911
847
|
type: 'boolean',
|
|
912
|
-
|
|
848
|
+
args: 'none',
|
|
849
|
+
desc: 'Generate Bash completion script',
|
|
913
850
|
})
|
|
914
851
|
.option({
|
|
915
852
|
long: 'fish',
|
|
916
853
|
type: 'boolean',
|
|
917
|
-
|
|
854
|
+
args: 'none',
|
|
855
|
+
desc: 'Generate Fish completion script',
|
|
918
856
|
})
|
|
919
857
|
.option({
|
|
920
858
|
long: 'pwsh',
|
|
921
859
|
type: 'boolean',
|
|
922
|
-
|
|
860
|
+
args: 'none',
|
|
861
|
+
desc: 'Generate PowerShell completion script',
|
|
923
862
|
})
|
|
924
863
|
.option({
|
|
925
864
|
long: 'write',
|
|
926
865
|
short: 'w',
|
|
927
866
|
type: 'string',
|
|
928
|
-
|
|
929
|
-
|
|
867
|
+
args: 'required',
|
|
868
|
+
desc: 'Write to file (use shell default path if empty)',
|
|
869
|
+
default: undefined,
|
|
930
870
|
})
|
|
931
871
|
.action(({ opts }) => {
|
|
932
872
|
const meta = root.getCompletionMeta();
|
|
@@ -982,45 +922,6 @@ function expandHome(filepath) {
|
|
|
982
922
|
}
|
|
983
923
|
return filepath;
|
|
984
924
|
}
|
|
985
|
-
function resolveOptionalStringOption(argv, longName, shortName) {
|
|
986
|
-
const remaining = [];
|
|
987
|
-
let value;
|
|
988
|
-
for (let i = 0; i < argv.length; i++) {
|
|
989
|
-
const arg = argv[i];
|
|
990
|
-
if (arg.startsWith(`--${longName}=`)) {
|
|
991
|
-
value = arg.slice(`--${longName}=`.length);
|
|
992
|
-
continue;
|
|
993
|
-
}
|
|
994
|
-
if (arg === `--${longName}`) {
|
|
995
|
-
const next = argv[i + 1];
|
|
996
|
-
if (next !== undefined && !next.startsWith('-')) {
|
|
997
|
-
value = next;
|
|
998
|
-
i += 1;
|
|
999
|
-
}
|
|
1000
|
-
else {
|
|
1001
|
-
value = '';
|
|
1002
|
-
}
|
|
1003
|
-
continue;
|
|
1004
|
-
}
|
|
1005
|
-
if (arg.startsWith(`-${shortName}=`)) {
|
|
1006
|
-
value = arg.slice(`-${shortName}=`.length);
|
|
1007
|
-
continue;
|
|
1008
|
-
}
|
|
1009
|
-
if (arg === `-${shortName}`) {
|
|
1010
|
-
const next = argv[i + 1];
|
|
1011
|
-
if (next !== undefined && !next.startsWith('-')) {
|
|
1012
|
-
value = next;
|
|
1013
|
-
i += 1;
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
value = '';
|
|
1017
|
-
}
|
|
1018
|
-
continue;
|
|
1019
|
-
}
|
|
1020
|
-
remaining.push(arg);
|
|
1021
|
-
}
|
|
1022
|
-
return { value, remaining };
|
|
1023
|
-
}
|
|
1024
925
|
class BashCompletion {
|
|
1025
926
|
#meta;
|
|
1026
927
|
#programName;
|
|
@@ -1053,11 +954,12 @@ class BashCompletion {
|
|
|
1053
954
|
const lines = [];
|
|
1054
955
|
const optParts = [];
|
|
1055
956
|
for (const opt of cmd.options) {
|
|
957
|
+
const kebabLong = camelToKebabCase(opt.long);
|
|
1056
958
|
if (opt.short)
|
|
1057
959
|
optParts.push(`-${opt.short}`);
|
|
1058
|
-
optParts.push(`--${
|
|
960
|
+
optParts.push(`--${kebabLong}`);
|
|
1059
961
|
if (!opt.takesValue) {
|
|
1060
|
-
optParts.push(`--no-${
|
|
962
|
+
optParts.push(`--no-${kebabLong}`);
|
|
1061
963
|
}
|
|
1062
964
|
}
|
|
1063
965
|
const subParts = cmd.subcommands.flatMap(sub => [sub.name, ...sub.aliases]);
|
|
@@ -1106,13 +1008,14 @@ class FishCompletion {
|
|
|
1106
1008
|
const isRoot = parentPath.length === 0;
|
|
1107
1009
|
const condition = this.#buildCondition(parentPath);
|
|
1108
1010
|
for (const opt of cmd.options) {
|
|
1011
|
+
const kebabLong = camelToKebabCase(opt.long);
|
|
1109
1012
|
let line = `complete -c ${this.#programName}`;
|
|
1110
1013
|
if (condition)
|
|
1111
1014
|
line += ` -n '${condition}'`;
|
|
1112
1015
|
if (opt.short)
|
|
1113
1016
|
line += ` -s ${opt.short}`;
|
|
1114
|
-
line += ` -l ${
|
|
1115
|
-
line += ` -d '${this.#escape(opt.
|
|
1017
|
+
line += ` -l ${kebabLong}`;
|
|
1018
|
+
line += ` -d '${this.#escape(opt.desc)}'`;
|
|
1116
1019
|
if (opt.choices && opt.choices.length > 0) {
|
|
1117
1020
|
line += ` -xa '${opt.choices.join(' ')}'`;
|
|
1118
1021
|
}
|
|
@@ -1121,8 +1024,8 @@ class FishCompletion {
|
|
|
1121
1024
|
let noLine = `complete -c ${this.#programName}`;
|
|
1122
1025
|
if (condition)
|
|
1123
1026
|
noLine += ` -n '${condition}'`;
|
|
1124
|
-
noLine += ` -l no-${
|
|
1125
|
-
noLine += ` -d '${this.#escape(opt.
|
|
1027
|
+
noLine += ` -l no-${kebabLong}`;
|
|
1028
|
+
noLine += ` -d '${this.#escape(opt.desc)}'`;
|
|
1126
1029
|
lines.push(noLine);
|
|
1127
1030
|
}
|
|
1128
1031
|
}
|
|
@@ -1135,7 +1038,7 @@ class FishCompletion {
|
|
|
1135
1038
|
line += ` -n '${condition}; and not __fish_seen_subcommand_from ${this.#getSubcommandNames(cmd).join(' ')}'`;
|
|
1136
1039
|
}
|
|
1137
1040
|
line += ` -a ${sub.name}`;
|
|
1138
|
-
line += ` -d '${this.#escape(sub.
|
|
1041
|
+
line += ` -d '${this.#escape(sub.desc)}'`;
|
|
1139
1042
|
lines.push(line);
|
|
1140
1043
|
for (const alias of sub.aliases) {
|
|
1141
1044
|
let aliasLine = `complete -c ${this.#programName}`;
|
|
@@ -1208,7 +1111,7 @@ class PwshCompletion {
|
|
|
1208
1111
|
' "--$($opt.long)",',
|
|
1209
1112
|
' $opt.long,',
|
|
1210
1113
|
' "ParameterName",',
|
|
1211
|
-
' $opt.
|
|
1114
|
+
' $opt.desc',
|
|
1212
1115
|
' )',
|
|
1213
1116
|
' }',
|
|
1214
1117
|
' if ($opt.isBoolean -and "--no-$($opt.long)" -like "$current*") {',
|
|
@@ -1216,7 +1119,7 @@ class PwshCompletion {
|
|
|
1216
1119
|
' "--no-$($opt.long)",',
|
|
1217
1120
|
' "no-$($opt.long)",',
|
|
1218
1121
|
' "ParameterName",',
|
|
1219
|
-
' $opt.
|
|
1122
|
+
' $opt.desc',
|
|
1220
1123
|
' )',
|
|
1221
1124
|
' }',
|
|
1222
1125
|
' if ($opt.short -and "-$($opt.short)" -like "$current*") {',
|
|
@@ -1224,7 +1127,7 @@ class PwshCompletion {
|
|
|
1224
1127
|
' "-$($opt.short)",',
|
|
1225
1128
|
' $opt.short,',
|
|
1226
1129
|
' "ParameterName",',
|
|
1227
|
-
' $opt.
|
|
1130
|
+
' $opt.desc',
|
|
1228
1131
|
' )',
|
|
1229
1132
|
' }',
|
|
1230
1133
|
' }',
|
|
@@ -1238,7 +1141,7 @@ class PwshCompletion {
|
|
|
1238
1141
|
' $sub,',
|
|
1239
1142
|
' $sub,',
|
|
1240
1143
|
' "Command",',
|
|
1241
|
-
' $cmd.subcommands[$sub].
|
|
1144
|
+
' $cmd.subcommands[$sub].desc',
|
|
1242
1145
|
' )',
|
|
1243
1146
|
' }',
|
|
1244
1147
|
' }',
|
|
@@ -1252,14 +1155,15 @@ class PwshCompletion {
|
|
|
1252
1155
|
}
|
|
1253
1156
|
#generateCommandHash(cmd, indent) {
|
|
1254
1157
|
const lines = [];
|
|
1255
|
-
lines.push(`${indent}description = '${this.#escape(cmd.
|
|
1158
|
+
lines.push(`${indent}description = '${this.#escape(cmd.desc)}'`);
|
|
1256
1159
|
lines.push(`${indent}options = @(`);
|
|
1257
1160
|
for (const opt of cmd.options) {
|
|
1161
|
+
const kebabLong = camelToKebabCase(opt.long);
|
|
1258
1162
|
lines.push(`${indent} @{`);
|
|
1259
1163
|
if (opt.short)
|
|
1260
1164
|
lines.push(`${indent} short = '${opt.short}'`);
|
|
1261
|
-
lines.push(`${indent} long = '${
|
|
1262
|
-
lines.push(`${indent} description = '${this.#escape(opt.
|
|
1165
|
+
lines.push(`${indent} long = '${kebabLong}'`);
|
|
1166
|
+
lines.push(`${indent} description = '${this.#escape(opt.desc)}'`);
|
|
1263
1167
|
lines.push(`${indent} isBoolean = $${!opt.takesValue}`);
|
|
1264
1168
|
if (opt.choices) {
|
|
1265
1169
|
lines.push(`${indent} choices = @('${opt.choices.join("', '")}')`);
|