@optique/core 0.7.0-dev.150 → 0.7.0-dev.153

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.
@@ -4,6 +4,65 @@ const require_suggestion = require('./suggestion.cjs');
4
4
 
5
5
  //#region src/constructs.ts
6
6
  /**
7
+ * Extracts required (non-optional) usage terms from a usage array.
8
+ * @param usage The usage to extract required terms from
9
+ * @returns Usage containing only required (non-optional) terms
10
+ */
11
+ function extractRequiredUsage(usage) {
12
+ const required = [];
13
+ for (const term of usage) if (term.type === "optional") continue;
14
+ else if (term.type === "exclusive") {
15
+ const requiredBranches = term.terms.map((branch) => extractRequiredUsage(branch)).filter((branch) => branch.length > 0);
16
+ if (requiredBranches.length > 0) required.push({
17
+ type: "exclusive",
18
+ terms: requiredBranches
19
+ });
20
+ } else if (term.type === "multiple") {
21
+ if (term.min > 0) {
22
+ const requiredTerms = extractRequiredUsage(term.terms);
23
+ if (requiredTerms.length > 0) required.push({
24
+ type: "multiple",
25
+ terms: requiredTerms,
26
+ min: term.min
27
+ });
28
+ }
29
+ } else required.push(term);
30
+ return required;
31
+ }
32
+ /**
33
+ * Analyzes parsers to determine what types of inputs are expected.
34
+ * @param parsers The parsers being combined
35
+ * @returns Context about what types of inputs are expected
36
+ */
37
+ function analyzeNoMatchContext(parsers) {
38
+ const combinedUsage = [{
39
+ type: "exclusive",
40
+ terms: parsers.map((p) => p.usage)
41
+ }];
42
+ const requiredUsage = extractRequiredUsage(combinedUsage);
43
+ return {
44
+ hasOptions: require_usage.extractOptionNames(requiredUsage).size > 0,
45
+ hasCommands: require_usage.extractCommandNames(requiredUsage).size > 0,
46
+ hasArguments: require_usage.extractArgumentMetavars(requiredUsage).size > 0
47
+ };
48
+ }
49
+ /**
50
+ * Generates a contextual error message based on what types of inputs
51
+ * the parsers expect (options, commands, or arguments).
52
+ * @param context Context about what types of inputs are expected
53
+ * @returns An appropriate error message
54
+ */
55
+ function generateNoMatchError(context) {
56
+ const { hasOptions, hasCommands, hasArguments } = context;
57
+ if (hasArguments && !hasOptions && !hasCommands) return require_message.message`Missing required argument.`;
58
+ else if (hasCommands && !hasOptions && !hasArguments) return require_message.message`No matching command found.`;
59
+ else if (hasOptions && !hasCommands && !hasArguments) return require_message.message`No matching option found.`;
60
+ else if (hasCommands && hasOptions && !hasArguments) return require_message.message`No matching option or command found.`;
61
+ else if (hasArguments && hasOptions && !hasCommands) return require_message.message`No matching option or argument found.`;
62
+ else if (hasArguments && hasCommands && !hasOptions) return require_message.message`No matching command or argument found.`;
63
+ else return require_message.message`No matching option, command, or argument found.`;
64
+ }
65
+ /**
7
66
  * @since 0.5.0
8
67
  */
