@guanghechen/commander 4.7.1 → 4.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/lib/cjs/browser.cjs +410 -54
- package/lib/cjs/node.cjs +770 -88
- package/lib/esm/browser.mjs +410 -54
- package/lib/esm/node.mjs +770 -88
- package/lib/types/browser.d.ts +28 -6
- package/lib/types/node.d.ts +28 -6
- package/package.json +1 -1
package/lib/cjs/node.cjs
CHANGED
|
@@ -197,6 +197,140 @@ function kebabToCamelCase(str) {
|
|
|
197
197
|
function camelToKebabCase$1(str) {
|
|
198
198
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
199
199
|
}
|
|
200
|
+
const ANSI_ESCAPE_REGEX = new RegExp(String.raw `\\x1B\\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
201
|
+
const DECIMAL_INTEGER_REGEX = /^\d(?:_?\d)*$/;
|
|
202
|
+
const DECIMAL_FRACTION_REGEX = /^\d(?:_?\d)*$/;
|
|
203
|
+
const DECIMAL_EXPONENT_REGEX = /^[eE][+-]?\d(?:_?\d)*$/;
|
|
204
|
+
const BINARY_LITERAL_REGEX = /^0[bB][01](?:_?[01])*$/;
|
|
205
|
+
const OCTAL_LITERAL_REGEX = /^0[oO][0-7](?:_?[0-7])*$/;
|
|
206
|
+
const HEX_LITERAL_REGEX = /^0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*$/;
|
|
207
|
+
function stripAnsi(value) {
|
|
208
|
+
return value.replace(ANSI_ESCAPE_REGEX, '');
|
|
209
|
+
}
|
|
210
|
+
function isCombiningMark(codePoint) {
|
|
211
|
+
return ((codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
212
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
213
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
214
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
215
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f));
|
|
216
|
+
}
|
|
217
|
+
function isWideCodePoint(codePoint) {
|
|
218
|
+
if (codePoint < 0x1100) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return (codePoint <= 0x115f ||
|
|
222
|
+
codePoint === 0x2329 ||
|
|
223
|
+
codePoint === 0x232a ||
|
|
224
|
+
(codePoint >= 0x2e80 && codePoint <= 0x3247 && codePoint !== 0x303f) ||
|
|
225
|
+
(codePoint >= 0x3250 && codePoint <= 0x4dbf) ||
|
|
226
|
+
(codePoint >= 0x4e00 && codePoint <= 0xa4c6) ||
|
|
227
|
+
(codePoint >= 0xa960 && codePoint <= 0xa97c) ||
|
|
228
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
229
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
230
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
231
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6b) ||
|
|
232
|
+
(codePoint >= 0xff01 && codePoint <= 0xff60) ||
|
|
233
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
234
|
+
(codePoint >= 0x1b000 && codePoint <= 0x1b001) ||
|
|
235
|
+
(codePoint >= 0x1f200 && codePoint <= 0x1f251) ||
|
|
236
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
237
|
+
}
|
|
238
|
+
function getDisplayWidth(value) {
|
|
239
|
+
const normalized = stripAnsi(value).normalize('NFC');
|
|
240
|
+
let width = 0;
|
|
241
|
+
for (const char of normalized) {
|
|
242
|
+
const codePoint = char.codePointAt(0);
|
|
243
|
+
if (codePoint === undefined || isCombiningMark(codePoint)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
width += isWideCodePoint(codePoint) ? 2 : 1;
|
|
247
|
+
}
|
|
248
|
+
return width;
|
|
249
|
+
}
|
|
250
|
+
function padDisplayEnd(value, targetWidth) {
|
|
251
|
+
const width = getDisplayWidth(value);
|
|
252
|
+
if (width >= targetWidth) {
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
return value + ' '.repeat(targetWidth - width);
|
|
256
|
+
}
|
|
257
|
+
function isValidPrimitiveNumberLiteral(rawValue) {
|
|
258
|
+
if (rawValue.trim() !== rawValue || rawValue.length === 0) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
if (rawValue === 'NaN' || rawValue === 'Infinity' || rawValue === '-Infinity') {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
if (BINARY_LITERAL_REGEX.test(rawValue)) {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
if (OCTAL_LITERAL_REGEX.test(rawValue)) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
if (HEX_LITERAL_REGEX.test(rawValue)) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
const sign = rawValue[0] === '+' || rawValue[0] === '-' ? rawValue[0] : '';
|
|
274
|
+
const body = sign ? rawValue.slice(1) : rawValue;
|
|
275
|
+
if (body.length === 0) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
const expIndex = body.search(/[eE]/);
|
|
279
|
+
const basePart = expIndex === -1 ? body : body.slice(0, expIndex);
|
|
280
|
+
const expPart = expIndex === -1 ? '' : body.slice(expIndex);
|
|
281
|
+
if (expPart && !DECIMAL_EXPONENT_REGEX.test(expPart)) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (basePart.includes('.')) {
|
|
285
|
+
const decimalParts = basePart.split('.');
|
|
286
|
+
if (decimalParts.length !== 2) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
const [intPart, fracPart] = decimalParts;
|
|
290
|
+
const intOk = intPart.length === 0 || DECIMAL_INTEGER_REGEX.test(intPart);
|
|
291
|
+
const fracOk = fracPart.length === 0 || DECIMAL_FRACTION_REGEX.test(fracPart);
|
|
292
|
+
if (!intOk || !fracOk) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
return intPart.length > 0 || fracPart.length > 0;
|
|
296
|
+
}
|
|
297
|
+
return DECIMAL_INTEGER_REGEX.test(basePart);
|
|
298
|
+
}
|
|
299
|
+
function parsePrimitiveNumber(rawValue) {
|
|
300
|
+
if (!isValidPrimitiveNumberLiteral(rawValue)) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
const normalized = rawValue.replaceAll('_', '');
|
|
304
|
+
const value = Number(normalized);
|
|
305
|
+
if (!Number.isFinite(value)) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
function normalizeSubcommandNameForDistance(name) {
|
|
311
|
+
return camelToKebabCase$1(name).toLowerCase();
|
|
312
|
+
}
|
|
313
|
+
function levenshteinDistance(left, right) {
|
|
314
|
+
if (left === right) {
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
if (left.length === 0) {
|
|
318
|
+
return right.length;
|
|
319
|
+
}
|
|
320
|
+
if (right.length === 0) {
|
|
321
|
+
return left.length;
|
|
322
|
+
}
|
|
323
|
+
let prev = Array.from({ length: right.length + 1 }, (_, i) => i);
|
|
324
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
325
|
+
const current = [i + 1];
|
|
326
|
+
for (let j = 0; j < right.length; j += 1) {
|
|
327
|
+
const substitutionCost = left[i] === right[j] ? 0 : 1;
|
|
328
|
+
current[j + 1] = Math.min(current[j] + 1, prev[j + 1] + 1, prev[j] + substitutionCost);
|
|
329
|
+
}
|
|
330
|
+
prev = current;
|
|
331
|
+
}
|
|
332
|
+
return prev[right.length];
|
|
333
|
+
}
|
|
200
334
|
function tokenizeLongOption(arg, commandPath) {
|
|
201
335
|
const eqIdx = arg.indexOf('=');
|
|
202
336
|
const namePart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
|
|
@@ -433,9 +567,13 @@ class Command {
|
|
|
433
567
|
if (cmd.#parent && cmd.#parent !== this) {
|
|
434
568
|
throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
|
|
435
569
|
}
|
|
570
|
+
const occupied = this.#subcommandsMap.get(name);
|
|
571
|
+
if (occupied && occupied !== cmd) {
|
|
572
|
+
throw new CommanderError('ConfigurationError', `subcommand name/alias "${name}" conflicts with an existing command`, this.#getCommandPath());
|
|
573
|
+
}
|
|
436
574
|
const existing = this.#subcommandsList.find(e => e.command === cmd);
|
|
437
575
|
if (existing) {
|
|
438
|
-
if (existing.aliases.includes(name)) {
|
|
576
|
+
if (existing.name === name || existing.aliases.includes(name)) {
|
|
439
577
|
return this;
|
|
440
578
|
}
|
|
441
579
|
existing.aliases.push(name);
|
|
@@ -564,16 +702,41 @@ class Command {
|
|
|
564
702
|
else if (arg.kind === 'optional') {
|
|
565
703
|
usage += ` [${arg.name}]`;
|
|
566
704
|
}
|
|
705
|
+
else if (arg.kind === 'some') {
|
|
706
|
+
usage += ` <${arg.name}...>`;
|
|
707
|
+
}
|
|
567
708
|
else {
|
|
568
709
|
usage += ` [${arg.name}...]`;
|
|
569
710
|
}
|
|
570
711
|
}
|
|
712
|
+
const argumentsLines = [];
|
|
713
|
+
for (const arg of this.#arguments) {
|
|
714
|
+
const sig = arg.kind === 'required'
|
|
715
|
+
? `<${arg.name}>`
|
|
716
|
+
: arg.kind === 'optional'
|
|
717
|
+
? `[${arg.name}]`
|
|
718
|
+
: arg.kind === 'some'
|
|
719
|
+
? `<${arg.name}...>`
|
|
720
|
+
: `[${arg.name}...]`;
|
|
721
|
+
const metadata = [`[type: ${arg.type}]`];
|
|
722
|
+
if (arg.kind === 'optional' && arg.default !== undefined) {
|
|
723
|
+
metadata.push(`[default: ${JSON.stringify(arg.default)}]`);
|
|
724
|
+
}
|
|
725
|
+
if (arg.choices && arg.choices.length > 0) {
|
|
726
|
+
metadata.push(`[choices: ${arg.choices.map(choice => JSON.stringify(choice)).join(', ')}]`);
|
|
727
|
+
}
|
|
728
|
+
const desc = metadata.length > 0 ? `${arg.desc} ${metadata.join(' ')}` : arg.desc;
|
|
729
|
+
argumentsLines.push({ sig, desc });
|
|
730
|
+
}
|
|
571
731
|
const options = [];
|
|
572
732
|
for (const opt of allOptions) {
|
|
573
733
|
const kebabLong = camelToKebabCase$1(opt.long);
|
|
574
734
|
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
575
735
|
sig += `--${kebabLong}`;
|
|
576
|
-
if (opt.args
|
|
736
|
+
if (opt.args === 'optional') {
|
|
737
|
+
sig += ' [value]';
|
|
738
|
+
}
|
|
739
|
+
else if (opt.args !== 'none') {
|
|
577
740
|
sig += ' <value>';
|
|
578
741
|
}
|
|
579
742
|
let desc = opt.desc;
|
|
@@ -581,7 +744,7 @@ class Command {
|
|
|
581
744
|
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
582
745
|
}
|
|
583
746
|
if (opt.choices) {
|
|
584
|
-
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
747
|
+
desc += ` [choices: ${opt.choices.map(choice => JSON.stringify(choice)).join(', ')}]`;
|
|
585
748
|
}
|
|
586
749
|
options.push({ sig, desc });
|
|
587
750
|
if (opt.type === 'boolean' &&
|
|
@@ -613,6 +776,7 @@ class Command {
|
|
|
613
776
|
return {
|
|
614
777
|
desc: this.#desc,
|
|
615
778
|
usage,
|
|
779
|
+
arguments: argumentsLines,
|
|
616
780
|
options,
|
|
617
781
|
commands,
|
|
618
782
|
examples,
|
|
@@ -620,25 +784,29 @@ class Command {
|
|
|
620
784
|
}
|
|
621
785
|
#renderHelpPlain(helpData) {
|
|
622
786
|
const lines = [];
|
|
787
|
+
const labelWidth = this.#getHelpLabelWidth(helpData);
|
|
623
788
|
lines.push(helpData.desc);
|
|
624
789
|
lines.push('');
|
|
625
790
|
lines.push(helpData.usage);
|
|
626
791
|
lines.push('');
|
|
792
|
+
if (helpData.arguments.length > 0) {
|
|
793
|
+
lines.push('Arguments:');
|
|
794
|
+
for (const { sig, desc } of helpData.arguments) {
|
|
795
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
|
|
796
|
+
}
|
|
797
|
+
lines.push('');
|
|
798
|
+
}
|
|
627
799
|
if (helpData.options.length > 0) {
|
|
628
800
|
lines.push('Options:');
|
|
629
|
-
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
630
801
|
for (const { sig, desc } of helpData.options) {
|
|
631
|
-
|
|
632
|
-
lines.push(` ${sig}${padding}${desc}`);
|
|
802
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
|
|
633
803
|
}
|
|
634
804
|
lines.push('');
|
|
635
805
|
}
|
|
636
806
|
if (helpData.commands.length > 0) {
|
|
637
807
|
lines.push('Commands:');
|
|
638
|
-
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
639
808
|
for (const { name, desc } of helpData.commands) {
|
|
640
|
-
|
|
641
|
-
lines.push(` ${name}${padding}${desc}`);
|
|
809
|
+
lines.push(this.#renderAlignedHelpLine(name, desc, labelWidth));
|
|
642
810
|
}
|
|
643
811
|
lines.push('');
|
|
644
812
|
}
|
|
@@ -655,25 +823,29 @@ class Command {
|
|
|
655
823
|
}
|
|
656
824
|
#renderHelpTerminal(helpData) {
|
|
657
825
|
const lines = [];
|
|
826
|
+
const labelWidth = this.#getHelpLabelWidth(helpData);
|
|
658
827
|
lines.push(helpData.desc);
|
|
659
828
|
lines.push('');
|
|
660
829
|
lines.push(styleText(helpData.usage, TERMINAL_STYLE.bold));
|
|
661
830
|
lines.push('');
|
|
831
|
+
if (helpData.arguments.length > 0) {
|
|
832
|
+
lines.push(styleText('Arguments:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
833
|
+
for (const { sig, desc } of helpData.arguments) {
|
|
834
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
|
|
835
|
+
}
|
|
836
|
+
lines.push('');
|
|
837
|
+
}
|
|
662
838
|
if (helpData.options.length > 0) {
|
|
663
839
|
lines.push(styleText('Options:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
664
|
-
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
665
840
|
for (const { sig, desc } of helpData.options) {
|
|
666
|
-
|
|
667
|
-
lines.push(` ${styleText(sig, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
841
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
|
|
668
842
|
}
|
|
669
843
|
lines.push('');
|
|
670
844
|
}
|
|
671
845
|
if (helpData.commands.length > 0) {
|
|
672
846
|
lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
673
|
-
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
674
847
|
for (const { name, desc } of helpData.commands) {
|
|
675
|
-
|
|
676
|
-
lines.push(` ${styleText(name, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
848
|
+
lines.push(this.#renderAlignedHelpLine(name, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
|
|
677
849
|
}
|
|
678
850
|
lines.push('');
|
|
679
851
|
}
|
|
@@ -688,16 +860,41 @@ class Command {
|
|
|
688
860
|
}
|
|
689
861
|
return lines.join('\n');
|
|
690
862
|
}
|
|
863
|
+
#getHelpLabelWidth(helpData) {
|
|
864
|
+
const labels = [
|
|
865
|
+
...helpData.arguments.map(line => line.sig),
|
|
866
|
+
...helpData.options.map(line => line.sig),
|
|
867
|
+
...helpData.commands.map(line => line.name),
|
|
868
|
+
];
|
|
869
|
+
if (labels.length === 0) {
|
|
870
|
+
return 0;
|
|
871
|
+
}
|
|
872
|
+
return Math.max(...labels.map(getDisplayWidth));
|
|
873
|
+
}
|
|
874
|
+
#renderAlignedHelpLine(label, desc, labelWidth, styleLabel) {
|
|
875
|
+
const paddedLabel = padDisplayEnd(label, labelWidth);
|
|
876
|
+
const outputLabel = styleLabel ? styleLabel(paddedLabel) : paddedLabel;
|
|
877
|
+
return ` ${outputLabel} ${desc}`;
|
|
878
|
+
}
|
|
691
879
|
getCompletionMeta() {
|
|
692
880
|
const allOptions = this.#resolveOptionPolicy().mergedOptions;
|
|
693
881
|
const options = [];
|
|
882
|
+
const argumentsMeta = [];
|
|
694
883
|
for (const opt of allOptions) {
|
|
695
884
|
options.push({
|
|
696
885
|
long: opt.long,
|
|
697
886
|
short: opt.short,
|
|
698
887
|
desc: opt.desc,
|
|
699
888
|
takesValue: opt.args !== 'none',
|
|
700
|
-
choices: opt.choices,
|
|
889
|
+
choices: opt.choices?.map(choice => String(choice)),
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
for (const arg of this.#arguments) {
|
|
893
|
+
argumentsMeta.push({
|
|
894
|
+
name: arg.name,
|
|
895
|
+
kind: arg.kind,
|
|
896
|
+
type: arg.type,
|
|
897
|
+
choices: arg.type === 'choice' ? arg.choices?.map(choice => String(choice)) : undefined,
|
|
701
898
|
});
|
|
702
899
|
}
|
|
703
900
|
return {
|
|
@@ -705,6 +902,7 @@ class Command {
|
|
|
705
902
|
desc: this.#desc,
|
|
706
903
|
aliases: [],
|
|
707
904
|
options,
|
|
905
|
+
arguments: argumentsMeta,
|
|
708
906
|
subcommands: this.#subcommandsList.map(entry => {
|
|
709
907
|
const subMeta = entry.command.getCompletionMeta();
|
|
710
908
|
return {
|
|
@@ -1157,6 +1355,14 @@ class Command {
|
|
|
1157
1355
|
consumed.push(tokens[i]);
|
|
1158
1356
|
}
|
|
1159
1357
|
}
|
|
1358
|
+
else if (opt.args === 'optional') {
|
|
1359
|
+
if (!token.resolved.includes('=') &&
|
|
1360
|
+
i + 1 < tokens.length &&
|
|
1361
|
+
tokens[i + 1].type === 'none') {
|
|
1362
|
+
i += 1;
|
|
1363
|
+
consumed.push(tokens[i]);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1160
1366
|
else if (opt.args === 'variadic') {
|
|
1161
1367
|
if (!token.resolved.includes('=')) {
|
|
1162
1368
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
@@ -1182,6 +1388,12 @@ class Command {
|
|
|
1182
1388
|
consumed.push(tokens[i]);
|
|
1183
1389
|
}
|
|
1184
1390
|
}
|
|
1391
|
+
else if (opt.args === 'optional') {
|
|
1392
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1393
|
+
i += 1;
|
|
1394
|
+
consumed.push(tokens[i]);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1185
1397
|
else if (opt.args === 'variadic') {
|
|
1186
1398
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1187
1399
|
i += 1;
|
|
@@ -1223,6 +1435,7 @@ class Command {
|
|
|
1223
1435
|
leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
|
|
1224
1436
|
}
|
|
1225
1437
|
}
|
|
1438
|
+
leafCommand.#assertUnknownSubcommand(ctx.sources.user.argv);
|
|
1226
1439
|
const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
|
|
1227
1440
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
1228
1441
|
const parseCtx = {
|
|
@@ -1305,6 +1518,23 @@ class Command {
|
|
|
1305
1518
|
i += 1;
|
|
1306
1519
|
continue;
|
|
1307
1520
|
}
|
|
1521
|
+
if (opt.args === 'optional') {
|
|
1522
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1523
|
+
if (eqIdx !== -1) {
|
|
1524
|
+
opts[opt.long] = this.#convertValue(opt, token.resolved.slice(eqIdx + 1));
|
|
1525
|
+
i += 1;
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1529
|
+
opts[opt.long] = this.#convertValue(opt, tokens[i + 1].original);
|
|
1530
|
+
i += 1;
|
|
1531
|
+
}
|
|
1532
|
+
else {
|
|
1533
|
+
opts[opt.long] = undefined;
|
|
1534
|
+
}
|
|
1535
|
+
i += 1;
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1308
1538
|
if (opt.args === 'variadic') {
|
|
1309
1539
|
const values = Array.isArray(opts[opt.long]) ? opts[opt.long] : [];
|
|
1310
1540
|
const eqIdx = token.resolved.indexOf('=');
|
|
@@ -1324,7 +1554,7 @@ class Command {
|
|
|
1324
1554
|
i += 1;
|
|
1325
1555
|
}
|
|
1326
1556
|
for (const opt of allOptions) {
|
|
1327
|
-
if (opt.required && opts
|
|
1557
|
+
if (opt.required && !Object.prototype.hasOwnProperty.call(opts, opt.long)) {
|
|
1328
1558
|
throw new CommanderError('MissingRequired', `missing required option "--${camelToKebabCase$1(opt.long)}"`, this.#getCommandPath());
|
|
1329
1559
|
}
|
|
1330
1560
|
}
|
|
@@ -1350,8 +1580,8 @@ class Command {
|
|
|
1350
1580
|
return opt.coerce(rawValue);
|
|
1351
1581
|
}
|
|
1352
1582
|
if (opt.type === 'number') {
|
|
1353
|
-
const num =
|
|
1354
|
-
if (
|
|
1583
|
+
const num = parsePrimitiveNumber(rawValue);
|
|
1584
|
+
if (num === undefined) {
|
|
1355
1585
|
throw new CommanderError('InvalidType', `invalid number "${rawValue}" for option "--${camelToKebabCase$1(opt.long)}"`, this.#getCommandPath());
|
|
1356
1586
|
}
|
|
1357
1587
|
return num;
|
|
@@ -1361,12 +1591,37 @@ class Command {
|
|
|
1361
1591
|
#parseArguments(rawArgs) {
|
|
1362
1592
|
const argumentDefs = this.#arguments;
|
|
1363
1593
|
const args = {};
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1594
|
+
if (argumentDefs.length === 0 && rawArgs.length > 0) {
|
|
1595
|
+
throw new CommanderError('UnexpectedArgument', `unexpected argument "${rawArgs[0]}"`, this.#getCommandPath());
|
|
1596
|
+
}
|
|
1597
|
+
const missing = [];
|
|
1598
|
+
let remaining = rawArgs.length;
|
|
1599
|
+
for (const def of argumentDefs) {
|
|
1600
|
+
if (def.kind === 'required') {
|
|
1601
|
+
if (remaining === 0) {
|
|
1602
|
+
missing.push(def.name);
|
|
1603
|
+
}
|
|
1604
|
+
else {
|
|
1605
|
+
remaining -= 1;
|
|
1606
|
+
}
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
if (def.kind === 'optional') {
|
|
1610
|
+
if (remaining > 0) {
|
|
1611
|
+
remaining -= 1;
|
|
1612
|
+
}
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
if (def.kind === 'some') {
|
|
1616
|
+
if (remaining === 0) {
|
|
1617
|
+
missing.push(def.name);
|
|
1618
|
+
}
|
|
1619
|
+
remaining = 0;
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
remaining = 0;
|
|
1623
|
+
}
|
|
1624
|
+
if (missing.length > 0) {
|
|
1370
1625
|
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${missing.join(', ')}`, this.#getCommandPath());
|
|
1371
1626
|
}
|
|
1372
1627
|
let index = 0;
|
|
@@ -1377,41 +1632,101 @@ class Command {
|
|
|
1377
1632
|
index = rawArgs.length;
|
|
1378
1633
|
break;
|
|
1379
1634
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1635
|
+
if (def.kind === 'some') {
|
|
1636
|
+
const rest = rawArgs.slice(index);
|
|
1637
|
+
args[def.name] = rest.map(raw => this.#convertArgument(def, raw));
|
|
1638
|
+
index = rawArgs.length;
|
|
1639
|
+
break;
|
|
1640
|
+
}
|
|
1641
|
+
if (def.kind === 'optional') {
|
|
1642
|
+
const raw = rawArgs[index];
|
|
1643
|
+
if (raw === undefined) {
|
|
1383
1644
|
args[def.name] = def.default ?? undefined;
|
|
1384
1645
|
continue;
|
|
1385
1646
|
}
|
|
1386
|
-
}
|
|
1387
|
-
else {
|
|
1388
1647
|
args[def.name] = this.#convertArgument(def, raw);
|
|
1389
1648
|
index += 1;
|
|
1649
|
+
continue;
|
|
1390
1650
|
}
|
|
1651
|
+
const raw = rawArgs[index];
|
|
1652
|
+
args[def.name] = this.#convertArgument(def, raw);
|
|
1653
|
+
index += 1;
|
|
1391
1654
|
}
|
|
1392
|
-
const
|
|
1393
|
-
if (!
|
|
1655
|
+
const hasRestArgument = argumentDefs.some(a => a.kind === 'variadic' || a.kind === 'some');
|
|
1656
|
+
if (!hasRestArgument && index < rawArgs.length) {
|
|
1394
1657
|
throw new CommanderError('TooManyArguments', `too many arguments: expected ${argumentDefs.length}, got ${rawArgs.length}`, this.#getCommandPath());
|
|
1395
1658
|
}
|
|
1396
1659
|
return { args, rawArgs };
|
|
1397
1660
|
}
|
|
1398
1661
|
#convertArgument(def, raw) {
|
|
1662
|
+
let value;
|
|
1399
1663
|
if (def.coerce) {
|
|
1400
1664
|
try {
|
|
1401
|
-
|
|
1665
|
+
value = def.coerce(raw);
|
|
1402
1666
|
}
|
|
1403
1667
|
catch {
|
|
1404
1668
|
throw new CommanderError('InvalidType', `invalid value "${raw}" for argument "${def.name}"`, this.#getCommandPath());
|
|
1405
1669
|
}
|
|
1406
1670
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1671
|
+
else {
|
|
1672
|
+
value = raw;
|
|
1673
|
+
}
|
|
1674
|
+
if (typeof value !== 'string') {
|
|
1675
|
+
throw new CommanderError('InvalidType', `invalid value for argument "${def.name}": expected ${def.type}`, this.#getCommandPath());
|
|
1676
|
+
}
|
|
1677
|
+
if (def.type === 'choice') {
|
|
1678
|
+
const choices = def.choices ?? [];
|
|
1679
|
+
if (!choices.includes(value)) {
|
|
1680
|
+
throw new CommanderError('InvalidChoice', `invalid value "${value}" for argument "${def.name}". Allowed: ${choices
|
|
1681
|
+
.map(choice => JSON.stringify(choice))
|
|
1682
|
+
.join(', ')}`, this.#getCommandPath());
|
|
1411
1683
|
}
|
|
1412
|
-
return n;
|
|
1413
1684
|
}
|
|
1414
|
-
return
|
|
1685
|
+
return value;
|
|
1686
|
+
}
|
|
1687
|
+
#assertUnknownSubcommand(userTailArgv) {
|
|
1688
|
+
if (this.#subcommandsList.length === 0) {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
const token = userTailArgv[0];
|
|
1692
|
+
if (token === undefined || token.startsWith('-') || token === 'help') {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
if (this.#findSubcommandEntry(token) !== undefined) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const hints = [];
|
|
1699
|
+
if (this.#arguments.length === 0) {
|
|
1700
|
+
hints.push(`Hint: command "${this.#getCommandPath()}" does not accept positional arguments.`);
|
|
1701
|
+
}
|
|
1702
|
+
const candidate = this.#resolveDidYouMeanSubcommandName(token);
|
|
1703
|
+
if (candidate !== undefined) {
|
|
1704
|
+
hints.push(`Hint: did you mean "${candidate}"?`);
|
|
1705
|
+
}
|
|
1706
|
+
const details = hints.length > 0 ? `\n${hints.join('\n')}` : '';
|
|
1707
|
+
throw new CommanderError('UnknownSubcommand', `unknown subcommand "${token}" for command "${this.#getCommandPath()}"${details}`, this.#getCommandPath());
|
|
1708
|
+
}
|
|
1709
|
+
#resolveDidYouMeanSubcommandName(token) {
|
|
1710
|
+
const source = normalizeSubcommandNameForDistance(token);
|
|
1711
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
1712
|
+
let bestName;
|
|
1713
|
+
let isUniqueBest = false;
|
|
1714
|
+
for (const entry of this.#subcommandsList) {
|
|
1715
|
+
const target = normalizeSubcommandNameForDistance(entry.name);
|
|
1716
|
+
const distance = levenshteinDistance(source, target);
|
|
1717
|
+
if (distance < minDistance) {
|
|
1718
|
+
minDistance = distance;
|
|
1719
|
+
bestName = entry.name;
|
|
1720
|
+
isUniqueBest = true;
|
|
1721
|
+
}
|
|
1722
|
+
else if (distance === minDistance) {
|
|
1723
|
+
isUniqueBest = false;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
if (minDistance <= 2 && isUniqueBest) {
|
|
1727
|
+
return bestName;
|
|
1728
|
+
}
|
|
1729
|
+
return undefined;
|
|
1415
1730
|
}
|
|
1416
1731
|
#hasUserOption(long) {
|
|
1417
1732
|
return this.#options.some(option => option.long === long);
|
|
@@ -1456,11 +1771,9 @@ class Command {
|
|
|
1456
1771
|
return optionPolicyMap;
|
|
1457
1772
|
}
|
|
1458
1773
|
#mustGetOptionPolicy(optionPolicyMap, cmd) {
|
|
1459
|
-
const policy = optionPolicyMap.get(cmd);
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
}
|
|
1463
|
-
throw new CommanderError('ConfigurationError', `missing option policy for command "${cmd.#getCommandPath()}"`, this.#getCommandPath());
|
|
1774
|
+
const policy = optionPolicyMap.get(cmd) ?? cmd.#resolveOptionPolicy();
|
|
1775
|
+
optionPolicyMap.set(cmd, policy);
|
|
1776
|
+
return policy;
|
|
1464
1777
|
}
|
|
1465
1778
|
#validateMergedShortOptions(chain, optionPolicyMap) {
|
|
1466
1779
|
const mergedByLong = new Map();
|
|
@@ -1489,7 +1802,10 @@ class Command {
|
|
|
1489
1802
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
1490
1803
|
}
|
|
1491
1804
|
if ((opt.type === 'string' || opt.type === 'number') && opt.args === 'none') {
|
|
1492
|
-
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required' or 'variadic'`, this.#getCommandPath());
|
|
1805
|
+
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required', 'optional', or 'variadic'`, this.#getCommandPath());
|
|
1806
|
+
}
|
|
1807
|
+
if (opt.type === 'number' && opt.args === 'optional') {
|
|
1808
|
+
throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, this.#getCommandPath());
|
|
1493
1809
|
}
|
|
1494
1810
|
if (opt.long.startsWith('no')) {
|
|
1495
1811
|
throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, this.#getCommandPath());
|
|
@@ -1497,12 +1813,18 @@ class Command {
|
|
|
1497
1813
|
if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
|
|
1498
1814
|
throw new CommanderError('ConfigurationError', `option long name must be camelCase: "${opt.long}"`, this.#getCommandPath());
|
|
1499
1815
|
}
|
|
1816
|
+
if (opt.short !== undefined && opt.short.length !== 1) {
|
|
1817
|
+
throw new CommanderError('ConfigurationError', `option short name must be a single character: "${opt.short}"`, this.#getCommandPath());
|
|
1818
|
+
}
|
|
1500
1819
|
if (opt.required && opt.default !== undefined) {
|
|
1501
1820
|
throw new CommanderError('ConfigurationError', `option "--${opt.long}" cannot be both required and have a default value`, this.#getCommandPath());
|
|
1502
1821
|
}
|
|
1503
1822
|
if (opt.type === 'boolean' && opt.required) {
|
|
1504
1823
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, this.#getCommandPath());
|
|
1505
1824
|
}
|
|
1825
|
+
if (opt.required && opt.args !== 'required') {
|
|
1826
|
+
throw new CommanderError('ConfigurationError', `required option "--${opt.long}" must use args: 'required'`, this.#getCommandPath());
|
|
1827
|
+
}
|
|
1506
1828
|
}
|
|
1507
1829
|
#checkOptionUniqueness(opt) {
|
|
1508
1830
|
if (this.#options.some(o => o.long === opt.long)) {
|
|
@@ -1513,24 +1835,58 @@ class Command {
|
|
|
1513
1835
|
}
|
|
1514
1836
|
}
|
|
1515
1837
|
#validateArgumentConfig(arg) {
|
|
1516
|
-
if (arg.kind
|
|
1517
|
-
|
|
1838
|
+
if (arg.kind !== 'required' &&
|
|
1839
|
+
arg.kind !== 'optional' &&
|
|
1840
|
+
arg.kind !== 'variadic' &&
|
|
1841
|
+
arg.kind !== 'some') {
|
|
1842
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" must specify a valid kind`, this.#getCommandPath());
|
|
1843
|
+
}
|
|
1844
|
+
if (arg.type !== 'string' && arg.type !== 'choice') {
|
|
1845
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" must specify a valid type`, this.#getCommandPath());
|
|
1518
1846
|
}
|
|
1519
|
-
if (arg.kind
|
|
1520
|
-
|
|
1521
|
-
|
|
1847
|
+
if (arg.default !== undefined && arg.kind !== 'optional') {
|
|
1848
|
+
throw new CommanderError('ConfigurationError', `only optional argument "${arg.name}" can have a default value`, this.#getCommandPath());
|
|
1849
|
+
}
|
|
1850
|
+
if (arg.type === 'string' && arg.choices !== undefined) {
|
|
1851
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" of type "string" cannot declare choices`, this.#getCommandPath());
|
|
1852
|
+
}
|
|
1853
|
+
if (arg.type === 'choice') {
|
|
1854
|
+
if (!Array.isArray(arg.choices) || arg.choices.length === 0) {
|
|
1855
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" of type "choice" must declare a non-empty choices array`, this.#getCommandPath());
|
|
1856
|
+
}
|
|
1857
|
+
if (arg.choices.some(choice => typeof choice !== 'string')) {
|
|
1858
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" choices must be string[]`, this.#getCommandPath());
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
if (arg.default !== undefined) {
|
|
1862
|
+
this.#validateArgumentDefaultValue(arg);
|
|
1863
|
+
}
|
|
1864
|
+
if (arg.kind === 'variadic' || arg.kind === 'some') {
|
|
1865
|
+
if (this.#arguments.some(a => a.kind === 'variadic' || a.kind === 'some')) {
|
|
1866
|
+
throw new CommanderError('ConfigurationError', 'only one variadic/some argument is allowed', this.#getCommandPath());
|
|
1522
1867
|
}
|
|
1523
1868
|
}
|
|
1524
1869
|
if (this.#arguments.length > 0) {
|
|
1525
1870
|
const last = this.#arguments[this.#arguments.length - 1];
|
|
1526
|
-
if (last.kind === 'variadic') {
|
|
1527
|
-
throw new CommanderError('ConfigurationError', 'variadic argument must be the last argument', this.#getCommandPath());
|
|
1871
|
+
if (last.kind === 'variadic' || last.kind === 'some') {
|
|
1872
|
+
throw new CommanderError('ConfigurationError', 'variadic/some argument must be the last argument', this.#getCommandPath());
|
|
1528
1873
|
}
|
|
1529
1874
|
}
|
|
1530
1875
|
if (arg.kind === 'required') {
|
|
1531
|
-
const hasOptional = this.#arguments.some(a => a.kind === 'optional' || a.kind === 'variadic');
|
|
1876
|
+
const hasOptional = this.#arguments.some(a => a.kind === 'optional' || a.kind === 'variadic' || a.kind === 'some');
|
|
1532
1877
|
if (hasOptional) {
|
|
1533
|
-
throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot come after optional/variadic arguments`, this.#getCommandPath());
|
|
1878
|
+
throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot come after optional/variadic/some arguments`, this.#getCommandPath());
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
#validateArgumentDefaultValue(arg) {
|
|
1883
|
+
if (typeof arg.default !== 'string') {
|
|
1884
|
+
throw new CommanderError('ConfigurationError', `default value for argument "${arg.name}" must match type "${arg.type}"`, this.#getCommandPath());
|
|
1885
|
+
}
|
|
1886
|
+
if (arg.type === 'choice') {
|
|
1887
|
+
const choices = arg.choices ?? [];
|
|
1888
|
+
if (!choices.includes(arg.default)) {
|
|
1889
|
+
throw new CommanderError('ConfigurationError', `default value for argument "${arg.name}" must be one of declared choices`, this.#getCommandPath());
|
|
1534
1890
|
}
|
|
1535
1891
|
}
|
|
1536
1892
|
}
|
|
@@ -1777,6 +2133,35 @@ class Coerce {
|
|
|
1777
2133
|
function camelToKebabCase(str) {
|
|
1778
2134
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1779
2135
|
}
|
|
2136
|
+
const COMPLETION_SHELL_STATE = Symbol('completion-shell-state');
|
|
2137
|
+
function getCommandPath(ctx) {
|
|
2138
|
+
const names = ctx.chain
|
|
2139
|
+
.map(command => command.name)
|
|
2140
|
+
.filter((name) => Boolean(name));
|
|
2141
|
+
if (names.length > 0) {
|
|
2142
|
+
return names.join(' ');
|
|
2143
|
+
}
|
|
2144
|
+
return ctx.cmd.name ?? 'command';
|
|
2145
|
+
}
|
|
2146
|
+
function getCompletionShellState(ctx) {
|
|
2147
|
+
const host = ctx;
|
|
2148
|
+
host[COMPLETION_SHELL_STATE] ??= {};
|
|
2149
|
+
return host[COMPLETION_SHELL_STATE];
|
|
2150
|
+
}
|
|
2151
|
+
function registerCompletionShell(ctx, shell) {
|
|
2152
|
+
const state = getCompletionShellState(ctx);
|
|
2153
|
+
if (state.shell !== undefined && state.shell !== shell) {
|
|
2154
|
+
throw new CommanderError('OptionConflict', 'options "--bash", "--fish", and "--pwsh" are mutually exclusive', getCommandPath(ctx));
|
|
2155
|
+
}
|
|
2156
|
+
state.shell = shell;
|
|
2157
|
+
}
|
|
2158
|
+
function mustGetCompletionShell(ctx) {
|
|
2159
|
+
const state = getCompletionShellState(ctx);
|
|
2160
|
+
if (state.shell === undefined) {
|
|
2161
|
+
throw new CommanderError('MissingRequired', 'missing required option: one of "--bash", "--fish", or "--pwsh"', getCommandPath(ctx));
|
|
2162
|
+
}
|
|
2163
|
+
return state.shell;
|
|
2164
|
+
}
|
|
1780
2165
|
class CompletionCommand extends Command {
|
|
1781
2166
|
constructor(root, config = {}) {
|
|
1782
2167
|
const programName = config.programName ?? root.name ?? 'program';
|
|
@@ -1790,45 +2175,45 @@ class CompletionCommand extends Command {
|
|
|
1790
2175
|
type: 'boolean',
|
|
1791
2176
|
args: 'none',
|
|
1792
2177
|
desc: 'Generate Bash completion script',
|
|
2178
|
+
apply: (value, ctx) => {
|
|
2179
|
+
if (value === true) {
|
|
2180
|
+
registerCompletionShell(ctx, 'bash');
|
|
2181
|
+
}
|
|
2182
|
+
},
|
|
1793
2183
|
})
|
|
1794
2184
|
.option({
|
|
1795
2185
|
long: 'fish',
|
|
1796
2186
|
type: 'boolean',
|
|
1797
2187
|
args: 'none',
|
|
1798
2188
|
desc: 'Generate Fish completion script',
|
|
2189
|
+
apply: (value, ctx) => {
|
|
2190
|
+
if (value === true) {
|
|
2191
|
+
registerCompletionShell(ctx, 'fish');
|
|
2192
|
+
}
|
|
2193
|
+
},
|
|
1799
2194
|
})
|
|
1800
2195
|
.option({
|
|
1801
2196
|
long: 'pwsh',
|
|
1802
2197
|
type: 'boolean',
|
|
1803
2198
|
args: 'none',
|
|
1804
2199
|
desc: 'Generate PowerShell completion script',
|
|
2200
|
+
apply: (value, ctx) => {
|
|
2201
|
+
if (value === true) {
|
|
2202
|
+
registerCompletionShell(ctx, 'pwsh');
|
|
2203
|
+
}
|
|
2204
|
+
mustGetCompletionShell(ctx);
|
|
2205
|
+
},
|
|
1805
2206
|
})
|
|
1806
2207
|
.option({
|
|
1807
2208
|
long: 'write',
|
|
1808
2209
|
short: 'w',
|
|
1809
2210
|
type: 'string',
|
|
1810
|
-
args: '
|
|
1811
|
-
desc: 'Write to file (use shell default path
|
|
1812
|
-
default: undefined,
|
|
2211
|
+
args: 'optional',
|
|
2212
|
+
desc: 'Write to file (use shell default path when value is omitted or empty)',
|
|
1813
2213
|
})
|
|
1814
|
-
.action(({ opts }) => {
|
|
2214
|
+
.action(({ opts, ctx }) => {
|
|
1815
2215
|
const meta = root.getCompletionMeta();
|
|
1816
|
-
const
|
|
1817
|
-
opts['bash'] && 'bash',
|
|
1818
|
-
opts['fish'] && 'fish',
|
|
1819
|
-
opts['pwsh'] && 'pwsh',
|
|
1820
|
-
].filter(Boolean);
|
|
1821
|
-
if (selectedShells.length === 0) {
|
|
1822
|
-
console.error('Please specify a shell: --bash, --fish, or --pwsh');
|
|
1823
|
-
process.exit(1);
|
|
1824
|
-
return;
|
|
1825
|
-
}
|
|
1826
|
-
if (selectedShells.length > 1) {
|
|
1827
|
-
console.error('Please specify only one shell option');
|
|
1828
|
-
process.exit(1);
|
|
1829
|
-
return;
|
|
1830
|
-
}
|
|
1831
|
-
const shell = selectedShells[0];
|
|
2216
|
+
const shell = mustGetCompletionShell(ctx);
|
|
1832
2217
|
let script;
|
|
1833
2218
|
switch (shell) {
|
|
1834
2219
|
case 'bash':
|
|
@@ -1841,8 +2226,9 @@ class CompletionCommand extends Command {
|
|
|
1841
2226
|
script = new PwshCompletion(meta, programName).generate();
|
|
1842
2227
|
break;
|
|
1843
2228
|
}
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
2229
|
+
const hasWrite = Object.prototype.hasOwnProperty.call(opts, 'write');
|
|
2230
|
+
if (hasWrite) {
|
|
2231
|
+
const writeOpt = opts['write'];
|
|
1846
2232
|
const filePath = typeof writeOpt === 'string' && writeOpt !== '' ? writeOpt : paths[shell];
|
|
1847
2233
|
const expandedPath = expandHome(filePath);
|
|
1848
2234
|
const dir = path.dirname(expandedPath);
|
|
@@ -1887,6 +2273,7 @@ class BashCompletion {
|
|
|
1887
2273
|
'',
|
|
1888
2274
|
`${funcName}() {`,
|
|
1889
2275
|
' local cur prev words cword',
|
|
2276
|
+
' local opts arg_choices prefer_value_choices',
|
|
1890
2277
|
' _init_completion || return',
|
|
1891
2278
|
'',
|
|
1892
2279
|
...this.#generateCommandCase(this.#meta, 1),
|
|
@@ -1906,13 +2293,15 @@ class BashCompletion {
|
|
|
1906
2293
|
for (const opt of cmd.options) {
|
|
1907
2294
|
const kebabLong = camelToKebabCase(opt.long);
|
|
1908
2295
|
if (opt.short)
|
|
1909
|
-
optParts.push(`-${opt.short}`);
|
|
1910
|
-
optParts.push(`--${kebabLong}`);
|
|
2296
|
+
optParts.push(this.#escapeWord(`-${opt.short}`));
|
|
2297
|
+
optParts.push(this.#escapeWord(`--${kebabLong}`));
|
|
1911
2298
|
if (!opt.takesValue) {
|
|
1912
|
-
optParts.push(`--no-${kebabLong}`);
|
|
2299
|
+
optParts.push(this.#escapeWord(`--no-${kebabLong}`));
|
|
1913
2300
|
}
|
|
1914
2301
|
}
|
|
1915
|
-
const subParts = cmd.subcommands
|
|
2302
|
+
const subParts = cmd.subcommands
|
|
2303
|
+
.flatMap(sub => [sub.name, ...sub.aliases])
|
|
2304
|
+
.map(value => this.#escapeWord(value));
|
|
1916
2305
|
const allOpts = [...optParts, ...subParts].join(' ');
|
|
1917
2306
|
if (cmd.subcommands.length > 0) {
|
|
1918
2307
|
lines.push(`${indent}case "\${words[${depth}]}" in`);
|
|
@@ -1924,14 +2313,105 @@ class BashCompletion {
|
|
|
1924
2313
|
}
|
|
1925
2314
|
lines.push(`${indent} *)`);
|
|
1926
2315
|
lines.push(`${indent} opts="${allOpts}"`);
|
|
2316
|
+
this.#appendChoiceLogicForCommand(lines, `${indent} `, cmd, depth);
|
|
1927
2317
|
lines.push(`${indent} ;;`);
|
|
1928
2318
|
lines.push(`${indent}esac`);
|
|
1929
2319
|
}
|
|
1930
2320
|
else {
|
|
1931
2321
|
lines.push(`${indent}opts="${allOpts}"`);
|
|
2322
|
+
this.#appendChoiceLogicForCommand(lines, indent, cmd, depth);
|
|
1932
2323
|
}
|
|
1933
2324
|
return lines;
|
|
1934
2325
|
}
|
|
2326
|
+
#serializeWordList(words) {
|
|
2327
|
+
return words.map(choice => this.#escapeWord(choice)).join(' ');
|
|
2328
|
+
}
|
|
2329
|
+
#appendChoiceLogicForCommand(lines, indent, cmd, depth) {
|
|
2330
|
+
const valueOptions = cmd.options.filter(opt => opt.takesValue);
|
|
2331
|
+
const valueOptionsWithChoices = valueOptions.filter(opt => opt.choices && opt.choices.length > 0);
|
|
2332
|
+
const valueLongPatterns = valueOptions.map(opt => `--${camelToKebabCase(opt.long)}`);
|
|
2333
|
+
const valueShortPatterns = valueOptions
|
|
2334
|
+
.map(opt => opt.short)
|
|
2335
|
+
.filter((short) => typeof short === 'string');
|
|
2336
|
+
lines.push(`${indent}prefer_value_choices=0`);
|
|
2337
|
+
if (valueOptionsWithChoices.length > 0) {
|
|
2338
|
+
lines.push(`${indent}if [[ "$cur" != -* ]]; then`);
|
|
2339
|
+
lines.push(`${indent} case "$prev" in`);
|
|
2340
|
+
for (const opt of valueOptionsWithChoices) {
|
|
2341
|
+
const patterns = [`--${camelToKebabCase(opt.long)}`];
|
|
2342
|
+
if (opt.short) {
|
|
2343
|
+
patterns.push(`-${opt.short}`);
|
|
2344
|
+
}
|
|
2345
|
+
lines.push(`${indent} ${patterns.join('|')})`);
|
|
2346
|
+
lines.push(`${indent} opts="${this.#serializeWordList(opt.choices ?? [])}"`);
|
|
2347
|
+
lines.push(`${indent} prefer_value_choices=1`);
|
|
2348
|
+
lines.push(`${indent} ;;`);
|
|
2349
|
+
}
|
|
2350
|
+
lines.push(`${indent} esac`);
|
|
2351
|
+
lines.push(`${indent}fi`);
|
|
2352
|
+
}
|
|
2353
|
+
lines.push(`${indent}if [[ $prefer_value_choices -eq 0 ]]; then`);
|
|
2354
|
+
lines.push(`${indent} positional_count=0`);
|
|
2355
|
+
lines.push(`${indent} expect_value=0`);
|
|
2356
|
+
lines.push(`${indent} for ((idx=${depth}; idx<cword; idx++)); do`);
|
|
2357
|
+
lines.push(`${indent} token="\${words[idx]}"`);
|
|
2358
|
+
lines.push(`${indent} if [[ $expect_value -eq 1 ]]; then`);
|
|
2359
|
+
lines.push(`${indent} expect_value=0`);
|
|
2360
|
+
lines.push(`${indent} continue`);
|
|
2361
|
+
lines.push(`${indent} fi`);
|
|
2362
|
+
lines.push(`${indent} if [[ "$token" == --* ]]; then`);
|
|
2363
|
+
lines.push(`${indent} if [[ "$token" == *=* ]]; then`);
|
|
2364
|
+
lines.push(`${indent} continue`);
|
|
2365
|
+
lines.push(`${indent} fi`);
|
|
2366
|
+
if (valueLongPatterns.length > 0) {
|
|
2367
|
+
lines.push(`${indent} case "$token" in`);
|
|
2368
|
+
lines.push(`${indent} ${valueLongPatterns.join('|')}) expect_value=1 ;;`);
|
|
2369
|
+
lines.push(`${indent} esac`);
|
|
2370
|
+
}
|
|
2371
|
+
lines.push(`${indent} continue`);
|
|
2372
|
+
lines.push(`${indent} fi`);
|
|
2373
|
+
lines.push(`${indent} if [[ "$token" == -* && "$token" != "-" ]]; then`);
|
|
2374
|
+
lines.push(`${indent} if [[ \${#token} -eq 2 ]]; then`);
|
|
2375
|
+
if (valueShortPatterns.length > 0) {
|
|
2376
|
+
lines.push(`${indent} case "\${token:1:1}" in`);
|
|
2377
|
+
lines.push(`${indent} ${valueShortPatterns.join('|')}) expect_value=1 ;;`);
|
|
2378
|
+
lines.push(`${indent} esac`);
|
|
2379
|
+
}
|
|
2380
|
+
lines.push(`${indent} fi`);
|
|
2381
|
+
lines.push(`${indent} continue`);
|
|
2382
|
+
lines.push(`${indent} fi`);
|
|
2383
|
+
lines.push(`${indent} positional_count=$((positional_count + 1))`);
|
|
2384
|
+
lines.push(`${indent} done`);
|
|
2385
|
+
lines.push(`${indent} if [[ $expect_value -eq 1 ]]; then`);
|
|
2386
|
+
lines.push(`${indent} opts=""`);
|
|
2387
|
+
lines.push(`${indent} prefer_value_choices=1`);
|
|
2388
|
+
lines.push(`${indent} elif [[ "$cur" != -* ]]; then`);
|
|
2389
|
+
lines.push(`${indent} arg_slot=-1`);
|
|
2390
|
+
lines.push(`${indent} arg_count=${cmd.arguments.length}`);
|
|
2391
|
+
const hasRestArgument = cmd.arguments.length > 0 &&
|
|
2392
|
+
(cmd.arguments[cmd.arguments.length - 1].kind === 'variadic' ||
|
|
2393
|
+
cmd.arguments[cmd.arguments.length - 1].kind === 'some');
|
|
2394
|
+
lines.push(`${indent} has_rest=${hasRestArgument ? 1 : 0}`);
|
|
2395
|
+
lines.push(`${indent} if [[ $has_rest -eq 1 && $positional_count -ge $((arg_count - 1)) ]]; then`);
|
|
2396
|
+
lines.push(`${indent} arg_slot=$((arg_count - 1))`);
|
|
2397
|
+
lines.push(`${indent} elif [[ $positional_count -lt $arg_count ]]; then`);
|
|
2398
|
+
lines.push(`${indent} arg_slot=$positional_count`);
|
|
2399
|
+
lines.push(`${indent} fi`);
|
|
2400
|
+
lines.push(`${indent} case "$arg_slot" in`);
|
|
2401
|
+
for (let index = 0; index < cmd.arguments.length; index += 1) {
|
|
2402
|
+
const arg = cmd.arguments[index];
|
|
2403
|
+
if (arg.type !== 'choice' || !arg.choices || arg.choices.length === 0) {
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
lines.push(`${indent} ${index}) opts="${this.#serializeWordList(arg.choices)}" ;;`);
|
|
2407
|
+
}
|
|
2408
|
+
lines.push(`${indent} esac`);
|
|
2409
|
+
lines.push(`${indent} fi`);
|
|
2410
|
+
lines.push(`${indent}fi`);
|
|
2411
|
+
}
|
|
2412
|
+
#escapeWord(word) {
|
|
2413
|
+
return word.replace(/([\\\s'"`$!])/g, '\\$1');
|
|
2414
|
+
}
|
|
1935
2415
|
#sanitizeName(name) {
|
|
1936
2416
|
return name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
1937
2417
|
}
|
|
@@ -1939,15 +2419,19 @@ class BashCompletion {
|
|
|
1939
2419
|
class FishCompletion {
|
|
1940
2420
|
#meta;
|
|
1941
2421
|
#programName;
|
|
2422
|
+
#slotMatcherName;
|
|
1942
2423
|
constructor(meta, programName) {
|
|
1943
2424
|
this.#meta = meta;
|
|
1944
2425
|
this.#programName = programName;
|
|
2426
|
+
this.#slotMatcherName = `__${this.#sanitizeName(programName)}_match_arg_slot`;
|
|
1945
2427
|
}
|
|
1946
2428
|
generate() {
|
|
1947
2429
|
const lines = [
|
|
1948
2430
|
`# Fish completion for ${this.#programName}`,
|
|
1949
2431
|
'# Generated by @guanghechen/commander',
|
|
1950
2432
|
'',
|
|
2433
|
+
...this.#generateSlotMatcherFunction(),
|
|
2434
|
+
'',
|
|
1951
2435
|
...this.#generateCommandCompletions(this.#meta, []),
|
|
1952
2436
|
'',
|
|
1953
2437
|
];
|
|
@@ -1967,7 +2451,7 @@ class FishCompletion {
|
|
|
1967
2451
|
line += ` -l ${kebabLong}`;
|
|
1968
2452
|
line += ` -d '${this.#escape(opt.desc)}'`;
|
|
1969
2453
|
if (opt.choices && opt.choices.length > 0) {
|
|
1970
|
-
line += ` -xa '${opt.choices.join(' ')}'`;
|
|
2454
|
+
line += ` -xa '${opt.choices.map(choice => this.#escapeChoice(choice)).join(' ')}'`;
|
|
1971
2455
|
}
|
|
1972
2456
|
lines.push(line);
|
|
1973
2457
|
if (!opt.takesValue) {
|
|
@@ -1979,6 +2463,36 @@ class FishCompletion {
|
|
|
1979
2463
|
lines.push(noLine);
|
|
1980
2464
|
}
|
|
1981
2465
|
}
|
|
2466
|
+
const valueOptionLongs = cmd.options
|
|
2467
|
+
.filter(opt => opt.takesValue)
|
|
2468
|
+
.map(opt => camelToKebabCase(opt.long))
|
|
2469
|
+
.join(',');
|
|
2470
|
+
const valueOptionShorts = cmd.options
|
|
2471
|
+
.filter(opt => opt.takesValue && opt.short)
|
|
2472
|
+
.map(opt => opt.short)
|
|
2473
|
+
.join(',');
|
|
2474
|
+
const argCount = cmd.arguments.length;
|
|
2475
|
+
const hasRestArgument = argCount > 0 &&
|
|
2476
|
+
(cmd.arguments[argCount - 1].kind === 'variadic' ||
|
|
2477
|
+
cmd.arguments[argCount - 1].kind === 'some');
|
|
2478
|
+
for (let index = 0; index < cmd.arguments.length; index += 1) {
|
|
2479
|
+
const arg = cmd.arguments[index];
|
|
2480
|
+
if (arg.type !== 'choice' || !arg.choices || arg.choices.length === 0) {
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
let line = `complete -c ${this.#programName}`;
|
|
2484
|
+
const slotCondition = `${this.#slotMatcherName} ${parentPath.length} ${argCount} ${hasRestArgument ? 1 : 0} ${index} '${valueOptionLongs}' '${valueOptionShorts}'`;
|
|
2485
|
+
if (condition) {
|
|
2486
|
+
line += ` -n '${condition}; and ${slotCondition}'`;
|
|
2487
|
+
}
|
|
2488
|
+
else {
|
|
2489
|
+
line += ` -n '${slotCondition}'`;
|
|
2490
|
+
}
|
|
2491
|
+
line += ` -f`;
|
|
2492
|
+
line += ` -a '${arg.choices.map(choice => this.#escapeChoice(choice)).join(' ')}'`;
|
|
2493
|
+
line += ` -d '${this.#escape(`Argument: ${arg.name}`)}'`;
|
|
2494
|
+
lines.push(line);
|
|
2495
|
+
}
|
|
1982
2496
|
for (const sub of cmd.subcommands) {
|
|
1983
2497
|
let line = `complete -c ${this.#programName}`;
|
|
1984
2498
|
if (isRoot) {
|
|
@@ -2002,7 +2516,7 @@ class FishCompletion {
|
|
|
2002
2516
|
aliasLine += ` -d 'Alias for ${sub.name}'`;
|
|
2003
2517
|
lines.push(aliasLine);
|
|
2004
2518
|
}
|
|
2005
|
-
const newPath = [...parentPath, sub.name];
|
|
2519
|
+
const newPath = [...parentPath, [sub.name, ...sub.aliases]];
|
|
2006
2520
|
lines.push(...this.#generateCommandCompletions(sub, newPath));
|
|
2007
2521
|
}
|
|
2008
2522
|
return lines;
|
|
@@ -2010,7 +2524,7 @@ class FishCompletion {
|
|
|
2010
2524
|
#buildCondition(path) {
|
|
2011
2525
|
if (path.length === 0)
|
|
2012
2526
|
return '';
|
|
2013
|
-
return `__fish_seen_subcommand_from ${
|
|
2527
|
+
return path.map(level => `__fish_seen_subcommand_from ${level.join(' ')}`).join('; and ');
|
|
2014
2528
|
}
|
|
2015
2529
|
#getSubcommandNames(cmd) {
|
|
2016
2530
|
return cmd.subcommands.flatMap(sub => [sub.name, ...sub.aliases]);
|
|
@@ -2018,6 +2532,68 @@ class FishCompletion {
|
|
|
2018
2532
|
#escape(s) {
|
|
2019
2533
|
return s.replace(/'/g, "\\'");
|
|
2020
2534
|
}
|
|
2535
|
+
#escapeChoice(s) {
|
|
2536
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\s/g, '\\ ');
|
|
2537
|
+
}
|
|
2538
|
+
#sanitizeName(name) {
|
|
2539
|
+
return name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
2540
|
+
}
|
|
2541
|
+
#generateSlotMatcherFunction() {
|
|
2542
|
+
return [
|
|
2543
|
+
`function ${this.#slotMatcherName} --argument-names depth arg_count has_rest target_index long_opts short_opts`,
|
|
2544
|
+
' set -l tokens (commandline -opc)',
|
|
2545
|
+
' set -l start (math $depth + 2)',
|
|
2546
|
+
' set -l positional 0',
|
|
2547
|
+
' set -l expect_value 0',
|
|
2548
|
+
' set -l i $start',
|
|
2549
|
+
' set -l token_count (count $tokens)',
|
|
2550
|
+
' set -l long_list (string split "," -- $long_opts)',
|
|
2551
|
+
' set -l short_list (string split "," -- $short_opts)',
|
|
2552
|
+
' while test $i -le $token_count',
|
|
2553
|
+
' set -l token $tokens[$i]',
|
|
2554
|
+
' if test $expect_value -eq 1',
|
|
2555
|
+
' set expect_value 0',
|
|
2556
|
+
' set i (math $i + 1)',
|
|
2557
|
+
' continue',
|
|
2558
|
+
' end',
|
|
2559
|
+
' if string match -q -- "--*" $token',
|
|
2560
|
+
' if string match -q -- "*=*" $token',
|
|
2561
|
+
' set i (math $i + 1)',
|
|
2562
|
+
' continue',
|
|
2563
|
+
' end',
|
|
2564
|
+
' set -l opt_name (string replace -r "^--" "" -- $token)',
|
|
2565
|
+
' if contains -- $opt_name $long_list',
|
|
2566
|
+
' set expect_value 1',
|
|
2567
|
+
' end',
|
|
2568
|
+
' set i (math $i + 1)',
|
|
2569
|
+
' continue',
|
|
2570
|
+
' end',
|
|
2571
|
+
' if test "$token" != "-"; and string match -q -- "-*" $token',
|
|
2572
|
+
' set -l raw_short (string replace -r "^-" "" -- $token)',
|
|
2573
|
+
' if test (string length -- $raw_short) -eq 1',
|
|
2574
|
+
' if contains -- $raw_short $short_list',
|
|
2575
|
+
' set expect_value 1',
|
|
2576
|
+
' end',
|
|
2577
|
+
' end',
|
|
2578
|
+
' set i (math $i + 1)',
|
|
2579
|
+
' continue',
|
|
2580
|
+
' end',
|
|
2581
|
+
' set positional (math $positional + 1)',
|
|
2582
|
+
' set i (math $i + 1)',
|
|
2583
|
+
' end',
|
|
2584
|
+
' if test $expect_value -eq 1',
|
|
2585
|
+
' return 1',
|
|
2586
|
+
' end',
|
|
2587
|
+
' set -l slot -1',
|
|
2588
|
+
' if test $has_rest -eq 1; and test $positional -ge (math $arg_count - 1)',
|
|
2589
|
+
' set slot (math $arg_count - 1)',
|
|
2590
|
+
' else if test $positional -lt $arg_count',
|
|
2591
|
+
' set slot $positional',
|
|
2592
|
+
' end',
|
|
2593
|
+
' test $slot -eq $target_index',
|
|
2594
|
+
'end',
|
|
2595
|
+
];
|
|
2596
|
+
}
|
|
2021
2597
|
}
|
|
2022
2598
|
class PwshCompletion {
|
|
2023
2599
|
#meta;
|
|
@@ -2043,16 +2619,105 @@ class PwshCompletion {
|
|
|
2043
2619
|
'',
|
|
2044
2620
|
' # Find current command context',
|
|
2045
2621
|
' $cmd = $commands',
|
|
2622
|
+
' $commandDepth = 1',
|
|
2046
2623
|
' foreach ($word in $words[1..($words.Count - 1)]) {',
|
|
2047
2624
|
' if ($word.StartsWith("-")) { continue }',
|
|
2048
2625
|
' if ($cmd.subcommands -and $cmd.subcommands.ContainsKey($word)) {',
|
|
2049
2626
|
' $cmd = $cmd.subcommands[$word]',
|
|
2627
|
+
' $commandDepth += 1',
|
|
2050
2628
|
' }',
|
|
2051
2629
|
' }',
|
|
2052
2630
|
'',
|
|
2053
2631
|
' # Generate completions',
|
|
2054
2632
|
' $completions = @()',
|
|
2055
2633
|
'',
|
|
2634
|
+
' # Option value slot (always higher priority than arguments)',
|
|
2635
|
+
' $previous = if ($words.Count -ge 2) { $words[$words.Count - 2] } else { $null }',
|
|
2636
|
+
' if ($previous) {',
|
|
2637
|
+
' foreach ($opt in $cmd.options) {',
|
|
2638
|
+
' $isLong = $previous -eq "--$($opt.long)"',
|
|
2639
|
+
' $isShort = $opt.short -and $previous -eq "-$($opt.short)"',
|
|
2640
|
+
' if ($isLong -or $isShort) {',
|
|
2641
|
+
' if ($opt.choices) {',
|
|
2642
|
+
' foreach ($choice in $opt.choices) {',
|
|
2643
|
+
' if ($choice -like "$current*") {',
|
|
2644
|
+
' $completions += [System.Management.Automation.CompletionResult]::new(',
|
|
2645
|
+
' $choice,',
|
|
2646
|
+
' $choice,',
|
|
2647
|
+
' "ParameterValue",',
|
|
2648
|
+
' $choice',
|
|
2649
|
+
' )',
|
|
2650
|
+
' }',
|
|
2651
|
+
' }',
|
|
2652
|
+
' }',
|
|
2653
|
+
' return $completions',
|
|
2654
|
+
' }',
|
|
2655
|
+
' }',
|
|
2656
|
+
' }',
|
|
2657
|
+
'',
|
|
2658
|
+
' # Determine argument slot',
|
|
2659
|
+
' $positionalCount = 0',
|
|
2660
|
+
' $expectValue = $false',
|
|
2661
|
+
' for ($i = $commandDepth; $i -lt ($words.Count - 1); $i += 1) {',
|
|
2662
|
+
' $token = $words[$i]',
|
|
2663
|
+
' if ($expectValue) {',
|
|
2664
|
+
' $expectValue = $false',
|
|
2665
|
+
' continue',
|
|
2666
|
+
' }',
|
|
2667
|
+
' if ($token.StartsWith("--")) {',
|
|
2668
|
+
' if ($token.Contains("=")) { continue }',
|
|
2669
|
+
' foreach ($opt in $cmd.options) {',
|
|
2670
|
+
' if ($token -eq "--$($opt.long)" -and $opt.takesValue) {',
|
|
2671
|
+
' $expectValue = $true',
|
|
2672
|
+
' break',
|
|
2673
|
+
' }',
|
|
2674
|
+
' }',
|
|
2675
|
+
' continue',
|
|
2676
|
+
' }',
|
|
2677
|
+
' if ($token.StartsWith("-") -and $token -ne "-") {',
|
|
2678
|
+
' if ($token.Length -eq 2) {',
|
|
2679
|
+
' foreach ($opt in $cmd.options) {',
|
|
2680
|
+
' if ($opt.short -and $token -eq "-$($opt.short)" -and $opt.takesValue) {',
|
|
2681
|
+
' $expectValue = $true',
|
|
2682
|
+
' break',
|
|
2683
|
+
' }',
|
|
2684
|
+
' }',
|
|
2685
|
+
' }',
|
|
2686
|
+
' continue',
|
|
2687
|
+
' }',
|
|
2688
|
+
' $positionalCount += 1',
|
|
2689
|
+
' }',
|
|
2690
|
+
' if ($expectValue) {',
|
|
2691
|
+
' return $completions',
|
|
2692
|
+
' }',
|
|
2693
|
+
' if (-not $current.StartsWith("-") -and $cmd.arguments -and $cmd.arguments.Count -gt 0) {',
|
|
2694
|
+
' $argSlot = -1',
|
|
2695
|
+
' $argCount = $cmd.arguments.Count',
|
|
2696
|
+
' $lastArg = $cmd.arguments[$argCount - 1]',
|
|
2697
|
+
' $hasRest = $lastArg.kind -eq "variadic" -or $lastArg.kind -eq "some"',
|
|
2698
|
+
' if ($hasRest -and $positionalCount -ge ($argCount - 1)) {',
|
|
2699
|
+
' $argSlot = $argCount - 1',
|
|
2700
|
+
' } elseif ($positionalCount -lt $argCount) {',
|
|
2701
|
+
' $argSlot = $positionalCount',
|
|
2702
|
+
' }',
|
|
2703
|
+
' if ($argSlot -ge 0) {',
|
|
2704
|
+
' $argMeta = $cmd.arguments[$argSlot]',
|
|
2705
|
+
' if ($argMeta.choices) {',
|
|
2706
|
+
' foreach ($choice in $argMeta.choices) {',
|
|
2707
|
+
' if ($choice -like "$current*") {',
|
|
2708
|
+
' $completions += [System.Management.Automation.CompletionResult]::new(',
|
|
2709
|
+
' $choice,',
|
|
2710
|
+
' $choice,',
|
|
2711
|
+
' "ParameterValue",',
|
|
2712
|
+
' $choice',
|
|
2713
|
+
' )',
|
|
2714
|
+
' }',
|
|
2715
|
+
' }',
|
|
2716
|
+
' return $completions',
|
|
2717
|
+
' }',
|
|
2718
|
+
' }',
|
|
2719
|
+
' }',
|
|
2720
|
+
'',
|
|
2056
2721
|
' # Options',
|
|
2057
2722
|
' if ($current.StartsWith("-")) {',
|
|
2058
2723
|
' foreach ($opt in $cmd.options) {',
|
|
@@ -2061,7 +2726,7 @@ class PwshCompletion {
|
|
|
2061
2726
|
' "--$($opt.long)",',
|
|
2062
2727
|
' $opt.long,',
|
|
2063
2728
|
' "ParameterName",',
|
|
2064
|
-
' $opt.
|
|
2729
|
+
' $opt.description',
|
|
2065
2730
|
' )',
|
|
2066
2731
|
' }',
|
|
2067
2732
|
' if ($opt.isBoolean -and "--no-$($opt.long)" -like "$current*") {',
|
|
@@ -2069,7 +2734,7 @@ class PwshCompletion {
|
|
|
2069
2734
|
' "--no-$($opt.long)",',
|
|
2070
2735
|
' "no-$($opt.long)",',
|
|
2071
2736
|
' "ParameterName",',
|
|
2072
|
-
' $opt.
|
|
2737
|
+
' $opt.description',
|
|
2073
2738
|
' )',
|
|
2074
2739
|
' }',
|
|
2075
2740
|
' if ($opt.short -and "-$($opt.short)" -like "$current*") {',
|
|
@@ -2077,7 +2742,7 @@ class PwshCompletion {
|
|
|
2077
2742
|
' "-$($opt.short)",',
|
|
2078
2743
|
' $opt.short,',
|
|
2079
2744
|
' "ParameterName",',
|
|
2080
|
-
' $opt.
|
|
2745
|
+
' $opt.description',
|
|
2081
2746
|
' )',
|
|
2082
2747
|
' }',
|
|
2083
2748
|
' }',
|
|
@@ -2091,7 +2756,7 @@ class PwshCompletion {
|
|
|
2091
2756
|
' $sub,',
|
|
2092
2757
|
' $sub,',
|
|
2093
2758
|
' "Command",',
|
|
2094
|
-
' $cmd.subcommands[$sub].
|
|
2759
|
+
' $cmd.subcommands[$sub].description',
|
|
2095
2760
|
' )',
|
|
2096
2761
|
' }',
|
|
2097
2762
|
' }',
|
|
@@ -2115,8 +2780,25 @@ class PwshCompletion {
|
|
|
2115
2780
|
lines.push(`${indent} long = '${kebabLong}'`);
|
|
2116
2781
|
lines.push(`${indent} description = '${this.#escape(opt.desc)}'`);
|
|
2117
2782
|
lines.push(`${indent} isBoolean = $${!opt.takesValue}`);
|
|
2783
|
+
lines.push(`${indent} takesValue = $${opt.takesValue}`);
|
|
2118
2784
|
if (opt.choices) {
|
|
2119
|
-
lines.push(`${indent} choices = @('${opt.choices
|
|
2785
|
+
lines.push(`${indent} choices = @('${opt.choices
|
|
2786
|
+
.map(choice => this.#escape(choice))
|
|
2787
|
+
.join("', '")}')`);
|
|
2788
|
+
}
|
|
2789
|
+
lines.push(`${indent} }`);
|
|
2790
|
+
}
|
|
2791
|
+
lines.push(`${indent})`);
|
|
2792
|
+
lines.push(`${indent}arguments = @(`);
|
|
2793
|
+
for (const arg of cmd.arguments) {
|
|
2794
|
+
lines.push(`${indent} @{`);
|
|
2795
|
+
lines.push(`${indent} name = '${this.#escape(arg.name)}'`);
|
|
2796
|
+
lines.push(`${indent} kind = '${arg.kind}'`);
|
|
2797
|
+
lines.push(`${indent} type = '${arg.type}'`);
|
|
2798
|
+
if (arg.choices && arg.choices.length > 0) {
|
|
2799
|
+
lines.push(`${indent} choices = @('${arg.choices
|
|
2800
|
+
.map(choice => this.#escape(choice))
|
|
2801
|
+
.join("', '")}')`);
|
|
2120
2802
|
}
|
|
2121
2803
|
lines.push(`${indent} }`);
|
|
2122
2804
|
}
|