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