@guanghechen/commander 4.7.2 → 4.7.4
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 +16 -0
- package/lib/cjs/browser.cjs +136 -19
- package/lib/cjs/index.cjs +176 -1028
- package/lib/cjs/node.cjs +205 -51
- package/lib/esm/browser.mjs +136 -19
- package/lib/esm/index.mjs +175 -1021
- package/lib/esm/node.mjs +205 -51
- package/lib/types/browser.d.ts +6 -3
- package/lib/types/index.d.ts +15 -191
- package/lib/types/node.d.ts +6 -3
- package/package.json +3 -3
package/lib/esm/node.mjs
CHANGED
|
@@ -305,6 +305,30 @@ function parsePrimitiveNumber(rawValue) {
|
|
|
305
305
|
}
|
|
306
306
|
return value;
|
|
307
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
|
+
}
|
|
308
332
|
function tokenizeLongOption(arg, commandPath) {
|
|
309
333
|
const eqIdx = arg.indexOf('=');
|
|
310
334
|
const namePart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
|
|
@@ -707,7 +731,10 @@ class Command {
|
|
|
707
731
|
const kebabLong = camelToKebabCase$1(opt.long);
|
|
708
732
|
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
709
733
|
sig += `--${kebabLong}`;
|
|
710
|
-
if (opt.args
|
|
734
|
+
if (opt.args === 'optional') {
|
|
735
|
+
sig += ' [value]';
|
|
736
|
+
}
|
|
737
|
+
else if (opt.args !== 'none') {
|
|
711
738
|
sig += ' <value>';
|
|
712
739
|
}
|
|
713
740
|
let desc = opt.desc;
|
|
@@ -848,7 +875,15 @@ class Command {
|
|
|
848
875
|
return ` ${outputLabel} ${desc}`;
|
|
849
876
|
}
|
|
850
877
|
getCompletionMeta() {
|
|
851
|
-
const
|
|
878
|
+
const optionMap = new Map();
|
|
879
|
+
for (const option of this.#resolveOptionPolicy().mergedOptions) {
|
|
880
|
+
optionMap.set(option.long, option);
|
|
881
|
+
}
|
|
882
|
+
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
883
|
+
if (this.#supportsBuiltinVersion()) {
|
|
884
|
+
optionMap.set('version', BUILTIN_VERSION_OPTION);
|
|
885
|
+
}
|
|
886
|
+
const allOptions = Array.from(optionMap.values());
|
|
852
887
|
const options = [];
|
|
853
888
|
const argumentsMeta = [];
|
|
854
889
|
for (const opt of allOptions) {
|
|
@@ -856,7 +891,8 @@ class Command {
|
|
|
856
891
|
long: opt.long,
|
|
857
892
|
short: opt.short,
|
|
858
893
|
desc: opt.desc,
|
|
859
|
-
|
|
894
|
+
type: opt.type,
|
|
895
|
+
args: opt.args,
|
|
860
896
|
choices: opt.choices?.map(choice => String(choice)),
|
|
861
897
|
});
|
|
862
898
|
}
|
|
@@ -1326,6 +1362,14 @@ class Command {
|
|
|
1326
1362
|
consumed.push(tokens[i]);
|
|
1327
1363
|
}
|
|
1328
1364
|
}
|
|
1365
|
+
else if (opt.args === 'optional') {
|
|
1366
|
+
if (!token.resolved.includes('=') &&
|
|
1367
|
+
i + 1 < tokens.length &&
|
|
1368
|
+
tokens[i + 1].type === 'none') {
|
|
1369
|
+
i += 1;
|
|
1370
|
+
consumed.push(tokens[i]);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1329
1373
|
else if (opt.args === 'variadic') {
|
|
1330
1374
|
if (!token.resolved.includes('=')) {
|
|
1331
1375
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
@@ -1351,6 +1395,12 @@ class Command {
|
|
|
1351
1395
|
consumed.push(tokens[i]);
|
|
1352
1396
|
}
|
|
1353
1397
|
}
|
|
1398
|
+
else if (opt.args === 'optional') {
|
|
1399
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1400
|
+
i += 1;
|
|
1401
|
+
consumed.push(tokens[i]);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1354
1404
|
else if (opt.args === 'variadic') {
|
|
1355
1405
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1356
1406
|
i += 1;
|
|
@@ -1392,6 +1442,7 @@ class Command {
|
|
|
1392
1442
|
leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
|
|
1393
1443
|
}
|
|
1394
1444
|
}
|
|
1445
|
+
leafCommand.#assertUnknownSubcommand(ctx.sources.user.argv);
|
|
1395
1446
|
const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
|
|
1396
1447
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
1397
1448
|
const parseCtx = {
|
|
@@ -1474,6 +1525,23 @@ class Command {
|
|
|
1474
1525
|
i += 1;
|
|
1475
1526
|
continue;
|
|
1476
1527
|
}
|
|
1528
|
+
if (opt.args === 'optional') {
|
|
1529
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1530
|
+
if (eqIdx !== -1) {
|
|
1531
|
+
opts[opt.long] = this.#convertValue(opt, token.resolved.slice(eqIdx + 1));
|
|
1532
|
+
i += 1;
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1536
|
+
opts[opt.long] = this.#convertValue(opt, tokens[i + 1].original);
|
|
1537
|
+
i += 1;
|
|
1538
|
+
}
|
|
1539
|
+
else {
|
|
1540
|
+
opts[opt.long] = undefined;
|
|
1541
|
+
}
|
|
1542
|
+
i += 1;
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1477
1545
|
if (opt.args === 'variadic') {
|
|
1478
1546
|
const values = Array.isArray(opts[opt.long]) ? opts[opt.long] : [];
|
|
1479
1547
|
const eqIdx = token.resolved.indexOf('=');
|
|
@@ -1493,7 +1561,7 @@ class Command {
|
|
|
1493
1561
|
i += 1;
|
|
1494
1562
|
}
|
|
1495
1563
|
for (const opt of allOptions) {
|
|
1496
|
-
if (opt.required && opts
|
|
1564
|
+
if (opt.required && !Object.prototype.hasOwnProperty.call(opts, opt.long)) {
|
|
1497
1565
|
throw new CommanderError('MissingRequired', `missing required option "--${camelToKebabCase$1(opt.long)}"`, this.#getCommandPath());
|
|
1498
1566
|
}
|
|
1499
1567
|
}
|
|
@@ -1530,6 +1598,9 @@ class Command {
|
|
|
1530
1598
|
#parseArguments(rawArgs) {
|
|
1531
1599
|
const argumentDefs = this.#arguments;
|
|
1532
1600
|
const args = {};
|
|
1601
|
+
if (argumentDefs.length === 0 && rawArgs.length > 0) {
|
|
1602
|
+
throw new CommanderError('UnexpectedArgument', `unexpected argument "${rawArgs[0]}"`, this.#getCommandPath());
|
|
1603
|
+
}
|
|
1533
1604
|
const missing = [];
|
|
1534
1605
|
let remaining = rawArgs.length;
|
|
1535
1606
|
for (const def of argumentDefs) {
|
|
@@ -1570,25 +1641,23 @@ class Command {
|
|
|
1570
1641
|
}
|
|
1571
1642
|
if (def.kind === 'some') {
|
|
1572
1643
|
const rest = rawArgs.slice(index);
|
|
1573
|
-
if (rest.length === 0) {
|
|
1574
|
-
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${def.name}`, this.#getCommandPath());
|
|
1575
|
-
}
|
|
1576
1644
|
args[def.name] = rest.map(raw => this.#convertArgument(def, raw));
|
|
1577
1645
|
index = rawArgs.length;
|
|
1578
1646
|
break;
|
|
1579
1647
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
if (
|
|
1648
|
+
if (def.kind === 'optional') {
|
|
1649
|
+
const raw = rawArgs[index];
|
|
1650
|
+
if (raw === undefined) {
|
|
1583
1651
|
args[def.name] = def.default ?? undefined;
|
|
1584
1652
|
continue;
|
|
1585
1653
|
}
|
|
1586
|
-
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${def.name}`, this.#getCommandPath());
|
|
1587
|
-
}
|
|
1588
|
-
else {
|
|
1589
1654
|
args[def.name] = this.#convertArgument(def, raw);
|
|
1590
1655
|
index += 1;
|
|
1656
|
+
continue;
|
|
1591
1657
|
}
|
|
1658
|
+
const raw = rawArgs[index];
|
|
1659
|
+
args[def.name] = this.#convertArgument(def, raw);
|
|
1660
|
+
index += 1;
|
|
1592
1661
|
}
|
|
1593
1662
|
const hasRestArgument = argumentDefs.some(a => a.kind === 'variadic' || a.kind === 'some');
|
|
1594
1663
|
if (!hasRestArgument && index < rawArgs.length) {
|
|
@@ -1622,6 +1691,50 @@ class Command {
|
|
|
1622
1691
|
}
|
|
1623
1692
|
return value;
|
|
1624
1693
|
}
|
|
1694
|
+
#assertUnknownSubcommand(userTailArgv) {
|
|
1695
|
+
if (this.#subcommandsList.length === 0) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const token = userTailArgv[0];
|
|
1699
|
+
if (token === undefined || token.startsWith('-') || token === 'help') {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (this.#findSubcommandEntry(token) !== undefined) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
const hints = [];
|
|
1706
|
+
if (this.#arguments.length === 0) {
|
|
1707
|
+
hints.push(`Hint: command "${this.#getCommandPath()}" does not accept positional arguments.`);
|
|
1708
|
+
}
|
|
1709
|
+
const candidate = this.#resolveDidYouMeanSubcommandName(token);
|
|
1710
|
+
if (candidate !== undefined) {
|
|
1711
|
+
hints.push(`Hint: did you mean "${candidate}"?`);
|
|
1712
|
+
}
|
|
1713
|
+
const details = hints.length > 0 ? `\n${hints.join('\n')}` : '';
|
|
1714
|
+
throw new CommanderError('UnknownSubcommand', `unknown subcommand "${token}" for command "${this.#getCommandPath()}"${details}`, this.#getCommandPath());
|
|
1715
|
+
}
|
|
1716
|
+
#resolveDidYouMeanSubcommandName(token) {
|
|
1717
|
+
const source = normalizeSubcommandNameForDistance(token);
|
|
1718
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
1719
|
+
let bestName;
|
|
1720
|
+
let isUniqueBest = false;
|
|
1721
|
+
for (const entry of this.#subcommandsList) {
|
|
1722
|
+
const target = normalizeSubcommandNameForDistance(entry.name);
|
|
1723
|
+
const distance = levenshteinDistance(source, target);
|
|
1724
|
+
if (distance < minDistance) {
|
|
1725
|
+
minDistance = distance;
|
|
1726
|
+
bestName = entry.name;
|
|
1727
|
+
isUniqueBest = true;
|
|
1728
|
+
}
|
|
1729
|
+
else if (distance === minDistance) {
|
|
1730
|
+
isUniqueBest = false;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
if (minDistance <= 2 && isUniqueBest) {
|
|
1734
|
+
return bestName;
|
|
1735
|
+
}
|
|
1736
|
+
return undefined;
|
|
1737
|
+
}
|
|
1625
1738
|
#hasUserOption(long) {
|
|
1626
1739
|
return this.#options.some(option => option.long === long);
|
|
1627
1740
|
}
|
|
@@ -1665,11 +1778,9 @@ class Command {
|
|
|
1665
1778
|
return optionPolicyMap;
|
|
1666
1779
|
}
|
|
1667
1780
|
#mustGetOptionPolicy(optionPolicyMap, cmd) {
|
|
1668
|
-
const policy = optionPolicyMap.get(cmd);
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
}
|
|
1672
|
-
throw new CommanderError('ConfigurationError', `missing option policy for command "${cmd.#getCommandPath()}"`, this.#getCommandPath());
|
|
1781
|
+
const policy = optionPolicyMap.get(cmd) ?? cmd.#resolveOptionPolicy();
|
|
1782
|
+
optionPolicyMap.set(cmd, policy);
|
|
1783
|
+
return policy;
|
|
1673
1784
|
}
|
|
1674
1785
|
#validateMergedShortOptions(chain, optionPolicyMap) {
|
|
1675
1786
|
const mergedByLong = new Map();
|
|
@@ -1698,7 +1809,10 @@ class Command {
|
|
|
1698
1809
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
1699
1810
|
}
|
|
1700
1811
|
if ((opt.type === 'string' || opt.type === 'number') && opt.args === 'none') {
|
|
1701
|
-
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required' or 'variadic'`, this.#getCommandPath());
|
|
1812
|
+
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required', 'optional', or 'variadic'`, this.#getCommandPath());
|
|
1813
|
+
}
|
|
1814
|
+
if (opt.type === 'number' && opt.args === 'optional') {
|
|
1815
|
+
throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, this.#getCommandPath());
|
|
1702
1816
|
}
|
|
1703
1817
|
if (opt.long.startsWith('no')) {
|
|
1704
1818
|
throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, this.#getCommandPath());
|
|
@@ -1715,6 +1829,9 @@ class Command {
|
|
|
1715
1829
|
if (opt.type === 'boolean' && opt.required) {
|
|
1716
1830
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, this.#getCommandPath());
|
|
1717
1831
|
}
|
|
1832
|
+
if (opt.required && opt.args !== 'required') {
|
|
1833
|
+
throw new CommanderError('ConfigurationError', `required option "--${opt.long}" must use args: 'required'`, this.#getCommandPath());
|
|
1834
|
+
}
|
|
1718
1835
|
}
|
|
1719
1836
|
#checkOptionUniqueness(opt) {
|
|
1720
1837
|
if (this.#options.some(o => o.long === opt.long)) {
|
|
@@ -2023,6 +2140,41 @@ class Coerce {
|
|
|
2023
2140
|
function camelToKebabCase(str) {
|
|
2024
2141
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
2025
2142
|
}
|
|
2143
|
+
function canGenerateNegativeCompletion(opt) {
|
|
2144
|
+
return (opt.type === 'boolean' && opt.args === 'none' && opt.long !== 'help' && opt.long !== 'version');
|
|
2145
|
+
}
|
|
2146
|
+
function optionTakesValue(opt) {
|
|
2147
|
+
return opt.args !== 'none';
|
|
2148
|
+
}
|
|
2149
|
+
const COMPLETION_SHELL_STATE = Symbol('completion-shell-state');
|
|
2150
|
+
function getCommandPath(ctx) {
|
|
2151
|
+
const names = ctx.chain
|
|
2152
|
+
.map(command => command.name)
|
|
2153
|
+
.filter((name) => Boolean(name));
|
|
2154
|
+
if (names.length > 0) {
|
|
2155
|
+
return names.join(' ');
|
|
2156
|
+
}
|
|
2157
|
+
return ctx.cmd.name ?? 'command';
|
|
2158
|
+
}
|
|
2159
|
+
function getCompletionShellState(ctx) {
|
|
2160
|
+
const host = ctx;
|
|
2161
|
+
host[COMPLETION_SHELL_STATE] ??= {};
|
|
2162
|
+
return host[COMPLETION_SHELL_STATE];
|
|
2163
|
+
}
|
|
2164
|
+
function registerCompletionShell(ctx, shell) {
|
|
2165
|
+
const state = getCompletionShellState(ctx);
|
|
2166
|
+
if (state.shell !== undefined && state.shell !== shell) {
|
|
2167
|
+
throw new CommanderError('OptionConflict', 'options "--bash", "--fish", and "--pwsh" are mutually exclusive', getCommandPath(ctx));
|
|
2168
|
+
}
|
|
2169
|
+
state.shell = shell;
|
|
2170
|
+
}
|
|
2171
|
+
function mustGetCompletionShell(ctx) {
|
|
2172
|
+
const state = getCompletionShellState(ctx);
|
|
2173
|
+
if (state.shell === undefined) {
|
|
2174
|
+
throw new CommanderError('MissingRequired', 'missing required option: one of "--bash", "--fish", or "--pwsh"', getCommandPath(ctx));
|
|
2175
|
+
}
|
|
2176
|
+
return state.shell;
|
|
2177
|
+
}
|
|
2026
2178
|
class CompletionCommand extends Command {
|
|
2027
2179
|
constructor(root, config = {}) {
|
|
2028
2180
|
const programName = config.programName ?? root.name ?? 'program';
|
|
@@ -2036,45 +2188,45 @@ class CompletionCommand extends Command {
|
|
|
2036
2188
|
type: 'boolean',
|
|
2037
2189
|
args: 'none',
|
|
2038
2190
|
desc: 'Generate Bash completion script',
|
|
2191
|
+
apply: (value, ctx) => {
|
|
2192
|
+
if (value === true) {
|
|
2193
|
+
registerCompletionShell(ctx, 'bash');
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2039
2196
|
})
|
|
2040
2197
|
.option({
|
|
2041
2198
|
long: 'fish',
|
|
2042
2199
|
type: 'boolean',
|
|
2043
2200
|
args: 'none',
|
|
2044
2201
|
desc: 'Generate Fish completion script',
|
|
2202
|
+
apply: (value, ctx) => {
|
|
2203
|
+
if (value === true) {
|
|
2204
|
+
registerCompletionShell(ctx, 'fish');
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2045
2207
|
})
|
|
2046
2208
|
.option({
|
|
2047
2209
|
long: 'pwsh',
|
|
2048
2210
|
type: 'boolean',
|
|
2049
2211
|
args: 'none',
|
|
2050
2212
|
desc: 'Generate PowerShell completion script',
|
|
2213
|
+
apply: (value, ctx) => {
|
|
2214
|
+
if (value === true) {
|
|
2215
|
+
registerCompletionShell(ctx, 'pwsh');
|
|
2216
|
+
}
|
|
2217
|
+
mustGetCompletionShell(ctx);
|
|
2218
|
+
},
|
|
2051
2219
|
})
|
|
2052
2220
|
.option({
|
|
2053
2221
|
long: 'write',
|
|
2054
2222
|
short: 'w',
|
|
2055
2223
|
type: 'string',
|
|
2056
|
-
args: '
|
|
2057
|
-
desc: 'Write to file (use shell default path
|
|
2058
|
-
default: undefined,
|
|
2224
|
+
args: 'optional',
|
|
2225
|
+
desc: 'Write to file (use shell default path when value is omitted or empty)',
|
|
2059
2226
|
})
|
|
2060
|
-
.action(({ opts }) => {
|
|
2227
|
+
.action(({ opts, ctx }) => {
|
|
2061
2228
|
const meta = root.getCompletionMeta();
|
|
2062
|
-
const
|
|
2063
|
-
opts['bash'] && 'bash',
|
|
2064
|
-
opts['fish'] && 'fish',
|
|
2065
|
-
opts['pwsh'] && 'pwsh',
|
|
2066
|
-
].filter(Boolean);
|
|
2067
|
-
if (selectedShells.length === 0) {
|
|
2068
|
-
console.error('Please specify a shell: --bash, --fish, or --pwsh');
|
|
2069
|
-
process.exit(1);
|
|
2070
|
-
return;
|
|
2071
|
-
}
|
|
2072
|
-
if (selectedShells.length > 1) {
|
|
2073
|
-
console.error('Please specify only one shell option');
|
|
2074
|
-
process.exit(1);
|
|
2075
|
-
return;
|
|
2076
|
-
}
|
|
2077
|
-
const shell = selectedShells[0];
|
|
2229
|
+
const shell = mustGetCompletionShell(ctx);
|
|
2078
2230
|
let script;
|
|
2079
2231
|
switch (shell) {
|
|
2080
2232
|
case 'bash':
|
|
@@ -2087,8 +2239,9 @@ class CompletionCommand extends Command {
|
|
|
2087
2239
|
script = new PwshCompletion(meta, programName).generate();
|
|
2088
2240
|
break;
|
|
2089
2241
|
}
|
|
2090
|
-
const
|
|
2091
|
-
if (
|
|
2242
|
+
const hasWrite = Object.prototype.hasOwnProperty.call(opts, 'write');
|
|
2243
|
+
if (hasWrite) {
|
|
2244
|
+
const writeOpt = opts['write'];
|
|
2092
2245
|
const filePath = typeof writeOpt === 'string' && writeOpt !== '' ? writeOpt : paths[shell];
|
|
2093
2246
|
const expandedPath = expandHome(filePath);
|
|
2094
2247
|
const dir = path.dirname(expandedPath);
|
|
@@ -2155,7 +2308,7 @@ class BashCompletion {
|
|
|
2155
2308
|
if (opt.short)
|
|
2156
2309
|
optParts.push(this.#escapeWord(`-${opt.short}`));
|
|
2157
2310
|
optParts.push(this.#escapeWord(`--${kebabLong}`));
|
|
2158
|
-
if (
|
|
2311
|
+
if (canGenerateNegativeCompletion(opt)) {
|
|
2159
2312
|
optParts.push(this.#escapeWord(`--no-${kebabLong}`));
|
|
2160
2313
|
}
|
|
2161
2314
|
}
|
|
@@ -2187,7 +2340,7 @@ class BashCompletion {
|
|
|
2187
2340
|
return words.map(choice => this.#escapeWord(choice)).join(' ');
|
|
2188
2341
|
}
|
|
2189
2342
|
#appendChoiceLogicForCommand(lines, indent, cmd, depth) {
|
|
2190
|
-
const valueOptions = cmd.options.filter(
|
|
2343
|
+
const valueOptions = cmd.options.filter(optionTakesValue);
|
|
2191
2344
|
const valueOptionsWithChoices = valueOptions.filter(opt => opt.choices && opt.choices.length > 0);
|
|
2192
2345
|
const valueLongPatterns = valueOptions.map(opt => `--${camelToKebabCase(opt.long)}`);
|
|
2193
2346
|
const valueShortPatterns = valueOptions
|
|
@@ -2314,7 +2467,7 @@ class FishCompletion {
|
|
|
2314
2467
|
line += ` -xa '${opt.choices.map(choice => this.#escapeChoice(choice)).join(' ')}'`;
|
|
2315
2468
|
}
|
|
2316
2469
|
lines.push(line);
|
|
2317
|
-
if (
|
|
2470
|
+
if (canGenerateNegativeCompletion(opt)) {
|
|
2318
2471
|
let noLine = `complete -c ${this.#programName}`;
|
|
2319
2472
|
if (condition)
|
|
2320
2473
|
noLine += ` -n '${condition}'`;
|
|
@@ -2324,11 +2477,11 @@ class FishCompletion {
|
|
|
2324
2477
|
}
|
|
2325
2478
|
}
|
|
2326
2479
|
const valueOptionLongs = cmd.options
|
|
2327
|
-
.filter(
|
|
2480
|
+
.filter(optionTakesValue)
|
|
2328
2481
|
.map(opt => camelToKebabCase(opt.long))
|
|
2329
2482
|
.join(',');
|
|
2330
2483
|
const valueOptionShorts = cmd.options
|
|
2331
|
-
.filter(opt => opt
|
|
2484
|
+
.filter(opt => optionTakesValue(opt) && opt.short)
|
|
2332
2485
|
.map(opt => opt.short)
|
|
2333
2486
|
.join(',');
|
|
2334
2487
|
const argCount = cmd.arguments.length;
|
|
@@ -2527,7 +2680,7 @@ class PwshCompletion {
|
|
|
2527
2680
|
' if ($token.StartsWith("--")) {',
|
|
2528
2681
|
' if ($token.Contains("=")) { continue }',
|
|
2529
2682
|
' foreach ($opt in $cmd.options) {',
|
|
2530
|
-
' if ($token -eq "--$($opt.long)" -and $opt.
|
|
2683
|
+
' if ($token -eq "--$($opt.long)" -and $opt.args -ne "none") {',
|
|
2531
2684
|
' $expectValue = $true',
|
|
2532
2685
|
' break',
|
|
2533
2686
|
' }',
|
|
@@ -2537,7 +2690,7 @@ class PwshCompletion {
|
|
|
2537
2690
|
' if ($token.StartsWith("-") -and $token -ne "-") {',
|
|
2538
2691
|
' if ($token.Length -eq 2) {',
|
|
2539
2692
|
' foreach ($opt in $cmd.options) {',
|
|
2540
|
-
' if ($opt.short -and $token -eq "-$($opt.short)" -and $opt.
|
|
2693
|
+
' if ($opt.short -and $token -eq "-$($opt.short)" -and $opt.args -ne "none") {',
|
|
2541
2694
|
' $expectValue = $true',
|
|
2542
2695
|
' break',
|
|
2543
2696
|
' }',
|
|
@@ -2589,7 +2742,7 @@ class PwshCompletion {
|
|
|
2589
2742
|
' $opt.description',
|
|
2590
2743
|
' )',
|
|
2591
2744
|
' }',
|
|
2592
|
-
' if ($opt.
|
|
2745
|
+
' if ($opt.canNegate -and "--no-$($opt.long)" -like "$current*") {',
|
|
2593
2746
|
' $completions += [System.Management.Automation.CompletionResult]::new(',
|
|
2594
2747
|
' "--no-$($opt.long)",',
|
|
2595
2748
|
' "no-$($opt.long)",',
|
|
@@ -2639,8 +2792,9 @@ class PwshCompletion {
|
|
|
2639
2792
|
lines.push(`${indent} short = '${opt.short}'`);
|
|
2640
2793
|
lines.push(`${indent} long = '${kebabLong}'`);
|
|
2641
2794
|
lines.push(`${indent} description = '${this.#escape(opt.desc)}'`);
|
|
2642
|
-
lines.push(`${indent}
|
|
2643
|
-
lines.push(`${indent}
|
|
2795
|
+
lines.push(`${indent} type = '${opt.type}'`);
|
|
2796
|
+
lines.push(`${indent} args = '${opt.args}'`);
|
|
2797
|
+
lines.push(`${indent} canNegate = $${canGenerateNegativeCompletion(opt)}`);
|
|
2644
2798
|
if (opt.choices) {
|
|
2645
2799
|
lines.push(`${indent} choices = @('${opt.choices
|
|
2646
2800
|
.map(choice => this.#escape(choice))
|
package/lib/types/browser.d.ts
CHANGED
|
@@ -29,13 +29,14 @@ interface ICommandToken {
|
|
|
29
29
|
/** Option value type */
|
|
30
30
|
type ICommandOptionType = 'boolean' | 'number' | 'string';
|
|
31
31
|
/** Option argument mode */
|
|
32
|
-
type ICommandOptionArgs = 'none' | 'required' | 'variadic';
|
|
32
|
+
type ICommandOptionArgs = 'none' | 'required' | 'optional' | 'variadic';
|
|
33
33
|
/**
|
|
34
34
|
* Option configuration.
|
|
35
35
|
*
|
|
36
36
|
* `type` and `args` must be specified together. Valid combinations:
|
|
37
37
|
* - boolean + none → boolean
|
|
38
38
|
* - string + required → string
|
|
39
|
+
* - string + optional → string | undefined
|
|
39
40
|
* - number + required → number
|
|
40
41
|
* - string + variadic → string[]
|
|
41
42
|
* - number + variadic → number[]
|
|
@@ -364,8 +365,10 @@ interface ICompletionOptionMeta {
|
|
|
364
365
|
short?: string;
|
|
365
366
|
/** Description */
|
|
366
367
|
desc: string;
|
|
367
|
-
/**
|
|
368
|
-
|
|
368
|
+
/** Option type */
|
|
369
|
+
type: ICommandOptionType;
|
|
370
|
+
/** Option args mode */
|
|
371
|
+
args: ICommandOptionArgs;
|
|
369
372
|
/** Allowed values */
|
|
370
373
|
choices?: string[];
|
|
371
374
|
}
|