@clo4/cli 3.0.0 → 3.0.1
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/dist/index.js +324 -312
- package/dist/index.js.map +1 -1
- package/dist/testing.js +414 -369
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -192,42 +192,6 @@ function closestChoice(input, choices) {
|
|
|
192
192
|
}
|
|
193
193
|
// src/subcommands.ts
|
|
194
194
|
/**
|
|
195
|
-
* Find the subcommand `token` names among `subcommands`, in a command where
|
|
196
|
-
* `directives` are in effect, or else among the persistent subcommands the
|
|
197
|
-
* command `inherited`. An exact name always wins. With
|
|
198
|
-
* `subcommandsMatchUniquePrefix`, a token that's a prefix of exactly one
|
|
199
|
-
* of the command's own names matches that command, unless `exactOnly`. A
|
|
200
|
-
* hidden or inherited command only matches its exact name, so it can't make a
|
|
201
|
-
* visible command's prefix ambiguous or take a prefix meant as an argument,
|
|
202
|
-
* and an empty token is no command's prefix.
|
|
203
|
-
*/
|
|
204
|
-
function matchSubcommand(subcommands, token, directives, exactOnly = false, inherited = []) {
|
|
205
|
-
for (const candidate of subcommands) if (hasName$1(candidate, token)) return { command: candidate };
|
|
206
|
-
for (const candidate of inherited) if (hasName$1(candidate, token)) return { command: candidate };
|
|
207
|
-
const matches = directives.subcommandsMatchUniquePrefix && !exactOnly ? prefixMatches(subcommands, token) : [];
|
|
208
|
-
if (matches.length === 1) return { command: matches[0] };
|
|
209
|
-
return new NoSubcommand(withInherited(subcommands, inherited), token, matches);
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* A command's own `subcommands` followed by the `inherited` ones it can use,
|
|
213
|
-
* leaving out each inherited one that an earlier command shadows a name of.
|
|
214
|
-
*/
|
|
215
|
-
function withInherited(subcommands, inherited) {
|
|
216
|
-
if (inherited.length === 0) return subcommands;
|
|
217
|
-
const all = [...subcommands];
|
|
218
|
-
for (const candidate of inherited) if (!asArray(candidate.name).some((name) => all.some((earlier) => hasName$1(earlier, name)))) all.push(candidate);
|
|
219
|
-
return all;
|
|
220
|
-
}
|
|
221
|
-
function hasName$1(command, token) {
|
|
222
|
-
const names = command.name;
|
|
223
|
-
return typeof names === "string" ? names === token : names.includes(token);
|
|
224
|
-
}
|
|
225
|
-
/** The visible commands that `token` is a prefix of a name of, each listed once. */
|
|
226
|
-
function prefixMatches(subcommands, token) {
|
|
227
|
-
if (token === "") return [];
|
|
228
|
-
return subcommands.filter((candidate) => !candidate.hidden && asArray(candidate.name).some((name) => name.startsWith(token)));
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
195
|
* A token that names no subcommand. The reason is only worked out when asked
|
|
232
196
|
* for, since most tokens the parser tries aren't subcommands.
|
|
233
197
|
*/
|
|
@@ -293,16 +257,17 @@ function applyDirectives(directives, command) {
|
|
|
293
257
|
}
|
|
294
258
|
// src/scope.ts
|
|
295
259
|
/**
|
|
296
|
-
*
|
|
260
|
+
* What a name means at one command on a path: the options, subcommands,
|
|
261
|
+
* directives and argument limits in effect there.
|
|
297
262
|
*
|
|
298
263
|
* Moving to a subcommand replaces the local options and adds its persistent
|
|
299
264
|
* options to the inherited ones. A name refers to one option: a local option
|
|
300
265
|
* wins over a persistent one, and a later option wins over an earlier one in
|
|
301
|
-
* the same list.
|
|
302
|
-
*
|
|
303
|
-
*
|
|
266
|
+
* the same list. A subcommand wins over an option where the next token can
|
|
267
|
+
* name a subcommand. The parser, help, completion and the spec checker all
|
|
268
|
+
* ask this module what a name refers to, so they can't disagree.
|
|
304
269
|
*/
|
|
305
|
-
var
|
|
270
|
+
var CommandScope = class CommandScope {
|
|
306
271
|
/** Non-persistent options of the current command, in definition order. */
|
|
307
272
|
local = [];
|
|
308
273
|
/** Persistent options from every command on the path, in definition order. */
|
|
@@ -314,25 +279,69 @@ var OptionScope = class OptionScope {
|
|
|
314
279
|
inheritedSubcommands = [];
|
|
315
280
|
/** The directives in effect. A subcommand inherits each until it sets its own. */
|
|
316
281
|
directives = defaultDirectives();
|
|
317
|
-
|
|
282
|
+
current = null;
|
|
283
|
+
/** The fewest positional arguments the current command takes. */
|
|
284
|
+
argsMin = 0;
|
|
285
|
+
/** The most positional arguments the current command takes. */
|
|
286
|
+
argsMax = 0;
|
|
318
287
|
/** The scope at the last command on `path`. */
|
|
319
288
|
static at(path) {
|
|
320
|
-
const scope = new
|
|
289
|
+
const scope = new CommandScope();
|
|
321
290
|
for (const command of path) scope.enter(command);
|
|
322
291
|
return scope;
|
|
323
292
|
}
|
|
324
293
|
/** Move to `command`, a subcommand of the current one. */
|
|
325
294
|
enter(command) {
|
|
326
|
-
const parentSubcommands = this.
|
|
295
|
+
const parentSubcommands = this.current?.subcommands;
|
|
327
296
|
if (parentSubcommands) {
|
|
328
297
|
const persistent = parentSubcommands.filter((subcommand) => subcommand.isPersistent);
|
|
329
298
|
if (persistent.length > 0) this.inheritedSubcommands.unshift(...persistent);
|
|
330
299
|
}
|
|
331
|
-
this.
|
|
300
|
+
this.current = command;
|
|
301
|
+
this.argsMin = getMinArgs(command.args);
|
|
302
|
+
this.argsMax = getMaxArgs(command.args);
|
|
332
303
|
this.local.length = 0;
|
|
333
304
|
if (command.options) for (const option of command.options) (option.isPersistent ? this.persistent : this.local).push(option);
|
|
334
305
|
applyDirectives(this.directives, command);
|
|
335
306
|
}
|
|
307
|
+
/** The current command. */
|
|
308
|
+
get command() {
|
|
309
|
+
if (!this.current) throw new Error("The scope hasn't entered a command");
|
|
310
|
+
return this.current;
|
|
311
|
+
}
|
|
312
|
+
/** Whether the current command has subcommands of its own or inherits any. */
|
|
313
|
+
get hasSubcommands() {
|
|
314
|
+
return this.current?.subcommands !== void 0 || this.inheritedSubcommands.length > 0;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* The subcommands the next token can name: the current command's own,
|
|
318
|
+
* followed by each inherited one that an earlier one doesn't shadow a name
|
|
319
|
+
* of. Hidden ones are included.
|
|
320
|
+
*/
|
|
321
|
+
subcommands() {
|
|
322
|
+
const own = this.current?.subcommands ?? [];
|
|
323
|
+
if (this.inheritedSubcommands.length === 0) return own;
|
|
324
|
+
const all = [...own];
|
|
325
|
+
for (const candidate of this.inheritedSubcommands) if (!asArray(candidate.name).some((name) => all.some((earlier) => hasName(earlier.name, name)))) all.push(candidate);
|
|
326
|
+
return all;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Find the subcommand `token` names: one of the current command's own, or
|
|
330
|
+
* else an inherited persistent one. An exact name always wins. With
|
|
331
|
+
* `subcommandsMatchUniquePrefix`, a token that's a prefix of exactly one of
|
|
332
|
+
* the command's own names matches that command, unless `exactOnly`. A hidden
|
|
333
|
+
* or inherited command only matches its exact name, so it can't make a
|
|
334
|
+
* visible command's prefix ambiguous or take a prefix meant as an argument,
|
|
335
|
+
* and an empty token is no command's prefix.
|
|
336
|
+
*/
|
|
337
|
+
matchSubcommand(token, exactOnly = false) {
|
|
338
|
+
const own = this.current?.subcommands ?? [];
|
|
339
|
+
for (const candidate of own) if (hasName(candidate.name, token)) return { command: candidate };
|
|
340
|
+
for (const candidate of this.inheritedSubcommands) if (hasName(candidate.name, token)) return { command: candidate };
|
|
341
|
+
const matches = this.directives.subcommandsMatchUniquePrefix && !exactOnly ? prefixMatches(own, token) : [];
|
|
342
|
+
if (matches.length === 1) return { command: matches[0] };
|
|
343
|
+
return new NoSubcommand(this.subcommands(), token, matches);
|
|
344
|
+
}
|
|
336
345
|
/** The option `name` refers to, if any. */
|
|
337
346
|
resolve(name) {
|
|
338
347
|
const { local, persistent } = this;
|
|
@@ -343,15 +352,16 @@ var OptionScope = class OptionScope {
|
|
|
343
352
|
/**
|
|
344
353
|
* The options that can be used here, in definition order, each with only
|
|
345
354
|
* its live names: the names that still refer to it. Options with every name
|
|
346
|
-
* shadowed are left out.
|
|
355
|
+
* shadowed are left out. Where the next token can name a subcommand, as
|
|
356
|
+
* `subcommands` says, a name that one matches is that subcommand's.
|
|
347
357
|
*/
|
|
348
|
-
live() {
|
|
358
|
+
live(subcommands = null) {
|
|
349
359
|
const owner = /* @__PURE__ */ new Map();
|
|
350
360
|
for (const options of [this.persistent, this.local]) for (const option of options) for (const name of asArray(option.name)) owner.set(name, option);
|
|
351
361
|
const pick = (options) => {
|
|
352
362
|
const picked = [];
|
|
353
363
|
for (const option of options) {
|
|
354
|
-
const names = asArray(option.name).filter((name) => owner.get(name) === option);
|
|
364
|
+
const names = asArray(option.name).filter((name) => owner.get(name) === option && !(subcommands && this.matchSubcommand(name, subcommands.exactOnly).command));
|
|
355
365
|
if (names.length > 0) picked.push({
|
|
356
366
|
option,
|
|
357
367
|
names
|
|
@@ -364,9 +374,12 @@ var OptionScope = class OptionScope {
|
|
|
364
374
|
persistent: pick(this.persistent)
|
|
365
375
|
};
|
|
366
376
|
}
|
|
367
|
-
/**
|
|
377
|
+
/**
|
|
378
|
+
* The options a user can see at the start of the current command: the
|
|
379
|
+
* {@link live} ones that aren't hidden, without the names a subcommand takes.
|
|
380
|
+
*/
|
|
368
381
|
visible() {
|
|
369
|
-
const { local, persistent } = this.live();
|
|
382
|
+
const { local, persistent } = this.live(this.hasSubcommands ? { exactOnly: false } : null);
|
|
370
383
|
const shown = (visible) => !visible.option.hidden;
|
|
371
384
|
return {
|
|
372
385
|
local: local.filter(shown),
|
|
@@ -375,8 +388,9 @@ var OptionScope = class OptionScope {
|
|
|
375
388
|
}
|
|
376
389
|
/** The live names of each option a user could have meant, for "did you mean" suggestions. */
|
|
377
390
|
suggestionNames() {
|
|
378
|
-
const { local, persistent } = this.
|
|
379
|
-
|
|
391
|
+
const { local, persistent } = this.live();
|
|
392
|
+
const shown = (visible) => !visible.option.hidden;
|
|
393
|
+
return [...local, ...persistent].filter(shown).map((visible) => visible.names);
|
|
380
394
|
}
|
|
381
395
|
/** The options that must be used, local first. */
|
|
382
396
|
required() {
|
|
@@ -407,6 +421,46 @@ var OptionScope = class OptionScope {
|
|
|
407
421
|
if (typeof required === "string") return required;
|
|
408
422
|
return required ? this.directives.optionArgSeparators[0] ?? "=" : null;
|
|
409
423
|
}
|
|
424
|
+
/** Whether the parser splits an option from its value at `separator` here. */
|
|
425
|
+
readsSeparator(separator) {
|
|
426
|
+
return this.directives.optionArgSeparators.includes(separator);
|
|
427
|
+
}
|
|
428
|
+
/** The separator that `text` starts with, or "" if none. Used after a short option. */
|
|
429
|
+
leadingSeparator(text) {
|
|
430
|
+
for (const separator of this.directives.optionArgSeparators) if (text.startsWith(separator)) return separator;
|
|
431
|
+
return "";
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The separator that appears first in `token`, so the value after it can
|
|
435
|
+
* contain the other separators. When two separators start at the same place,
|
|
436
|
+
* the longer one wins. Null if there's none, or if the one found is empty.
|
|
437
|
+
*/
|
|
438
|
+
firstSeparator(token) {
|
|
439
|
+
let found = "";
|
|
440
|
+
let index = -1;
|
|
441
|
+
for (const candidate of this.directives.optionArgSeparators) {
|
|
442
|
+
const at = token.indexOf(candidate);
|
|
443
|
+
if (at === -1) continue;
|
|
444
|
+
if (index === -1 || at < index || at === index && candidate.length > found.length) {
|
|
445
|
+
found = candidate;
|
|
446
|
+
index = at;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return found ? {
|
|
450
|
+
index,
|
|
451
|
+
separator: found
|
|
452
|
+
} : null;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Why `option`, used as `name`, can't take a value given after `separator`,
|
|
456
|
+
* or null if it can. A separate token has the separator "".
|
|
457
|
+
*/
|
|
458
|
+
separatorProblem(option, name, separator) {
|
|
459
|
+
const required = option.requiresSeparator;
|
|
460
|
+
if (typeof required === "string" && separator !== required) return `Incorrect separator, use '${required}' instead of '${separator}'`;
|
|
461
|
+
if (required === true && separator === "") return `Missing separator, use '${this.requiredSeparator(option)}' between ${name} and its value`;
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
410
464
|
};
|
|
411
465
|
const optionKeys = /* @__PURE__ */ new WeakMap();
|
|
412
466
|
/**
|
|
@@ -428,6 +482,11 @@ function optionKey(option) {
|
|
|
428
482
|
function hasName(names, name) {
|
|
429
483
|
return typeof names === "string" ? names === name : names.includes(name);
|
|
430
484
|
}
|
|
485
|
+
/** The visible commands that `token` is a prefix of a name of, each listed once. */
|
|
486
|
+
function prefixMatches(subcommands, token) {
|
|
487
|
+
if (token === "") return [];
|
|
488
|
+
return subcommands.filter((candidate) => !candidate.hidden && asArray(candidate.name).some((name) => name.startsWith(token)));
|
|
489
|
+
}
|
|
431
490
|
// src/help.ts
|
|
432
491
|
function dedent(str) {
|
|
433
492
|
if (str[0] !== "\n") return str;
|
|
@@ -474,14 +533,15 @@ const helpCommand = {
|
|
|
474
533
|
action({ path, args, help }) {
|
|
475
534
|
const self = path[path.length - 1];
|
|
476
535
|
const helpPath = path.slice(0, -1);
|
|
536
|
+
const scope = CommandScope.at(helpPath);
|
|
477
537
|
for (const name of args) {
|
|
478
538
|
const command = helpPath[helpPath.length - 1];
|
|
479
539
|
if (!command.subcommands) break;
|
|
480
|
-
const
|
|
481
|
-
const match = matchSubcommand(command.subcommands, name, scope.directives, false, scope.inheritedSubcommands);
|
|
540
|
+
const match = scope.matchSubcommand(name);
|
|
482
541
|
if (match.command === self && !command.subcommands.includes(self)) break;
|
|
483
542
|
if (match.command) {
|
|
484
543
|
helpPath.push(match.command);
|
|
544
|
+
scope.enter(match.command);
|
|
485
545
|
continue;
|
|
486
546
|
}
|
|
487
547
|
if (!command.requiresSubcommand && getMaxArgs(command.args) > 0) break;
|
|
@@ -508,7 +568,7 @@ function getSubcommandPathString(commands) {
|
|
|
508
568
|
}
|
|
509
569
|
function getSections(commands) {
|
|
510
570
|
const command = commands[commands.length - 1];
|
|
511
|
-
const scope =
|
|
571
|
+
const scope = CommandScope.at(commands);
|
|
512
572
|
const show = ({ option, names }) => optionToString(option, scope.forms(option, names));
|
|
513
573
|
const description = command.description;
|
|
514
574
|
const defaultDesc = "No description";
|
|
@@ -712,32 +772,9 @@ var MissingRequiredOption = class extends ParseError {
|
|
|
712
772
|
}
|
|
713
773
|
};
|
|
714
774
|
// src/parse.ts
|
|
715
|
-
/**
|
|
716
|
-
function
|
|
717
|
-
|
|
718
|
-
return "";
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Find the separator that appears first in `token`, so the value after it can
|
|
722
|
-
* contain the other separators. When two separators start at the same place,
|
|
723
|
-
* the longer one wins. Returns null if there's no separator, or if the one
|
|
724
|
-
* found is empty.
|
|
725
|
-
*/
|
|
726
|
-
function firstSeparator(token, separators) {
|
|
727
|
-
let sep = "";
|
|
728
|
-
let index = -1;
|
|
729
|
-
for (const candidate of separators) {
|
|
730
|
-
const at = token.indexOf(candidate);
|
|
731
|
-
if (at === -1) continue;
|
|
732
|
-
if (index === -1 || at < index || at === index && candidate.length > sep.length) {
|
|
733
|
-
sep = candidate;
|
|
734
|
-
index = at;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
return sep ? {
|
|
738
|
-
index,
|
|
739
|
-
sep
|
|
740
|
-
} : null;
|
|
775
|
+
/** How many times `option` can be used. */
|
|
776
|
+
function repeatLimit(option) {
|
|
777
|
+
return typeof option.isRepeatable === "number" ? option.isRepeatable : Infinity;
|
|
741
778
|
}
|
|
742
779
|
/**
|
|
743
780
|
* Throw if the options used break a `dependsOn` or `exclusiveOn` rule, or if
|
|
@@ -755,25 +792,21 @@ function checkOptionRules(options, seenOptions, required, context) {
|
|
|
755
792
|
for (const option of required) for (const name of asArray(option.name)) if (!options.has(name)) throw new MissingRequiredOption(name, context);
|
|
756
793
|
}
|
|
757
794
|
/**
|
|
758
|
-
* The state of reading one command line
|
|
759
|
-
* code that decides what each input token is.
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
* token.
|
|
795
|
+
* The state of reading one command line, one token at a time. This is the
|
|
796
|
+
* only code that decides what each input token is. It never throws: it skips
|
|
797
|
+
* whatever is invalid and keeps going, so {@link readNext} can still read a
|
|
798
|
+
* line that's wrong at the moment, and it keeps the first problem it finds for
|
|
799
|
+
* {@link parse} to throw. Each method handles one kind of token, and says what
|
|
800
|
+
* the token was read as.
|
|
764
801
|
*/
|
|
765
802
|
var Scanner = class {
|
|
766
|
-
strict;
|
|
767
803
|
path;
|
|
768
804
|
optionAction = void 0;
|
|
769
805
|
args = [];
|
|
770
806
|
options = /* @__PURE__ */ new Map();
|
|
771
807
|
seenOptions = [];
|
|
772
808
|
argSeparatorIndex = -1;
|
|
773
|
-
scope = new
|
|
774
|
-
subcommands = void 0;
|
|
775
|
-
commandArgsMin = 0;
|
|
776
|
-
commandArgsMax = 0;
|
|
809
|
+
scope = new CommandScope();
|
|
777
810
|
directives = this.scope.directives;
|
|
778
811
|
hasFoundArg = false;
|
|
779
812
|
openOption = null;
|
|
@@ -786,40 +819,60 @@ var Scanner = class {
|
|
|
786
819
|
valuesMin = 0;
|
|
787
820
|
valuesMax = 0;
|
|
788
821
|
valueArgs = [];
|
|
789
|
-
|
|
822
|
+
/**
|
|
823
|
+
* Makes the error for the first token that can't be read the way it's
|
|
824
|
+
* written, like an unknown option or a value an option doesn't take.
|
|
825
|
+
* {@link parse} stops there.
|
|
826
|
+
*/
|
|
827
|
+
problem = null;
|
|
790
828
|
tooManyArgs = null;
|
|
791
829
|
repeatedUses = /* @__PURE__ */ new Map();
|
|
792
|
-
|
|
793
|
-
constructor(spec, strict) {
|
|
794
|
-
this.strict = strict;
|
|
830
|
+
constructor(spec) {
|
|
795
831
|
this.path = [spec];
|
|
796
|
-
this.
|
|
832
|
+
this.scope.enter(spec);
|
|
797
833
|
}
|
|
798
834
|
ctx() {
|
|
799
|
-
return { path: this.path };
|
|
835
|
+
return { path: [...this.path] };
|
|
800
836
|
}
|
|
801
837
|
/** Whether the open option can take the next token as a value. */
|
|
802
838
|
get hasRoomForValue() {
|
|
803
839
|
return this.valueCount < this.separateValueMax;
|
|
804
840
|
}
|
|
805
|
-
enterCommand(command) {
|
|
806
|
-
this.scope.enter(command);
|
|
807
|
-
this.subcommands = command.subcommands;
|
|
808
|
-
this.commandArgsMin = getMinArgs(command.args);
|
|
809
|
-
this.commandArgsMax = getMaxArgs(command.args);
|
|
810
|
-
}
|
|
811
841
|
findOption(name) {
|
|
812
842
|
return this.scope.resolve(name);
|
|
813
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Keep the problem `make` describes if it's the first. The error is only
|
|
846
|
+
* made if {@link parse} throws it, since a lenient read never needs it and
|
|
847
|
+
* an error is slow to make. So `make` can't read anything later tokens change.
|
|
848
|
+
*/
|
|
849
|
+
fail(make) {
|
|
850
|
+
if (!this.problem) {
|
|
851
|
+
const ctx = this.ctx();
|
|
852
|
+
this.problem = () => make(ctx);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/** Keep the first argument the command has no room for, like {@link fail}. */
|
|
856
|
+
failArgs(make) {
|
|
857
|
+
if (!this.tooManyArgs) {
|
|
858
|
+
const ctx = this.ctx();
|
|
859
|
+
this.tooManyArgs = () => make(ctx);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
814
862
|
unknownOption(option) {
|
|
815
|
-
|
|
863
|
+
if (!this.problem) {
|
|
864
|
+
const names = this.scope.suggestionNames();
|
|
865
|
+
this.fail((ctx) => new UnknownOption(option, names, ctx));
|
|
866
|
+
}
|
|
816
867
|
}
|
|
817
868
|
endOption() {
|
|
818
869
|
const values = this.values;
|
|
819
|
-
if (
|
|
870
|
+
if (values) {
|
|
820
871
|
const given = values.length - this.valuesStart;
|
|
821
|
-
if (given < this.valuesMin)
|
|
822
|
-
|
|
872
|
+
if (given < this.valuesMin) {
|
|
873
|
+
const { openOptionName, valuesMin, valuesMax } = this;
|
|
874
|
+
this.fail((ctx) => new TooFewOptionArguments(openOptionName, valuesMin, valuesMax, ctx));
|
|
875
|
+
} else addDefaults(values, this.valueArgs, given);
|
|
823
876
|
}
|
|
824
877
|
this.values = null;
|
|
825
878
|
}
|
|
@@ -842,7 +895,6 @@ var Scanner = class {
|
|
|
842
895
|
const options = this.options;
|
|
843
896
|
let optionValues;
|
|
844
897
|
if (option.isRepeatable) {
|
|
845
|
-
const maxRepeat = option.isRepeatable === true ? Infinity : option.isRepeatable;
|
|
846
898
|
const key = optionKey(option);
|
|
847
899
|
let uses = this.repeatedUses.get(key);
|
|
848
900
|
if (!uses) {
|
|
@@ -852,7 +904,7 @@ var Scanner = class {
|
|
|
852
904
|
};
|
|
853
905
|
this.repeatedUses.set(key, uses);
|
|
854
906
|
}
|
|
855
|
-
if (
|
|
907
|
+
if (uses.count >= repeatLimit(option)) this.fail((ctx) => new ParseError(ctx, "Too many repetitions"));
|
|
856
908
|
uses.count += 1;
|
|
857
909
|
for (const other of names) options.set(other, uses.values);
|
|
858
910
|
if (!option.args) {
|
|
@@ -866,150 +918,162 @@ var Scanner = class {
|
|
|
866
918
|
}
|
|
867
919
|
this.valuesMin = this.valueMin;
|
|
868
920
|
this.valuesMax = getMaxArgs(option.args);
|
|
869
|
-
this.requiredSeparator = option.requiresSeparator ?? false;
|
|
870
921
|
if (this.valuesMax > 0) {
|
|
871
922
|
this.values = optionValues;
|
|
872
923
|
this.valuesStart = optionValues.length;
|
|
873
924
|
this.valueArgs = asArray(option.args);
|
|
874
925
|
}
|
|
875
926
|
}
|
|
876
|
-
/**
|
|
877
|
-
* Add a value to the open option. Returns whether it's a value the option
|
|
878
|
-
* accepts. A lenient read keeps going either way.
|
|
879
|
-
*/
|
|
927
|
+
/** Add a value to the open option. Returns whether it's a value the option accepts. */
|
|
880
928
|
addValue(value, separator) {
|
|
881
929
|
this.valueCount += 1;
|
|
882
930
|
if (!this.values) {
|
|
883
|
-
|
|
931
|
+
const name = this.openOptionName;
|
|
932
|
+
this.fail((ctx) => new InvalidOptionArg(name, ctx));
|
|
884
933
|
return false;
|
|
885
934
|
}
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
if (typeof requiredSeparator === "string" && separator !== requiredSeparator) {
|
|
889
|
-
if (this.strict) throw new ParseError(this.ctx(), `Incorrect separator, use '${requiredSeparator}' instead of '${separator}'`);
|
|
890
|
-
accepted = false;
|
|
891
|
-
}
|
|
892
|
-
if (requiredSeparator === true && separator === "") {
|
|
893
|
-
if (this.strict) {
|
|
894
|
-
const suggested = this.scope.requiredSeparator(this.openOption);
|
|
895
|
-
throw new ParseError(this.ctx(), `Missing separator, use '${suggested}' between ${this.openOptionName} and its value`);
|
|
896
|
-
}
|
|
897
|
-
accepted = false;
|
|
898
|
-
}
|
|
935
|
+
const problem = this.scope.separatorProblem(this.openOption, this.openOptionName, separator);
|
|
936
|
+
if (problem) this.fail((ctx) => new ParseError(ctx, problem));
|
|
899
937
|
this.values.push(value);
|
|
900
|
-
return
|
|
938
|
+
return problem === null;
|
|
939
|
+
}
|
|
940
|
+
/** Add a separate token as a value of the open option. */
|
|
941
|
+
value(token) {
|
|
942
|
+
this.addValue(token, "");
|
|
943
|
+
return {
|
|
944
|
+
kind: "value",
|
|
945
|
+
option: this.openOption
|
|
946
|
+
};
|
|
901
947
|
}
|
|
902
948
|
/**
|
|
903
|
-
* Add a value attached to the option's own token,
|
|
904
|
-
*
|
|
905
|
-
*
|
|
949
|
+
* Add a value attached to the option's own token, starting at `start`. It's
|
|
950
|
+
* the option's only value, so no later token can be one. Returns where the
|
|
951
|
+
* value starts, or null if the option doesn't accept it.
|
|
906
952
|
*/
|
|
907
|
-
addAttachedValue(
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
index,
|
|
911
|
-
start
|
|
912
|
-
};
|
|
953
|
+
addAttachedValue(start, value, separator) {
|
|
954
|
+
const option = this.openOption;
|
|
955
|
+
const accepted = this.addValue(value, separator);
|
|
913
956
|
this.valueMin = this.separateValueMax = 0;
|
|
957
|
+
return accepted ? {
|
|
958
|
+
option,
|
|
959
|
+
start
|
|
960
|
+
} : null;
|
|
914
961
|
}
|
|
915
962
|
/**
|
|
916
963
|
* Whether the command already has all the arguments it takes. A command that
|
|
917
964
|
* requires a subcommand takes none.
|
|
918
965
|
*/
|
|
919
966
|
get argsAreFull() {
|
|
920
|
-
|
|
921
|
-
return path[path.length - 1].requiresSubcommand === true || this.args.length >= this.commandArgsMax;
|
|
967
|
+
return this.scope.command.requiresSubcommand === true || this.args.length >= this.scope.argsMax;
|
|
922
968
|
}
|
|
923
969
|
addArg(value) {
|
|
924
970
|
this.endOption();
|
|
925
|
-
if (this.
|
|
971
|
+
if (this.argsAreFull) this.failArgs((ctx) => new TooManyArguments(value, ctx));
|
|
926
972
|
this.args.push(value);
|
|
973
|
+
return { kind: "argument" };
|
|
927
974
|
}
|
|
928
|
-
/** Read
|
|
929
|
-
argSeparator(
|
|
975
|
+
/** Read `--`. Every token after it is an argument. */
|
|
976
|
+
argSeparator() {
|
|
930
977
|
this.closeOption();
|
|
931
|
-
const command = this.
|
|
932
|
-
if (this.
|
|
978
|
+
const command = this.scope.command;
|
|
979
|
+
if (this.argsAreFull && !command.requiresSubcommand) this.failArgs((ctx) => new ParseError(ctx, "Unexpected argument '--', did you mean to use an option instead?"));
|
|
933
980
|
this.argSeparatorIndex = this.args.length;
|
|
934
|
-
|
|
981
|
+
return { kind: "separator" };
|
|
935
982
|
}
|
|
936
983
|
/**
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
* the next token as its value. A required or variadic option
|
|
940
|
-
* priority over a subcommand.
|
|
984
|
+
* How the next token can name a subcommand, its own or an inherited one, or
|
|
985
|
+
* null if it can't. It can't once an argument is found, or while the open
|
|
986
|
+
* option needs the next token as its value. A required or variadic option
|
|
987
|
+
* value takes priority over a subcommand. A short optional value could be a
|
|
988
|
+
* prefix of a subcommand's name, so only an exact name can end an option
|
|
989
|
+
* that still has room.
|
|
941
990
|
*/
|
|
942
|
-
get
|
|
943
|
-
if (this.hasFoundArg) return
|
|
944
|
-
if (
|
|
945
|
-
return
|
|
991
|
+
get subcommandMatching() {
|
|
992
|
+
if (this.hasFoundArg || !this.scope.hasSubcommands) return null;
|
|
993
|
+
if (this.valueCount < this.valueMin || this.hasRoomForValue && this.separateValueMax === Infinity) return null;
|
|
994
|
+
return { exactOnly: this.hasRoomForValue };
|
|
946
995
|
}
|
|
947
996
|
/**
|
|
948
997
|
* The subcommand the next token enters if it's `token`, or why it enters
|
|
949
998
|
* none. Null if the next token can't be a subcommand at all.
|
|
950
999
|
*/
|
|
951
1000
|
subcommandFor(token) {
|
|
952
|
-
|
|
953
|
-
return
|
|
1001
|
+
const matching = this.subcommandMatching;
|
|
1002
|
+
return matching && this.scope.matchSubcommand(token, matching.exactOnly);
|
|
954
1003
|
}
|
|
955
1004
|
enterSubcommand(command) {
|
|
956
1005
|
this.closeOption();
|
|
957
1006
|
this.path.push(command);
|
|
958
|
-
this.
|
|
1007
|
+
this.scope.enter(command);
|
|
1008
|
+
return {
|
|
1009
|
+
kind: "subcommand",
|
|
1010
|
+
command
|
|
1011
|
+
};
|
|
959
1012
|
}
|
|
960
1013
|
/** Read a token like `-abc`, `+x` or `-ovalue`. */
|
|
961
|
-
shortOptions(token
|
|
1014
|
+
shortOptions(token) {
|
|
962
1015
|
const leadingChar = token[0];
|
|
963
1016
|
const dashOption = this.findOption(leadingChar);
|
|
964
1017
|
if (dashOption?.args && !this.findOption(token.slice(0, 2))) {
|
|
965
1018
|
this.useOption(dashOption, leadingChar);
|
|
966
|
-
this.addAttachedValue(
|
|
967
|
-
return
|
|
1019
|
+
const attached = this.addAttachedValue(1, token.slice(1), "");
|
|
1020
|
+
return {
|
|
1021
|
+
kind: "options",
|
|
1022
|
+
options: [dashOption],
|
|
1023
|
+
attached
|
|
1024
|
+
};
|
|
968
1025
|
}
|
|
1026
|
+
const used = [];
|
|
1027
|
+
let attached = null;
|
|
969
1028
|
for (let char = 1; char < token.length; char++) {
|
|
970
1029
|
const optionName = leadingChar + token[char];
|
|
971
1030
|
const option = this.findOption(optionName);
|
|
972
1031
|
if (!option) {
|
|
973
|
-
if (char === 1 && this.hasRoomForValue)
|
|
974
|
-
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
if (this.strict) throw this.unknownOption(optionName);
|
|
1032
|
+
if (char === 1 && this.hasRoomForValue) return this.value(token);
|
|
1033
|
+
this.unknownOption(optionName);
|
|
978
1034
|
continue;
|
|
979
1035
|
}
|
|
980
1036
|
this.useOption(option, optionName);
|
|
981
|
-
|
|
982
|
-
|
|
1037
|
+
used.push(option);
|
|
1038
|
+
if (char === token.length - 1) break;
|
|
1039
|
+
const sep = this.scope.leadingSeparator(token.slice(char + 1));
|
|
983
1040
|
if (option.args || sep && !this.findOption(leadingChar + token[char + 1])) {
|
|
984
1041
|
const argStart = char + sep.length + 1;
|
|
985
|
-
this.addAttachedValue(
|
|
986
|
-
|
|
1042
|
+
attached = this.addAttachedValue(argStart, token.slice(argStart), sep);
|
|
1043
|
+
break;
|
|
987
1044
|
}
|
|
988
1045
|
}
|
|
1046
|
+
return used.length > 0 ? {
|
|
1047
|
+
kind: "options",
|
|
1048
|
+
options: used,
|
|
1049
|
+
attached
|
|
1050
|
+
} : { kind: "unknown" };
|
|
989
1051
|
}
|
|
990
1052
|
/**
|
|
991
1053
|
* Read a token like `--name` or `--name=value`, or any token when options
|
|
992
|
-
* aren't POSIX compliant. Returns
|
|
1054
|
+
* aren't POSIX compliant. Returns null if the token should be an argument.
|
|
993
1055
|
*/
|
|
994
|
-
longOption(token
|
|
995
|
-
const found = firstSeparator(token
|
|
1056
|
+
longOption(token) {
|
|
1057
|
+
const found = this.scope.firstSeparator(token);
|
|
996
1058
|
const optionName = found ? token.slice(0, found.index) : token;
|
|
997
1059
|
const option = this.findOption(optionName);
|
|
998
1060
|
if (option) {
|
|
999
1061
|
this.useOption(option, optionName);
|
|
1062
|
+
let attached = null;
|
|
1000
1063
|
if (found) {
|
|
1001
|
-
const valueStart = found.index + found.
|
|
1002
|
-
this.addAttachedValue(
|
|
1064
|
+
const valueStart = found.index + found.separator.length;
|
|
1065
|
+
attached = this.addAttachedValue(valueStart, token.slice(valueStart), found.separator);
|
|
1003
1066
|
}
|
|
1004
|
-
return
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
return true;
|
|
1067
|
+
return {
|
|
1068
|
+
kind: "options",
|
|
1069
|
+
options: [option],
|
|
1070
|
+
attached
|
|
1071
|
+
};
|
|
1010
1072
|
}
|
|
1011
|
-
if (this.
|
|
1012
|
-
return
|
|
1073
|
+
if (this.directives.flagsArePosixNoncompliant) return null;
|
|
1074
|
+
if (this.hasRoomForValue) return this.value(token);
|
|
1075
|
+
this.unknownOption(optionName);
|
|
1076
|
+
return { kind: "unknown" };
|
|
1013
1077
|
}
|
|
1014
1078
|
/**
|
|
1015
1079
|
* Read a token that isn't `--`, a subcommand or an option. It's a value of
|
|
@@ -1018,34 +1082,39 @@ var Scanner = class {
|
|
|
1018
1082
|
* subcommand, if it could have been one.
|
|
1019
1083
|
*/
|
|
1020
1084
|
valueOrArg(token, unmatched) {
|
|
1021
|
-
if (this.hasRoomForValue)
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
if (this.strict && unmatched && this.argsAreFull) {
|
|
1026
|
-
const command = this.path[this.path.length - 1];
|
|
1027
|
-
if (command.subcommands?.length || command.requiresSubcommand) {
|
|
1085
|
+
if (this.hasRoomForValue) return this.value(token);
|
|
1086
|
+
if (unmatched && this.argsAreFull) {
|
|
1087
|
+
const command = this.scope.command;
|
|
1088
|
+
if (command.subcommands?.length || command.requiresSubcommand) this.failArgs((ctx) => {
|
|
1028
1089
|
const { message, didYouMean } = unmatched.reason();
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1090
|
+
return new ParseError(ctx, message, didYouMean);
|
|
1091
|
+
});
|
|
1031
1092
|
}
|
|
1032
1093
|
this.hasFoundArg = true;
|
|
1033
|
-
this.addArg(token);
|
|
1094
|
+
return this.addArg(token);
|
|
1034
1095
|
}
|
|
1035
1096
|
/**
|
|
1036
|
-
* Throw if the finished command line is missing anything. Returns
|
|
1037
|
-
*
|
|
1097
|
+
* Throw if the finished command line is missing anything. Returns what it
|
|
1098
|
+
* was parsed as. Only call this when there's no {@link problem}.
|
|
1038
1099
|
*/
|
|
1039
1100
|
finish() {
|
|
1040
|
-
const command = this.
|
|
1101
|
+
const command = this.scope.command;
|
|
1041
1102
|
const showsUsage = command.requiresSubcommand && !command.action && this.args.length === 0;
|
|
1042
1103
|
const skipsChecks = this.optionAction !== void 0 || showsUsage;
|
|
1043
|
-
if (!skipsChecks && this.tooManyArgs) throw this.tooManyArgs;
|
|
1044
|
-
if (!skipsChecks && this.args.length < this.
|
|
1104
|
+
if (!skipsChecks && this.tooManyArgs) throw this.tooManyArgs();
|
|
1105
|
+
if (!skipsChecks && this.args.length < this.scope.argsMin) throw new TooFewArguments(this.scope.argsMin, this.scope.argsMax, this.ctx());
|
|
1045
1106
|
this.endOption();
|
|
1107
|
+
if (this.problem) throw this.problem();
|
|
1046
1108
|
if (!skipsChecks) checkOptionRules(this.options, this.seenOptions, this.scope.required(), this.ctx());
|
|
1047
|
-
if (!command.requiresSubcommand && this.args.length >= this.
|
|
1048
|
-
return
|
|
1109
|
+
if (!command.requiresSubcommand && this.args.length >= this.scope.argsMin) addDefaults(this.args, asArray(command.args), this.args.length);
|
|
1110
|
+
return {
|
|
1111
|
+
path: this.path,
|
|
1112
|
+
action: this.optionAction ?? command.action ?? usage,
|
|
1113
|
+
args: this.args,
|
|
1114
|
+
argSeparatorIndex: this.argSeparatorIndex,
|
|
1115
|
+
options: this.options,
|
|
1116
|
+
defaults: (name) => this.defaults(name)
|
|
1117
|
+
};
|
|
1049
1118
|
}
|
|
1050
1119
|
/** See {@link ParseResult.defaults}. */
|
|
1051
1120
|
defaults(name) {
|
|
@@ -1058,42 +1127,41 @@ var Scanner = class {
|
|
|
1058
1127
|
}
|
|
1059
1128
|
/** Whether using `option` next would go over its repeat limit or break an `exclusiveOn` rule. */
|
|
1060
1129
|
conflicts(option) {
|
|
1061
|
-
|
|
1130
|
+
if ((this.repeatedUses.get(optionKey(option))?.count ?? 0) >= repeatLimit(option)) return true;
|
|
1062
1131
|
const options = this.options;
|
|
1063
|
-
if (typeof option.isRepeatable === "number") {
|
|
1064
|
-
if ((this.repeatedUses.get(optionKey(option))?.count ?? 0) >= option.isRepeatable) return true;
|
|
1065
|
-
}
|
|
1066
1132
|
if (option.exclusiveOn?.some((name) => options.has(name))) return true;
|
|
1133
|
+
const names = asArray(option.name);
|
|
1067
1134
|
return this.seenOptions.some((seen) => seen.exclusiveOn?.some((name) => names.includes(name)));
|
|
1068
1135
|
}
|
|
1069
1136
|
/** The options the next token can use. See {@link NextToken.options}. */
|
|
1070
1137
|
usableOptions() {
|
|
1071
1138
|
const used = new Set(this.seenOptions.map(optionKey));
|
|
1072
|
-
const { local, persistent } = this.scope.live();
|
|
1139
|
+
const { local, persistent } = this.scope.live(this.subcommandMatching);
|
|
1073
1140
|
const usable = [];
|
|
1074
1141
|
for (const { option, names } of [...local, ...persistent]) {
|
|
1075
1142
|
if (this.conflicts(option)) continue;
|
|
1076
|
-
const typeable = names.filter((name) => !this.subcommandFor(name)?.command);
|
|
1077
|
-
if (typeable.length === 0) continue;
|
|
1078
1143
|
usable.push({
|
|
1079
1144
|
option,
|
|
1080
|
-
forms: this.scope.forms(option,
|
|
1145
|
+
forms: this.scope.forms(option, names),
|
|
1081
1146
|
used: used.has(optionKey(option))
|
|
1082
1147
|
});
|
|
1083
1148
|
}
|
|
1084
1149
|
return usable;
|
|
1085
1150
|
}
|
|
1086
|
-
/**
|
|
1151
|
+
/**
|
|
1152
|
+
* What the next token can be. See {@link readNext}. Reading more tokens
|
|
1153
|
+
* doesn't change what this returns.
|
|
1154
|
+
*/
|
|
1087
1155
|
next() {
|
|
1088
1156
|
const open = this.openOption && this.hasRoomForValue ? this.openOption : null;
|
|
1089
|
-
const command = this.
|
|
1157
|
+
const command = this.scope.command;
|
|
1090
1158
|
const afterSeparator = this.argSeparatorIndex !== -1;
|
|
1091
1159
|
const optionsAllowed = !afterSeparator && this.optionsAllowed;
|
|
1092
1160
|
return {
|
|
1093
|
-
path: this.path,
|
|
1094
|
-
directives: this.directives,
|
|
1161
|
+
path: [...this.path],
|
|
1162
|
+
directives: { ...this.directives },
|
|
1095
1163
|
optionsAllowed,
|
|
1096
|
-
subcommands: !afterSeparator && this.
|
|
1164
|
+
subcommands: !afterSeparator && this.subcommandMatching ? this.scope.subcommands() : [],
|
|
1097
1165
|
options: optionsAllowed ? this.usableOptions() : [],
|
|
1098
1166
|
value: open && {
|
|
1099
1167
|
option: open,
|
|
@@ -1103,79 +1171,27 @@ var Scanner = class {
|
|
|
1103
1171
|
arg: open ? void 0 : argAt(asArray(command.args), this.args.length)
|
|
1104
1172
|
};
|
|
1105
1173
|
}
|
|
1106
|
-
/** Read the token at `index`, the next one, and say what it was read as. */
|
|
1107
|
-
readAndClassify(input, index) {
|
|
1108
|
-
if (this.argSeparatorIndex !== -1) return { kind: "argument" };
|
|
1109
|
-
const depth = this.path.length;
|
|
1110
|
-
const seen = this.seenOptions.length;
|
|
1111
|
-
const args = this.args.length;
|
|
1112
|
-
const values = this.valueCount;
|
|
1113
|
-
if (this.readAt(input, index)) return { kind: "separator" };
|
|
1114
|
-
if (this.path.length > depth) return {
|
|
1115
|
-
kind: "subcommand",
|
|
1116
|
-
command: this.path[depth]
|
|
1117
|
-
};
|
|
1118
|
-
if (this.seenOptions.length > seen) {
|
|
1119
|
-
const found = this.attachedAt(index);
|
|
1120
|
-
const attached = found && {
|
|
1121
|
-
option: found.option,
|
|
1122
|
-
start: found.start
|
|
1123
|
-
};
|
|
1124
|
-
return {
|
|
1125
|
-
kind: "options",
|
|
1126
|
-
options: this.seenOptions.slice(seen),
|
|
1127
|
-
attached
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
if (this.args.length > args) return { kind: "argument" };
|
|
1131
|
-
if (this.valueCount > values) return {
|
|
1132
|
-
kind: "value",
|
|
1133
|
-
option: this.openOption
|
|
1134
|
-
};
|
|
1135
|
-
return { kind: "unknown" };
|
|
1136
|
-
}
|
|
1137
|
-
/** The value attached to the token at `index`, if it has one the option accepts. */
|
|
1138
|
-
attachedAt(index) {
|
|
1139
|
-
return this.attached?.index === index ? this.attached : null;
|
|
1140
|
-
}
|
|
1141
1174
|
/** Whether the next token can be an option. Once options must stop, every token is an argument. */
|
|
1142
1175
|
get optionsAllowed() {
|
|
1143
1176
|
return !(this.directives.optionsMustPrecedeArguments && this.hasFoundArg);
|
|
1144
1177
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
for (let index = 0; index < input.length; index++) if (this.readAt(input, index)) return;
|
|
1148
|
-
}
|
|
1149
|
-
/**
|
|
1150
|
-
* Read the token at `index`. Returns true if it was `--`, which reads every
|
|
1151
|
-
* token after it too.
|
|
1152
|
-
*/
|
|
1153
|
-
readAt(input, index) {
|
|
1154
|
-
const token = input[index];
|
|
1178
|
+
read(token) {
|
|
1179
|
+
if (this.argSeparatorIndex !== -1) return this.addArg(token);
|
|
1155
1180
|
const optionsAllowed = this.optionsAllowed;
|
|
1156
|
-
if (token === "--" && optionsAllowed)
|
|
1157
|
-
this.argSeparator(input, index);
|
|
1158
|
-
return true;
|
|
1159
|
-
}
|
|
1181
|
+
if (token === "--" && optionsAllowed) return this.argSeparator();
|
|
1160
1182
|
const subcommand = this.subcommandFor(token);
|
|
1161
|
-
if (subcommand?.command)
|
|
1162
|
-
this.enterSubcommand(subcommand.command);
|
|
1163
|
-
return false;
|
|
1164
|
-
}
|
|
1183
|
+
if (subcommand?.command) return this.enterSubcommand(subcommand.command);
|
|
1165
1184
|
if (optionsAllowed) {
|
|
1166
1185
|
const flagsArePosixNoncompliant = this.directives.flagsArePosixNoncompliant;
|
|
1167
1186
|
const first = token.charCodeAt(0);
|
|
1168
1187
|
const isDoubleDash = first === 45 && token.charCodeAt(1) === 45;
|
|
1169
|
-
if (!flagsArePosixNoncompliant && token.length > 1 && (first === 45 || first === 43) && !isDoubleDash)
|
|
1170
|
-
this.shortOptions(token, index);
|
|
1171
|
-
return false;
|
|
1172
|
-
}
|
|
1188
|
+
if (!flagsArePosixNoncompliant && token.length > 1 && (first === 45 || first === 43) && !isDoubleDash) return this.shortOptions(token);
|
|
1173
1189
|
if (flagsArePosixNoncompliant || token.length > 2 && isDoubleDash) {
|
|
1174
|
-
|
|
1190
|
+
const reading = this.longOption(token);
|
|
1191
|
+
if (reading) return reading;
|
|
1175
1192
|
}
|
|
1176
1193
|
}
|
|
1177
|
-
this.valueOrArg(token, subcommand);
|
|
1178
|
-
return false;
|
|
1194
|
+
return this.valueOrArg(token, subcommand);
|
|
1179
1195
|
}
|
|
1180
1196
|
};
|
|
1181
1197
|
/** The argument at `index`, reusing a final variadic argument. */
|
|
@@ -1186,17 +1202,19 @@ function argAt(args, index) {
|
|
|
1186
1202
|
}
|
|
1187
1203
|
/**
|
|
1188
1204
|
* Work out what the token after `before` can be, and how the parser reads
|
|
1189
|
-
* `partial
|
|
1190
|
-
* line that's wrong at the moment still has
|
|
1191
|
-
* this.
|
|
1205
|
+
* `partial`: whether it's an option, and if so, whether it has a value
|
|
1206
|
+
* attached. Reads leniently, so a line that's wrong at the moment still has
|
|
1207
|
+
* an answer. Completion is built on this.
|
|
1192
1208
|
*/
|
|
1193
1209
|
function readNext(spec, before, partial) {
|
|
1194
|
-
const s = new Scanner(spec
|
|
1195
|
-
s.read(
|
|
1210
|
+
const s = new Scanner(spec);
|
|
1211
|
+
for (const token of before) s.read(token);
|
|
1196
1212
|
const next = s.next();
|
|
1197
1213
|
let attached = null;
|
|
1214
|
+
let partialIsOption = false;
|
|
1198
1215
|
if (partial !== "" && next.optionsAllowed) {
|
|
1199
|
-
const reading =
|
|
1216
|
+
const reading = s.read(partial);
|
|
1217
|
+
partialIsOption = reading.kind === "options" || reading.kind === "unknown" || reading.kind === "separator";
|
|
1200
1218
|
const found = reading.kind === "options" ? reading.attached : null;
|
|
1201
1219
|
const arg = found && asArray(found.option.args)[0];
|
|
1202
1220
|
if (found && arg) attached = {
|
|
@@ -1208,19 +1226,11 @@ function readNext(spec, before, partial) {
|
|
|
1208
1226
|
}
|
|
1209
1227
|
return {
|
|
1210
1228
|
...next,
|
|
1211
|
-
attached
|
|
1229
|
+
attached,
|
|
1230
|
+
partialIsOption
|
|
1212
1231
|
};
|
|
1213
1232
|
}
|
|
1214
1233
|
/**
|
|
1215
|
-
* Say what the parser reads `token` as when it comes after `before`. Reads
|
|
1216
|
-
* leniently, like {@link readNext}.
|
|
1217
|
-
*/
|
|
1218
|
-
function readToken(spec, before, token) {
|
|
1219
|
-
const s = new Scanner(spec, false);
|
|
1220
|
-
s.read(before);
|
|
1221
|
-
return s.readAndClassify([...before, token], before.length);
|
|
1222
|
-
}
|
|
1223
|
-
/**
|
|
1224
1234
|
* Parse the input tokens using the given CLI spec.
|
|
1225
1235
|
*
|
|
1226
1236
|
* This returns a `ParseResult`, which contains the array of
|
|
@@ -1230,17 +1240,12 @@ function readToken(spec, before, token) {
|
|
|
1230
1240
|
* If parsing fails, this function throws.
|
|
1231
1241
|
*/
|
|
1232
1242
|
function parse(input, spec) {
|
|
1233
|
-
const s = new Scanner(spec
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
args: s.args,
|
|
1240
|
-
argSeparatorIndex: s.argSeparatorIndex,
|
|
1241
|
-
options: s.options,
|
|
1242
|
-
defaults: (name) => s.defaults(name)
|
|
1243
|
-
};
|
|
1243
|
+
const s = new Scanner(spec);
|
|
1244
|
+
for (const token of input) {
|
|
1245
|
+
s.read(token);
|
|
1246
|
+
if (s.problem) throw s.problem();
|
|
1247
|
+
}
|
|
1248
|
+
return s.finish();
|
|
1244
1249
|
}
|
|
1245
1250
|
// src/completion.ts
|
|
1246
1251
|
const SHELLS = [
|
|
@@ -1320,11 +1325,18 @@ async function argValues(arg, tokens, partial) {
|
|
|
1320
1325
|
}
|
|
1321
1326
|
/** Decide which kinds of candidate the word being completed can use. */
|
|
1322
1327
|
function wantedCandidates(next, req) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1328
|
+
if (!req.gateOptions || next.directives.flagsArePosixNoncompliant) return {
|
|
1329
|
+
wantValues: true,
|
|
1330
|
+
wantOptions: true,
|
|
1331
|
+
wantSubcommand: () => true
|
|
1332
|
+
};
|
|
1333
|
+
const partial = req.partial;
|
|
1334
|
+
const wantValues = !next.partialIsOption;
|
|
1335
|
+
const starts = (name) => partial !== "" && name.startsWith(partial);
|
|
1325
1336
|
return {
|
|
1326
|
-
wantValues
|
|
1327
|
-
wantOptions:
|
|
1337
|
+
wantValues,
|
|
1338
|
+
wantOptions: next.options.some(({ forms }) => forms.some(({ name }) => starts(name))),
|
|
1339
|
+
wantSubcommand: (name) => wantValues || starts(name)
|
|
1328
1340
|
};
|
|
1329
1341
|
}
|
|
1330
1342
|
/**
|
|
@@ -1344,17 +1356,17 @@ async function completeRuntime(req) {
|
|
|
1344
1356
|
}
|
|
1345
1357
|
if (next.value?.required) return next.value.arg ? argValues(next.value.arg, tokens, req.partial) : [];
|
|
1346
1358
|
const candidates = [];
|
|
1347
|
-
const { wantValues, wantOptions } = wantedCandidates(next, req);
|
|
1359
|
+
const { wantValues, wantOptions, wantSubcommand } = wantedCandidates(next, req);
|
|
1348
1360
|
if (wantValues) {
|
|
1349
1361
|
const arg = next.value ? next.value.arg : next.arg;
|
|
1350
1362
|
if (arg) candidates.push(...await argValues(arg, tokens, req.partial));
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1363
|
+
}
|
|
1364
|
+
for (const sub of visibleSubcommands(next.subcommands)) {
|
|
1365
|
+
const description = firstLine(sub.description);
|
|
1366
|
+
for (const token of makeArray(sub.name)) if (wantSubcommand(token)) candidates.push({
|
|
1367
|
+
value: token,
|
|
1368
|
+
description
|
|
1369
|
+
});
|
|
1358
1370
|
}
|
|
1359
1371
|
if (wantOptions) candidates.push(...optionCandidates(next));
|
|
1360
1372
|
return candidates;
|