9
68
  function or(...args) {
@@ -16,6 +75,7 @@ function or(...args) {
16
75
  parsers = args;
17
76
  options = void 0;
18
77
  }
78
+ const noMatchContext = analyzeNoMatchContext(parsers);
19
79
  return {
20
80
  $valueType: [],
21
81
  $stateType: [],
@@ -26,10 +86,14 @@ function or(...args) {
26
86
  }],
27
87
  initialState: void 0,
28
88
  complete(state) {
29
- if (state == null) return {
30
- success: false,
31
- error: options?.errors?.noMatch ?? require_message.message`No matching option or command found.`
32
- };
89
+ if (state == null) {
90
+ const customNoMatch = options?.errors?.noMatch;
91
+ const error = customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
92
+ return {
93
+ success: false,
94
+ error
95
+ };
96
+ }
33
97
  const [i, result] = state;
34
98
  if (result.success) return parsers[i].complete(result.next.state);
35
99
  return {
@@ -40,7 +104,10 @@ function or(...args) {
40
104
  parse(context) {
41
105
  let error = {
42
106
  consumed: 0,
43
- error: context.buffer.length < 1 ? options?.errors?.noMatch ?? require_message.message`No matching option or command found.` : (() => {
107
+ error: context.buffer.length < 1 ? (() => {
108
+ const customNoMatch = options?.errors?.noMatch;
109
+ return customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
110
+ })() : (() => {
44
111
  const token = context.buffer[0];
45
112
  const defaultMsg = require_message.message`Unexpected option or subcommand: ${require_message.optionName(token)}.`;
46
113
  if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
@@ -151,6 +218,7 @@ function longestMatch(...args) {
151
218
  parsers = args;
152
219
  options = void 0;
153
220
  }
221
+ const noMatchContext = analyzeNoMatchContext(parsers);
154
222
  return {
155
223
  $valueType: [],
156
224
  $stateType: [],
@@ -161,10 +229,14 @@ function longestMatch(...args) {
161
229
  }],
162
230
  initialState: void 0,
163
231
  complete(state) {
164
- if (state == null) return {
165
- success: false,
166
- error: options?.errors?.noMatch ?? require_message.message`No matching option or command found.`
167
- };
232
+ if (state == null) {
233
+ const customNoMatch = options?.errors?.noMatch;
234
+ const error = customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
235
+ return {
236
+ success: false,
237
+ error
238
+ };
239
+ }
168
240
  const [i, result] = state;
169
241
  if (result.success) return parsers[i].complete(result.next.state);
170
242
  return {
@@ -176,7 +248,10 @@ function longestMatch(...args) {
176
248
  let bestMatch = null;
177
249
  let error = {
178
250
  consumed: 0,
179
- error: context.buffer.length < 1 ? options?.errors?.noMatch ?? require_message.message`No matching option or command found.` : (() => {
251
+ error: context.buffer.length < 1 ? (() => {
252
+ const customNoMatch = options?.errors?.noMatch;
253
+ return customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
254
+ })() : (() => {
180
255
  const token = context.buffer[0];
181
256
  const defaultMsg = require_message.message`Unexpected option or subcommand: ${require_message.optionName(token)}.`;
182
257
  if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
@@ -242,6 +317,7 @@ function longestMatch(...args) {
242
317
  },
243
318
  getDocFragments(state, _defaultValue) {
244
319
  let description;
320
+ let footer;
245
321
  let fragments;
246
322
  if (state.kind === "unavailable" || state.state == null) fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
247
323
  else {
@@ -252,12 +328,14 @@ function longestMatch(...args) {
252
328
  state: result.next.state
253
329
  });
254
330
  description = docResult.description;
331
+ footer = docResult.footer;
255
332
  fragments = docResult.fragments;
256
333
  } else fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
257
334
  }
258
335
  return {
259
336
  description,
260
- fragments
337
+ fragments,
338
+ footer
261
339
  };
262
340
  }
263
341
  };
@@ -275,6 +353,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
275
353
  }
276
354
  const parserPairs = Object.entries(parsers);
277
355
  parserPairs.sort(([_, parserA], [__, parserB]) => parserB.priority - parserA.priority);
356
+ const noMatchContext = analyzeNoMatchContext(Object.values(parsers));
278
357
  return {
279
358
  $valueType: [],
280
359
  $stateType: [],
@@ -305,7 +384,10 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
305
384
  if (customMessage) return typeof customMessage === "function" ? customMessage(token) : customMessage;
306
385
  const baseError = require_message.message`Unexpected option or argument: ${token}.`;
307
386
  return require_suggestion.createErrorWithSuggestions(baseError, token, context.usage, "both", options.errors?.suggestions);
308
- })() : options.errors?.endOfInput ?? require_message.message`Expected an option or argument, but got end of input.`
387
+ })() : (() => {
388
+ const customEndOfInput = options.errors?.endOfInput;
389
+ return customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext);
390
+ })()
309
391
  };
310
392
  let currentContext = context;
311
393
  let anySuccess = false;
@@ -13,6 +13,25 @@ interface OrOptions {
13
13
  */
14
14
  errors?: OrErrorOptions;
15
15
  }
16
+ /**
17
+ * Context information about what types of inputs are expected,
18
+ * used for generating contextual error messages.
19
+ * @since 0.9.0
20
+ */
21
+ interface NoMatchContext {
22
+ /**
23
+ * Whether any of the parsers expect options.
24
+ */
25
+ readonly hasOptions: boolean;
26
+ /**
27
+ * Whether any of the parsers expect commands.
28
+ */
29
+ readonly hasCommands: boolean;
30
+ /**
31
+ * Whether any of the parsers expect arguments.
32
+ */
33
+ readonly hasArguments: boolean;
34
+ }
16
35
  /**
17
36
  * Options for customizing error messages in the {@link or} parser.
18
37
  * @since 0.5.0
@@ -20,8 +39,27 @@ interface OrOptions {
20
39
  interface OrErrorOptions {
21
40
  /**
22
41
  * Custom error message when no parser matches.
42
+ * Can be a static message or a function that receives context about what
43
+ * types of inputs are expected, allowing for more precise error messages.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Static message (overrides all cases)
48
+ * { noMatch: message`Invalid input.` }
49
+ *
50
+ * // Dynamic message based on context (for i18n, etc.)
51
+ * {
52
+ * noMatch: ({ hasOptions, hasCommands, hasArguments }) => {
53
+ * if (hasArguments && !hasOptions && !hasCommands) {
54
+ * return message`인수가 필요합니다.`; // Korean: "Argument required"
55
+ * }
56
+ * // ... other cases
57
+ * }
58
+ * }
59
+ * ```
60
+ * @since 0.9.0 - Function form added
23
61
  */
24
- noMatch?: Message;
62
+ noMatch?: Message | ((context: NoMatchContext) => Message);
25
63
  /**
26
64
  * Custom error message for unexpected input.
27
65
  * Can be a static message or a function that receives the unexpected token.
@@ -306,8 +344,27 @@ interface LongestMatchOptions {
306
344
  interface LongestMatchErrorOptions {
307
345
  /**
308
346
  * Custom error message when no parser matches.
347
+ * Can be a static message or a function that receives context about what
348
+ * types of inputs are expected, allowing for more precise error messages.
349
+ *
350
+ * @example
351
+ * ```typescript
352
+ * // Static message (overrides all cases)
353
+ * { noMatch: message`Invalid input.` }
354
+ *
355
+ * // Dynamic message based on context (for i18n, etc.)
356
+ * {
357
+ * noMatch: ({ hasOptions, hasCommands, hasArguments }) => {
358
+ * if (hasArguments && !hasOptions && !hasCommands) {
359
+ * return message`引数が必要です。`; // Japanese: "Argument required"
360
+ * }
361
+ * // ... other cases
362
+ * }
363
+ * }
364
+ * ```
365
+ * @since 0.9.0 - Function form added
309
366
  */
310
- noMatch?: Message;
367
+ noMatch?: Message | ((context: NoMatchContext) => Message);
311
368
  /**
312
369
  * Custom error message for unexpected input.
313
370
  * Can be a static message or a function that receives the unexpected token.
@@ -447,8 +504,27 @@ interface ObjectErrorOptions {
447
504
  readonly unexpectedInput?: Message | ((token: string) => Message);
448
505
  /**
449
506
  * Error message when end of input is reached unexpectedly.
507
+ * Can be a static message or a function that receives context about what
508
+ * types of inputs are expected, allowing for more precise error messages.
509
+ *
510
+ * @example
511
+ * ```typescript
512
+ * // Static message (overrides all cases)
513
+ * { endOfInput: message`Invalid input.` }
514
+ *
515
+ * // Dynamic message based on context (for i18n, etc.)
516
+ * {
517
+ * endOfInput: ({ hasOptions, hasCommands, hasArguments }) => {
518
+ * if (hasArguments && !hasOptions && !hasCommands) {
519
+ * return message`Argument manquant.`; // French: "Missing argument"
520
+ * }
521
+ * // ... other cases
522
+ * }
523
+ * }
524
+ * ```
525
+ * @since 0.9.0 - Function form added
450
526
  */
451
- readonly endOfInput?: Message;
527
+ readonly endOfInput?: Message | ((context: NoMatchContext) => Message);
452
528
  /**
453
529
  * Custom function to format suggestion messages.
454
530
  * If provided, this will be used instead of the default "Did you mean?"
@@ -1183,4 +1259,4 @@ declare function concat<TA extends readonly unknown[], TB extends readonly unkno
1183
1259
  */
1184
1260
  declare function group<TValue, TState>(label: string, parser: Parser<TValue, TState>): Parser<TValue, TState>;
1185
1261
  //#endregion
1186
- export { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple };
1262
+ export { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple };
@@ -13,6 +13,25 @@ interface OrOptions {
13
13
  */
14
14
  errors?: OrErrorOptions;
15
15
  }
16
+ /**
17
+ * Context information about what types of inputs are expected,
18
+ * used for generating contextual error messages.
19
+ * @since 0.9.0
20
+ */
21
+ interface NoMatchContext {
22
+ /**
23
+ * Whether any of the parsers expect options.
24
+ */
25
+ readonly hasOptions: boolean;
26
+ /**
27
+ * Whether any of the parsers expect commands.
28
+ */
29
+ readonly hasCommands: boolean;
30
+ /**
31
+ * Whether any of the parsers expect arguments.
32
+ */
33
+ readonly hasArguments: boolean;
34
+ }
16
35
  /**
17
36
  * Options for customizing error messages in the {@link or} parser.
18
37
  * @since 0.5.0
@@ -20,8 +39,27 @@ interface OrOptions {
20
39
  interface OrErrorOptions {
21
40
  /**
22
41
  * Custom error message when no parser matches.
42
+ * Can be a static message or a function that receives context about what
43
+ * types of inputs are expected, allowing for more precise error messages.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Static message (overrides all cases)
48
+ * { noMatch: message`Invalid input.` }
49
+ *
50
+ * // Dynamic message based on context (for i18n, etc.)
51
+ * {
52
+ * noMatch: ({ hasOptions, hasCommands, hasArguments }) => {
53
+ * if (hasArguments && !hasOptions && !hasCommands) {
54
+ * return message`인수가 필요합니다.`; // Korean: "Argument required"
55
+ * }
56
+ * // ... other cases
57
+ * }
58
+ * }
59
+ * ```
60
+ * @since 0.9.0 - Function form added
23
61
  */
24
- noMatch?: Message;
62
+ noMatch?: Message | ((context: NoMatchContext) => Message);
25
63
  /**
26
64
  * Custom error message for unexpected input.
27
65
  * Can be a static message or a function that receives the unexpected token.
@@ -306,8 +344,27 @@ interface LongestMatchOptions {
306
344
  interface LongestMatchErrorOptions {
307
345
  /**
308
346
  * Custom error message when no parser matches.
347
+ * Can be a static message or a function that receives context about what
348
+ * types of inputs are expected, allowing for more precise error messages.
349
+ *
350
+ * @example
351
+ * ```typescript
352
+ * // Static message (overrides all cases)
353
+ * { noMatch: message`Invalid input.` }
354
+ *
355
+ * // Dynamic message based on context (for i18n, etc.)
356
+ * {
357
+ * noMatch: ({ hasOptions, hasCommands, hasArguments }) => {
358
+ * if (hasArguments && !hasOptions && !hasCommands) {
359
+ * return message`引数が必要です。`; // Japanese: "Argument required"
360
+ * }
361
+ * // ... other cases
362
+ * }
363
+ * }
364
+ * ```
365
+ * @since 0.9.0 - Function form added
309
366
  */
310
- noMatch?: Message;
367
+ noMatch?: Message | ((context: NoMatchContext) => Message);
311
368
  /**
312
369
  * Custom error message for unexpected input.
313
370
  * Can be a static message or a function that receives the unexpected token.
@@ -447,8 +504,27 @@ interface ObjectErrorOptions {
447
504
  readonly unexpectedInput?: Message | ((token: string) => Message);
448
505
  /**
449
506
  * Error message when end of input is reached unexpectedly.
507
+ * Can be a static message or a function that receives context about what
508
+ * types of inputs are expected, allowing for more precise error messages.
509
+ *
510
+ * @example
511
+ * ```typescript
512
+ * // Static message (overrides all cases)
513
+ * { endOfInput: message`Invalid input.` }
514
+ *
515
+ * // Dynamic message based on context (for i18n, etc.)
516
+ * {
517
+ * endOfInput: ({ hasOptions, hasCommands, hasArguments }) => {
518
+ * if (hasArguments && !hasOptions && !hasCommands) {
519
+ * return message`Argument manquant.`; // French: "Missing argument"
520
+ * }
521
+ * // ... other cases
522
+ * }
523
+ * }
524
+ * ```
525
+ * @since 0.9.0 - Function form added
450
526
  */
451
- readonly endOfInput?: Message;
527
+ readonly endOfInput?: Message | ((context: NoMatchContext) => Message);
452
528
  /**
453
529
  * Custom function to format suggestion messages.
454
530
  * If provided, this will be used instead of the default "Did you mean?"
@@ -1183,4 +1259,4 @@ declare function concat<TA extends readonly unknown[], TB extends readonly unkno
1183
1259
  */
1184
1260
  declare function group<TValue, TState>(label: string, parser: Parser<TValue, TState>): Parser<TValue, TState>;
1185
1261
  //#endregion
1186
- export { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple };
1262
+ export { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple };
@@ -1,9 +1,68 @@
1
1
  import { message, optionName, values } from "./message.js";
2
- import { extractOptionNames } from "./usage.js";
2
+ import { extractArgumentMetavars, extractCommandNames, extractOptionNames } from "./usage.js";
3
3
  import { createErrorWithSuggestions } from "./suggestion.js";
4
4
 
5
5
  //#region src/constructs.ts
6
6
  /**
7
+ * Extracts required (non-optional) usage terms from a usage array.
8
+ * @param usage The usage to extract required terms from
9
+ * @returns Usage containing only required (non-optional) terms
10
+ */
11
+ function extractRequiredUsage(usage) {
12
+ const required = [];
13
+ for (const term of usage) if (term.type === "optional") continue;
14
+ else if (term.type === "exclusive") {
15
+ const requiredBranches = term.terms.map((branch) => extractRequiredUsage(branch)).filter((branch) => branch.length > 0);
16
+ if (requiredBranches.length > 0) required.push({
17
+ type: "exclusive",
18
+ terms: requiredBranches
19
+ });
20
+ } else if (term.type === "multiple") {
21
+ if (term.min > 0) {
22
+ const requiredTerms = extractRequiredUsage(term.terms);
23
+ if (requiredTerms.length > 0) required.push({
24
+ type: "multiple",
25
+ terms: requiredTerms,
26
+ min: term.min
27
+ });
28
+ }
29
+ } else required.push(term);
30
+ return required;
31
+ }
32
+ /**
33
+ * Analyzes parsers to determine what types of inputs are expected.
34
+ * @param parsers The parsers being combined
35
+ * @returns Context about what types of inputs are expected
36
+ */
37
+ function analyzeNoMatchContext(parsers) {
38
+ const combinedUsage = [{
39
+ type: "exclusive",
40
+ terms: parsers.map((p) => p.usage)
41
+ }];
42
+ const requiredUsage = extractRequiredUsage(combinedUsage);
43
+ return {
44
+ hasOptions: extractOptionNames(requiredUsage).size > 0,
45
+ hasCommands: extractCommandNames(requiredUsage).size > 0,
46
+ hasArguments: extractArgumentMetavars(requiredUsage).size > 0
47
+ };
48
+ }
49
+ /**
50
+ * Generates a contextual error message based on what types of inputs
51
+ * the parsers expect (options, commands, or arguments).
52
+ * @param context Context about what types of inputs are expected
53
+ * @returns An appropriate error message
54
+ */
55
+ function generateNoMatchError(context) {
56
+ const { hasOptions, hasCommands, hasArguments } = context;
57
+ if (hasArguments && !hasOptions && !hasCommands) return message`Missing required argument.`;
58
+ else if (hasCommands && !hasOptions && !hasArguments) return message`No matching command found.`;
59
+ else if (hasOptions && !hasCommands && !hasArguments) return message`No matching option found.`;
60
+ else if (hasCommands && hasOptions && !hasArguments) return message`No matching option or command found.`;
61
+ else if (hasArguments && hasOptions && !hasCommands) return message`No matching option or argument found.`;
62
+ else if (hasArguments && hasCommands && !hasOptions) return message`No matching command or argument found.`;
63
+ else return message`No matching option, command, or argument found.`;
64
+ }
65
+ /**
7
66
  * @since 0.5.0
8
67
  */
9
68
  function or(...args) {
@@ -16,6 +75,7 @@ function or(...args) {
16
75
  parsers = args;
17
76
  options = void 0;
18
77
  }
78
+ const noMatchContext = analyzeNoMatchContext(parsers);
19
79
  return {
20
80
  $valueType: [],
21
81
  $stateType: [],
@@ -26,10 +86,14 @@ function or(...args) {
26
86
  }],
27
87
  initialState: void 0,
28
88
  complete(state) {
29
- if (state == null) return {
30
- success: false,
31
- error: options?.errors?.noMatch ?? message`No matching option or command found.`
32
- };
89
+ if (state == null) {
90
+ const customNoMatch = options?.errors?.noMatch;
91
+ const error = customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
92
+ return {
93
+ success: false,
94
+ error
95
+ };
96
+ }
33
97
  const [i, result] = state;
34
98
  if (result.success) return parsers[i].complete(result.next.state);
35
99
  return {
@@ -40,7 +104,10 @@ function or(...args) {
40
104
  parse(context) {
41
105
  let error = {
42
106
  consumed: 0,
43
- error: context.buffer.length < 1 ? options?.errors?.noMatch ?? message`No matching option or command found.` : (() => {
107
+ error: context.buffer.length < 1 ? (() => {
108
+ const customNoMatch = options?.errors?.noMatch;
109
+ return customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
110
+ })() : (() => {
44
111
  const token = context.buffer[0];
45
112
  const defaultMsg = message`Unexpected option or subcommand: ${optionName(token)}.`;
46
113
  if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
@@ -151,6 +218,7 @@ function longestMatch(...args) {
151
218
  parsers = args;
152
219
  options = void 0;
153
220
  }
221
+ const noMatchContext = analyzeNoMatchContext(parsers);
154
222
  return {
155
223
  $valueType: [],
156
224
  $stateType: [],
@@ -161,10 +229,14 @@ function longestMatch(...args) {
161
229
  }],
162
230
  initialState: void 0,
163
231
  complete(state) {
164
- if (state == null) return {
165
- success: false,
166
- error: options?.errors?.noMatch ?? message`No matching option or command found.`
167
- };
232
+ if (state == null) {
233
+ const customNoMatch = options?.errors?.noMatch;
234
+ const error = customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
235
+ return {
236
+ success: false,
237
+ error
238
+ };
239
+ }
168
240
  const [i, result] = state;
169
241
  if (result.success) return parsers[i].complete(result.next.state);
170
242
  return {
@@ -176,7 +248,10 @@ function longestMatch(...args) {
176
248
  let bestMatch = null;
177
249
  let error = {
178
250
  consumed: 0,
179
- error: context.buffer.length < 1 ? options?.errors?.noMatch ?? message`No matching option or command found.` : (() => {
251
+ error: context.buffer.length < 1 ? (() => {
252
+ const customNoMatch = options?.errors?.noMatch;
253
+ return customNoMatch ? typeof customNoMatch === "function" ? customNoMatch(noMatchContext) : customNoMatch : generateNoMatchError(noMatchContext);
254
+ })() : (() => {
180
255
  const token = context.buffer[0];
181
256
  const defaultMsg = message`Unexpected option or subcommand: ${optionName(token)}.`;
182
257
  if (options?.errors?.unexpectedInput != null) return typeof options.errors.unexpectedInput === "function" ? options.errors.unexpectedInput(token) : options.errors.unexpectedInput;
@@ -242,6 +317,7 @@ function longestMatch(...args) {
242
317
  },
243
318
  getDocFragments(state, _defaultValue) {
244
319
  let description;
320
+ let footer;
245
321
  let fragments;
246
322
  if (state.kind === "unavailable" || state.state == null) fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
247
323
  else {
@@ -252,12 +328,14 @@ function longestMatch(...args) {
252
328
  state: result.next.state
253
329
  });
254
330
  description = docResult.description;
331
+ footer = docResult.footer;
255
332
  fragments = docResult.fragments;
256
333
  } else fragments = parsers.flatMap((p) => p.getDocFragments({ kind: "unavailable" }).fragments);
257
334
  }
258
335
  return {
259
336
  description,
260
- fragments
337
+ fragments,
338
+ footer
261
339
  };
262
340
  }
263
341
  };
@@ -275,6 +353,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
275
353
  }
276
354
  const parserPairs = Object.entries(parsers);
277
355
  parserPairs.sort(([_, parserA], [__, parserB]) => parserB.priority - parserA.priority);
356
+ const noMatchContext = analyzeNoMatchContext(Object.values(parsers));
278
357
  return {
279
358
  $valueType: [],
280
359
  $stateType: [],
@@ -305,7 +384,10 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
305
384
  if (customMessage) return typeof customMessage === "function" ? customMessage(token) : customMessage;
306
385
  const baseError = message`Unexpected option or argument: ${token}.`;
307
386
  return createErrorWithSuggestions(baseError, token, context.usage, "both", options.errors?.suggestions);
308
- })() : options.errors?.endOfInput ?? message`Expected an option or argument, but got end of input.`
387
+ })() : (() => {
388
+ const customEndOfInput = options.errors?.endOfInput;
389
+ return customEndOfInput ? typeof customEndOfInput === "function" ? customEndOfInput(noMatchContext) : customEndOfInput : generateNoMatchError(noMatchContext);
390
+ })()
309
391
  };
310
392
  let currentContext = context;
311
393
  let anySuccess = false;
package/dist/facade.cjs CHANGED
@@ -54,27 +54,41 @@ function createVersionParser(mode) {
54
54
  /**
55
55
  * Creates completion parsers based on the specified mode.
56
56
  */
57
- function createCompletionParser(mode, programName, availableShells) {
57
+ function createCompletionParser(mode, programName, availableShells, name = "both") {
58
58
  const shellList = [];
59
59
  for (const shell in availableShells) {
60
60
  if (shellList.length > 0) shellList.push(require_message.text(", "));
61
61
  shellList.push(require_message.value(shell));
62
62
  }
63
- const completionCommand = require_primitives.command("completion", require_constructs.object({
63
+ const completionInner = require_constructs.object({
64
64
  shell: require_modifiers.optional(require_primitives.argument(require_valueparser.string({ metavar: "SHELL" }), { description: require_message.message`Shell type (${shellList}). Generate completion script when used alone, or provide completions when followed by arguments.` })),
65
65
  args: require_modifiers.multiple(require_primitives.argument(require_valueparser.string({ metavar: "ARG" }), { description: require_message.message`Command line arguments for completion suggestions (used by shell integration; you usually don't need to provide this).` }))
66
- }), {
66
+ });
67
+ const commandName = name === "plural" ? "completions" : "completion";
68
+ const completionCommandConfig = {
67
69
  brief: require_message.message`Generate shell completion script or provide completions.`,
68
70
  description: require_message.message`Generate shell completion script or provide completions.`,
69
71
  footer: require_message.message`Examples:
70
- Bash: ${require_message.commandLine(`eval "$(${programName} completion bash)"`)}
71
- zsh: ${require_message.commandLine(`eval "$(${programName} completion zsh)"`)}
72
- fish: ${require_message.commandLine(`eval "$(${programName} completion fish)"`)}
73
- PowerShell: ${require_message.commandLine(`${programName} completion pwsh > ${programName}-completion.ps1; . ./${programName}-completion.ps1`)}
74
- Nushell: ${require_message.commandLine(`${programName} completion nu | save ${programName}-completion.nu; source ./${programName}-completion.nu`)}
72
+ Bash: ${require_message.commandLine(`eval "$(${programName} ${commandName} bash)"`)}
73
+ zsh: ${require_message.commandLine(`eval "$(${programName} ${commandName} zsh)"`)}
74
+ fish: ${require_message.commandLine(`eval "$(${programName} ${commandName} fish)"`)}
75
+ PowerShell: ${require_message.commandLine(`${programName} ${commandName} pwsh > ${programName}-completion.ps1; . ./${programName}-completion.ps1`)}
76
+ Nushell: ${require_message.commandLine(`${programName} ${commandName} nu | save ${programName}-completion.nu; source ./${programName}-completion.nu`)}
75
77
  `
76
- });
77
- const completionOption = require_primitives.option("--completion", require_valueparser.string({ metavar: "SHELL" }), { description: require_message.message`Generate shell completion script.` });
78
+ };
79
+ const completionCommands = [];
80
+ if (name === "singular" || name === "both") completionCommands.push(require_primitives.command("completion", completionInner, completionCommandConfig));
81
+ if (name === "plural" || name === "both") completionCommands.push(require_primitives.command("completions", completionInner, completionCommandConfig));
82
+ const completionCommand = require_constructs.longestMatch(...completionCommands);
83
+ const completionOptionNames = [];
84
+ if (name === "singular" || name === "both") completionOptionNames.push("--completion");
85
+ if (name === "plural" || name === "both") completionOptionNames.push("--completions");
86
+ const completionOptionArgs = [
87
+ ...completionOptionNames,
88
+ require_valueparser.string({ metavar: "SHELL" }),
89
+ { description: require_message.message`Generate shell completion script.` }
90
+ ];
91
+ const completionOption = require_primitives.option(...completionOptionArgs);
78
92
  switch (mode) {
79
93
  case "command": return {
80
94
  completionCommand,
@@ -412,6 +426,7 @@ function run(parser, programName, args, options = {}) {
412
426
  const versionValue = options.version?.value ?? "";
413
427
  const onVersion = options.version?.onShow ?? (() => ({}));
414
428
  const completionMode = options.completion?.mode ?? "both";
429
+ const completionName = options.completion?.name ?? "both";
415
430
  const onCompletion = options.completion?.onShow ?? (() => ({}));
416
431
  const defaultShells = {
417
432
  bash: require_completion.bash,
@@ -438,20 +453,26 @@ function run(parser, programName, args, options = {}) {
438
453
  const completionParsers = completion === "none" ? {
439
454
  completionCommand: null,
440
455
  completionOption: null
441
- } : createCompletionParser(completion, programName, availableShells);
456
+ } : createCompletionParser(completion, programName, availableShells, completionName);
442
457
  if (options.completion) {
443
458
  const hasHelpOption = args.includes("--help");
444
- if ((completionMode === "command" || completionMode === "both") && args.length >= 1 && args[0] === "completion" && !hasHelpOption) return handleCompletion(args.slice(1), programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
459
+ if ((completionMode === "command" || completionMode === "both") && args.length >= 1 && ((completionName === "singular" || completionName === "both" ? args[0] === "completion" : false) || (completionName === "plural" || completionName === "both" ? args[0] === "completions" : false)) && !hasHelpOption) return handleCompletion(args.slice(1), programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
445
460
  if (completionMode === "option" || completionMode === "both") for (let i = 0; i < args.length; i++) {
446
461
  const arg = args[i];
447
- if (arg.startsWith("--completion=")) {
448
- const shell = arg.slice(13);
462
+ const singularMatch = completionName === "singular" || completionName === "both" ? arg.startsWith("--completion=") : false;
463
+ const pluralMatch = completionName === "plural" || completionName === "both" ? arg.startsWith("--completions=") : false;
464
+ if (singularMatch || pluralMatch) {
465
+ const shell = arg.slice(arg.indexOf("=") + 1);
449
466
  const completionArgs = args.slice(i + 1);
450
467
  return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
451
- } else if (arg === "--completion" && i + 1 < args.length) {
452
- const shell = args[i + 1];
453
- const completionArgs = args.slice(i + 2);
454
- return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
468
+ } else {
469
+ const singularMatchExact = completionName === "singular" || completionName === "both" ? arg === "--completion" : false;
470
+ const pluralMatchExact = completionName === "plural" || completionName === "both" ? arg === "--completions" : false;
471
+ if ((singularMatchExact || pluralMatchExact) && i + 1 < args.length) {
472
+ const shell = args[i + 1];
473
+ const completionArgs = args.slice(i + 2);
474
+ return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
475
+ }
455
476
  }
456
477
  }
457
478
  }
@@ -474,7 +495,7 @@ function run(parser, programName, args, options = {}) {
474
495
  const versionAsCommand = version === "command" || version === "both";
475
496
  const completionAsCommand = completion === "command" || completion === "both";
476
497
  const requestedCommand = classified.commands[0];
477
- if (requestedCommand === "completion" && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
498
+ if ((requestedCommand === "completion" || requestedCommand === "completions") && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
478
499
  else if (requestedCommand === "help" && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
479
500
  else if (requestedCommand === "version" && versionAsCommand && versionParsers.versionCommand) helpGeneratorParser = versionParsers.versionCommand;
480
501
  else {
@@ -494,7 +515,7 @@ function run(parser, programName, args, options = {}) {
494
515
  }
495
516
  const doc = require_parser.getDocPage(helpGeneratorParser, classified.commands);
496
517
  if (doc != null) {
497
- const isMetaCommandHelp = requestedCommand === "completion" || requestedCommand === "help" || requestedCommand === "version";
518
+ const isMetaCommandHelp = (completionName === "singular" || completionName === "both" ? requestedCommand === "completion" : false) || (completionName === "plural" || completionName === "both" ? requestedCommand === "completions" : false) || requestedCommand === "help" || requestedCommand === "version";
498
519
  const augmentedDoc = {
499
520
  ...doc,
500
521
  brief: !isMetaCommandHelp ? brief ?? doc.brief : doc.brief,
package/dist/facade.d.cts CHANGED
@@ -104,6 +104,16 @@ interface RunOptions<THelp, TError> {
104
104
  * @default `"both"`
105
105
  */
106
106
  readonly mode?: "command" | "option" | "both";
107
+ /**
108
+ * Determines whether to use singular or plural naming for completion command/option.
109
+ *
110
+ * - `"singular"`: Use `completion` / `--completion`
111
+ * - `"plural"`: Use `completions` / `--completions`
112
+ * - `"both"`: Use both singular and plural forms
113
+ *
114
+ * @default `"both"`
115
+ */
116
+ readonly name?: "singular" | "plural" | "both";
107
117
  /**
108
118
  * Available shell completions. By default, includes `bash`, `fish`, `nu`,
109
119
  * `pwsh`, and `zsh`. You can provide additional custom shell completions or
package/dist/facade.d.ts CHANGED
@@ -104,6 +104,16 @@ interface RunOptions<THelp, TError> {
104
104
  * @default `"both"`
105
105
  */
106
106
  readonly mode?: "command" | "option" | "both";
107
+ /**
108
+ * Determines whether to use singular or plural naming for completion command/option.
109
+ *
110
+ * - `"singular"`: Use `completion` / `--completion`
111
+ * - `"plural"`: Use `completions` / `--completions`
112
+ * - `"both"`: Use both singular and plural forms
113
+ *
114
+ * @default `"both"`
115
+ */
116
+ readonly name?: "singular" | "plural" | "both";
107
117
  /**
108
118
  * Available shell completions. By default, includes `bash`, `fish`, `nu`,
109
119
  * `pwsh`, and `zsh`. You can provide additional custom shell completions or
package/dist/facade.js CHANGED
@@ -54,27 +54,41 @@ function createVersionParser(mode) {
54
54
  /**
55
55
  * Creates completion parsers based on the specified mode.
56
56
  */
57
- function createCompletionParser(mode, programName, availableShells) {
57
+ function createCompletionParser(mode, programName, availableShells, name = "both") {
58
58
  const shellList = [];
59
59
  for (const shell in availableShells) {
60
60
  if (shellList.length > 0) shellList.push(text(", "));
61
61
  shellList.push(value(shell));
62
62
  }
63
- const completionCommand = command("completion", object({
63
+ const completionInner = object({
64
64
  shell: optional(argument(string({ metavar: "SHELL" }), { description: message`Shell type (${shellList}). Generate completion script when used alone, or provide completions when followed by arguments.` })),
65
65
  args: multiple(argument(string({ metavar: "ARG" }), { description: message`Command line arguments for completion suggestions (used by shell integration; you usually don't need to provide this).` }))
66
- }), {
66
+ });
67
+ const commandName = name === "plural" ? "completions" : "completion";
68
+ const completionCommandConfig = {
67
69
  brief: message`Generate shell completion script or provide completions.`,
68
70
  description: message`Generate shell completion script or provide completions.`,
69
71
  footer: message`Examples:
70
- Bash: ${commandLine(`eval "$(${programName} completion bash)"`)}
71
- zsh: ${commandLine(`eval "$(${programName} completion zsh)"`)}
72
- fish: ${commandLine(`eval "$(${programName} completion fish)"`)}
73
- PowerShell: ${commandLine(`${programName} completion pwsh > ${programName}-completion.ps1; . ./${programName}-completion.ps1`)}
74
- Nushell: ${commandLine(`${programName} completion nu | save ${programName}-completion.nu; source ./${programName}-completion.nu`)}
72
+ Bash: ${commandLine(`eval "$(${programName} ${commandName} bash)"`)}
73
+ zsh: ${commandLine(`eval "$(${programName} ${commandName} zsh)"`)}
74
+ fish: ${commandLine(`eval "$(${programName} ${commandName} fish)"`)}
75
+ PowerShell: ${commandLine(`${programName} ${commandName} pwsh > ${programName}-completion.ps1; . ./${programName}-completion.ps1`)}
76
+ Nushell: ${commandLine(`${programName} ${commandName} nu | save ${programName}-completion.nu; source ./${programName}-completion.nu`)}
75
77
  `
76
- });
77
- const completionOption = option("--completion", string({ metavar: "SHELL" }), { description: message`Generate shell completion script.` });
78
+ };
79
+ const completionCommands = [];
80
+ if (name === "singular" || name === "both") completionCommands.push(command("completion", completionInner, completionCommandConfig));
81
+ if (name === "plural" || name === "both") completionCommands.push(command("completions", completionInner, completionCommandConfig));
82
+ const completionCommand = longestMatch(...completionCommands);
83
+ const completionOptionNames = [];
84
+ if (name === "singular" || name === "both") completionOptionNames.push("--completion");
85
+ if (name === "plural" || name === "both") completionOptionNames.push("--completions");
86
+ const completionOptionArgs = [
87
+ ...completionOptionNames,
88
+ string({ metavar: "SHELL" }),
89
+ { description: message`Generate shell completion script.` }
90
+ ];
91
+ const completionOption = option(...completionOptionArgs);
78
92
  switch (mode) {
79
93
  case "command": return {
80
94
  completionCommand,
@@ -412,6 +426,7 @@ function run(parser, programName, args, options = {}) {
412
426
  const versionValue = options.version?.value ?? "";
413
427
  const onVersion = options.version?.onShow ?? (() => ({}));
414
428
  const completionMode = options.completion?.mode ?? "both";
429
+ const completionName = options.completion?.name ?? "both";
415
430
  const onCompletion = options.completion?.onShow ?? (() => ({}));
416
431
  const defaultShells = {
417
432
  bash,
@@ -438,20 +453,26 @@ function run(parser, programName, args, options = {}) {
438
453
  const completionParsers = completion === "none" ? {
439
454
  completionCommand: null,
440
455
  completionOption: null
441
- } : createCompletionParser(completion, programName, availableShells);
456
+ } : createCompletionParser(completion, programName, availableShells, completionName);
442
457
  if (options.completion) {
443
458
  const hasHelpOption = args.includes("--help");
444
- if ((completionMode === "command" || completionMode === "both") && args.length >= 1 && args[0] === "completion" && !hasHelpOption) return handleCompletion(args.slice(1), programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
459
+ if ((completionMode === "command" || completionMode === "both") && args.length >= 1 && ((completionName === "singular" || completionName === "both" ? args[0] === "completion" : false) || (completionName === "plural" || completionName === "both" ? args[0] === "completions" : false)) && !hasHelpOption) return handleCompletion(args.slice(1), programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
445
460
  if (completionMode === "option" || completionMode === "both") for (let i = 0; i < args.length; i++) {
446
461
  const arg = args[i];
447
- if (arg.startsWith("--completion=")) {
448
- const shell = arg.slice(13);
462
+ const singularMatch = completionName === "singular" || completionName === "both" ? arg.startsWith("--completion=") : false;
463
+ const pluralMatch = completionName === "plural" || completionName === "both" ? arg.startsWith("--completions=") : false;
464
+ if (singularMatch || pluralMatch) {
465
+ const shell = arg.slice(arg.indexOf("=") + 1);
449
466
  const completionArgs = args.slice(i + 1);
450
467
  return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
451
- } else if (arg === "--completion" && i + 1 < args.length) {
452
- const shell = args[i + 1];
453
- const completionArgs = args.slice(i + 2);
454
- return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
468
+ } else {
469
+ const singularMatchExact = completionName === "singular" || completionName === "both" ? arg === "--completion" : false;
470
+ const pluralMatchExact = completionName === "plural" || completionName === "both" ? arg === "--completions" : false;
471
+ if ((singularMatchExact || pluralMatchExact) && i + 1 < args.length) {
472
+ const shell = args[i + 1];
473
+ const completionArgs = args.slice(i + 2);
474
+ return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth);
475
+ }
455
476
  }
456
477
  }
457
478
  }
@@ -474,7 +495,7 @@ function run(parser, programName, args, options = {}) {
474
495
  const versionAsCommand = version === "command" || version === "both";
475
496
  const completionAsCommand = completion === "command" || completion === "both";
476
497
  const requestedCommand = classified.commands[0];
477
- if (requestedCommand === "completion" && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
498
+ if ((requestedCommand === "completion" || requestedCommand === "completions") && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
478
499
  else if (requestedCommand === "help" && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
479
500
  else if (requestedCommand === "version" && versionAsCommand && versionParsers.versionCommand) helpGeneratorParser = versionParsers.versionCommand;
480
501
  else {
@@ -494,7 +515,7 @@ function run(parser, programName, args, options = {}) {
494
515
  }
495
516
  const doc = getDocPage(helpGeneratorParser, classified.commands);
496
517
  if (doc != null) {
497
- const isMetaCommandHelp = requestedCommand === "completion" || requestedCommand === "help" || requestedCommand === "version";
518
+ const isMetaCommandHelp = (completionName === "singular" || completionName === "both" ? requestedCommand === "completion" : false) || (completionName === "plural" || completionName === "both" ? requestedCommand === "completions" : false) || requestedCommand === "help" || requestedCommand === "version";
498
519
  const augmentedDoc = {
499
520
  ...doc,
500
521
  brief: !isMetaCommandHelp ? brief ?? doc.brief : doc.brief,
package/dist/index.cjs CHANGED
@@ -19,6 +19,7 @@ exports.commandLine = require_message.commandLine;
19
19
  exports.concat = require_constructs.concat;
20
20
  exports.constant = require_primitives.constant;
21
21
  exports.envVar = require_message.envVar;
22
+ exports.extractArgumentMetavars = require_usage.extractArgumentMetavars;
22
23
  exports.extractCommandNames = require_usage.extractCommandNames;
23
24
  exports.extractOptionNames = require_usage.extractOptionNames;
24
25
  exports.fish = require_completion.fish;
package/dist/index.d.cts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { Message, MessageFormatOptions, MessageTerm, commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.cjs";
2
- import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.cjs";
2
+ import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.cjs";
3
3
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowDefaultOptions, formatDocPage } from "./doc.cjs";
4
4
  import { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, isValueParser, locale, string, url, uuid } from "./valueparser.cjs";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.cjs";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.cjs";
7
7
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.cjs";
8
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
8
+ import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
9
9
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.cjs";
10
10
  import { RunError, RunOptions, run } from "./facade.cjs";
11
- export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, RunError, RunOptions, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, constant, envVar, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
11
+ export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, RunError, RunOptions, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, constant, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { Message, MessageFormatOptions, MessageTerm, commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.js";
2
- import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.js";
2
+ import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.js";
3
3
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowDefaultOptions, formatDocPage } from "./doc.js";
4
4
  import { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, isValueParser, locale, string, url, uuid } from "./valueparser.js";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.js";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.js";
7
7
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.js";
8
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
8
+ import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
9
9
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.js";
10
10
  import { RunError, RunOptions, run } from "./facade.js";
11
- export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, RunError, RunOptions, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, constant, envVar, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
11
+ export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, RunError, RunOptions, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, constant, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.js";
2
- import { extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.js";
2
+ import { extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.js";
3
3
  import { concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
4
4
  import { formatDocPage } from "./doc.js";
5
5
  import { bash, fish, nu, pwsh, zsh } from "./completion.js";
@@ -9,4 +9,4 @@ import { argument, command, constant, flag, option } from "./primitives.js";
9
9
  import { getDocPage, parse, suggest } from "./parser.js";
10
10
  import { RunError, run } from "./facade.js";
11
11
 
12
- export { RunError, WithDefaultError, argument, bash, choice, command, commandLine, concat, constant, envVar, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
12
+ export { RunError, WithDefaultError, argument, bash, choice, command, commandLine, concat, constant, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
package/dist/parser.d.cts CHANGED
@@ -4,7 +4,7 @@ import { DocFragments, DocPage } from "./doc.cjs";
4
4
  import { ValueParserResult } from "./valueparser.cjs";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.cjs";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.cjs";
7
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
7
+ import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
8
8
 
9
9
  //#region src/parser.d.ts
10
10
 
@@ -323,4 +323,4 @@ declare function suggest<T>(parser: Parser<T, unknown>, args: readonly [string,
323
323
  */
324
324
  declare function getDocPage(parser: Parser<unknown, unknown>, args?: readonly string[]): DocPage | undefined;
325
325
  //#endregion
326
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
326
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
package/dist/parser.d.ts CHANGED
@@ -4,7 +4,7 @@ import { DocFragments, DocPage } from "./doc.js";
4
4
  import { ValueParserResult } from "./valueparser.js";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.js";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.js";
7
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
7
+ import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
8
8
 
9
9
  //#region src/parser.d.ts
10
10
 
@@ -323,4 +323,4 @@ declare function suggest<T>(parser: Parser<T, unknown>, args: readonly [string,
323
323
  */
324
324
  declare function getDocPage(parser: Parser<unknown, unknown>, args?: readonly string[]): DocPage | undefined;
325
325
  //#endregion
326
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
326
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
package/dist/usage.cjs CHANGED
@@ -63,6 +63,38 @@ function extractCommandNames(usage) {
63
63
  return names;
64
64
  }
65
65
  /**
66
+ * Extracts all argument metavars from a Usage array.
67
+ *
68
+ * This function recursively traverses the usage structure and collects
69
+ * all argument metavariable names, similar to {@link extractOptionNames}
70
+ * and {@link extractCommandNames}.
71
+ *
72
+ * @param usage The usage structure to extract argument metavars from.
73
+ * @returns A Set of all argument metavars found in the usage structure.
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const usage: Usage = [
78
+ * { type: "argument", metavar: "FILE" },
79
+ * { type: "argument", metavar: "OUTPUT" },
80
+ * ];
81
+ * const metavars = extractArgumentMetavars(usage);
82
+ * // metavars = Set(["FILE", "OUTPUT"])
83
+ * ```
84
+ * @since 0.9.0
85
+ */
86
+ function extractArgumentMetavars(usage) {
87
+ const metavars = /* @__PURE__ */ new Set();
88
+ function traverseUsage(terms) {
89
+ if (!terms || !Array.isArray(terms)) return;
90
+ for (const term of terms) if (term.type === "argument") metavars.add(term.metavar);
91
+ else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
92
+ else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
93
+ }
94
+ traverseUsage(usage);
95
+ return metavars;
96
+ }
97
+ /**
66
98
  * Formats a usage description into a human-readable string representation
67
99
  * suitable for command-line help text.
68
100
  *
@@ -299,6 +331,7 @@ function* formatUsageTermInternal(term, options) {
299
331
  }
300
332
 
301
333
  //#endregion
334
+ exports.extractArgumentMetavars = extractArgumentMetavars;
302
335
  exports.extractCommandNames = extractCommandNames;
303
336
  exports.extractOptionNames = extractOptionNames;
304
337
  exports.formatUsage = formatUsage;
package/dist/usage.d.cts CHANGED
@@ -158,6 +158,28 @@ declare function extractOptionNames(usage: Usage): Set<string>;
158
158
  * @since 0.7.0
159
159
  */
160
160
  declare function extractCommandNames(usage: Usage): Set<string>;
161
+ /**
162
+ * Extracts all argument metavars from a Usage array.
163
+ *
164
+ * This function recursively traverses the usage structure and collects
165
+ * all argument metavariable names, similar to {@link extractOptionNames}
166
+ * and {@link extractCommandNames}.
167
+ *
168
+ * @param usage The usage structure to extract argument metavars from.
169
+ * @returns A Set of all argument metavars found in the usage structure.
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * const usage: Usage = [
174
+ * { type: "argument", metavar: "FILE" },
175
+ * { type: "argument", metavar: "OUTPUT" },
176
+ * ];
177
+ * const metavars = extractArgumentMetavars(usage);
178
+ * // metavars = Set(["FILE", "OUTPUT"])
179
+ * ```
180
+ * @since 0.9.0
181
+ */
182
+ declare function extractArgumentMetavars(usage: Usage): Set<string>;
161
183
  /**
162
184
  * Options for formatting usage descriptions.
163
185
  */
@@ -256,4 +278,4 @@ interface UsageTermFormatOptions extends UsageFormatOptions {
256
278
  */
257
279
  declare function formatUsageTerm(term: UsageTerm, options?: UsageTermFormatOptions): string;
258
280
  //#endregion
259
- export { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage };
281
+ export { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage };
package/dist/usage.d.ts CHANGED
@@ -158,6 +158,28 @@ declare function extractOptionNames(usage: Usage): Set<string>;
158
158
  * @since 0.7.0
159
159
  */
160
160
  declare function extractCommandNames(usage: Usage): Set<string>;
161
+ /**
162
+ * Extracts all argument metavars from a Usage array.
163
+ *
164
+ * This function recursively traverses the usage structure and collects
165
+ * all argument metavariable names, similar to {@link extractOptionNames}
166
+ * and {@link extractCommandNames}.
167
+ *
168
+ * @param usage The usage structure to extract argument metavars from.
169
+ * @returns A Set of all argument metavars found in the usage structure.
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * const usage: Usage = [
174
+ * { type: "argument", metavar: "FILE" },
175
+ * { type: "argument", metavar: "OUTPUT" },
176
+ * ];
177
+ * const metavars = extractArgumentMetavars(usage);
178
+ * // metavars = Set(["FILE", "OUTPUT"])
179
+ * ```
180
+ * @since 0.9.0
181
+ */
182
+ declare function extractArgumentMetavars(usage: Usage): Set<string>;
161
183
  /**
162
184
  * Options for formatting usage descriptions.
163
185
  */
@@ -256,4 +278,4 @@ interface UsageTermFormatOptions extends UsageFormatOptions {
256
278
  */
257
279
  declare function formatUsageTerm(term: UsageTerm, options?: UsageTermFormatOptions): string;
258
280
  //#endregion
259
- export { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage };
281
+ export { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage };
package/dist/usage.js CHANGED
@@ -62,6 +62,38 @@ function extractCommandNames(usage) {
62
62
  return names;
63
63
  }
64
64
  /**
65
+ * Extracts all argument metavars from a Usage array.
66
+ *
67
+ * This function recursively traverses the usage structure and collects
68
+ * all argument metavariable names, similar to {@link extractOptionNames}
69
+ * and {@link extractCommandNames}.
70
+ *
71
+ * @param usage The usage structure to extract argument metavars from.
72
+ * @returns A Set of all argument metavars found in the usage structure.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const usage: Usage = [
77
+ * { type: "argument", metavar: "FILE" },
78
+ * { type: "argument", metavar: "OUTPUT" },
79
+ * ];
80
+ * const metavars = extractArgumentMetavars(usage);
81
+ * // metavars = Set(["FILE", "OUTPUT"])
82
+ * ```
83
+ * @since 0.9.0
84
+ */
85
+ function extractArgumentMetavars(usage) {
86
+ const metavars = /* @__PURE__ */ new Set();
87
+ function traverseUsage(terms) {
88
+ if (!terms || !Array.isArray(terms)) return;
89
+ for (const term of terms) if (term.type === "argument") metavars.add(term.metavar);
90
+ else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
91
+ else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
92
+ }
93
+ traverseUsage(usage);
94
+ return metavars;
95
+ }
96
+ /**
65
97
  * Formats a usage description into a human-readable string representation
66
98
  * suitable for command-line help text.
67
99
  *
@@ -298,4 +330,4 @@ function* formatUsageTermInternal(term, options) {
298
330
  }
299
331
 
300
332
  //#endregion
301
- export { extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage };
333
+ export { extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "0.7.0-dev.150+59dd16c2",
3
+ "version": "0.7.0-dev.153+e7e0b894",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",