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