@optique/core 1.0.0-dev.908 → 1.0.0
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/annotation-state.cjs +425 -0
- package/dist/annotation-state.d.cts +24 -0
- package/dist/annotation-state.d.ts +24 -0
- package/dist/annotation-state.js +414 -0
- package/dist/annotations.cjs +2 -248
- package/dist/annotations.d.cts +2 -137
- package/dist/annotations.d.ts +2 -137
- package/dist/annotations.js +2 -238
- package/dist/completion.cjs +611 -100
- package/dist/completion.d.cts +1 -1
- package/dist/completion.d.ts +1 -1
- package/dist/completion.js +611 -100
- package/dist/constructs.cjs +3338 -827
- package/dist/constructs.d.cts +48 -7
- package/dist/constructs.d.ts +48 -7
- package/dist/constructs.js +3338 -827
- package/dist/context.cjs +0 -23
- package/dist/context.d.cts +119 -53
- package/dist/context.d.ts +119 -53
- package/dist/context.js +0 -22
- package/dist/dependency-metadata.cjs +139 -0
- package/dist/dependency-metadata.d.cts +112 -0
- package/dist/dependency-metadata.d.ts +112 -0
- package/dist/dependency-metadata.js +138 -0
- package/dist/dependency-runtime.cjs +698 -0
- package/dist/dependency-runtime.d.cts +149 -0
- package/dist/dependency-runtime.d.ts +149 -0
- package/dist/dependency-runtime.js +687 -0
- package/dist/dependency.cjs +7 -928
- package/dist/dependency.d.cts +2 -794
- package/dist/dependency.d.ts +2 -794
- package/dist/dependency.js +2 -899
- package/dist/displaywidth.cjs +44 -0
- package/dist/displaywidth.js +43 -0
- package/dist/doc.cjs +285 -23
- package/dist/doc.d.cts +57 -2
- package/dist/doc.d.ts +57 -2
- package/dist/doc.js +283 -25
- package/dist/execution-context.cjs +56 -0
- package/dist/execution-context.js +53 -0
- package/dist/extension.cjs +87 -0
- package/dist/extension.d.cts +97 -0
- package/dist/extension.d.ts +97 -0
- package/dist/extension.js +76 -0
- package/dist/facade.cjs +718 -523
- package/dist/facade.d.cts +87 -18
- package/dist/facade.d.ts +87 -18
- package/dist/facade.js +718 -523
- package/dist/index.cjs +14 -29
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +7 -7
- package/dist/input-trace.cjs +56 -0
- package/dist/input-trace.d.cts +77 -0
- package/dist/input-trace.d.ts +77 -0
- package/dist/input-trace.js +55 -0
- package/dist/internal/annotations.cjs +316 -0
- package/dist/internal/annotations.d.cts +140 -0
- package/dist/internal/annotations.d.ts +140 -0
- package/dist/internal/annotations.js +306 -0
- package/dist/internal/dependency.cjs +984 -0
- package/dist/internal/dependency.d.cts +539 -0
- package/dist/internal/dependency.d.ts +539 -0
- package/dist/internal/dependency.js +964 -0
- package/dist/{mode-dispatch.cjs → internal/mode-dispatch.cjs} +1 -3
- package/dist/{mode-dispatch.d.cts → internal/mode-dispatch.d.cts} +3 -7
- package/dist/{mode-dispatch.d.ts → internal/mode-dispatch.d.ts} +3 -7
- package/dist/{mode-dispatch.js → internal/mode-dispatch.js} +1 -3
- package/dist/internal/parser.cjs +728 -0
- package/dist/internal/parser.d.cts +947 -0
- package/dist/internal/parser.d.ts +947 -0
- package/dist/internal/parser.js +711 -0
- package/dist/message.cjs +84 -26
- package/dist/message.d.cts +49 -9
- package/dist/message.d.ts +49 -9
- package/dist/message.js +84 -27
- package/dist/modifiers.cjs +1023 -240
- package/dist/modifiers.d.cts +42 -1
- package/dist/modifiers.d.ts +42 -1
- package/dist/modifiers.js +1023 -240
- package/dist/parser.cjs +11 -463
- package/dist/parser.d.cts +3 -537
- package/dist/parser.d.ts +3 -537
- package/dist/parser.js +2 -433
- package/dist/phase2-seed.cjs +59 -0
- package/dist/phase2-seed.js +56 -0
- package/dist/primitives.cjs +557 -208
- package/dist/primitives.d.cts +10 -14
- package/dist/primitives.d.ts +10 -14
- package/dist/primitives.js +557 -208
- package/dist/program.cjs +5 -1
- package/dist/program.d.cts +5 -3
- package/dist/program.d.ts +5 -3
- package/dist/program.js +6 -1
- package/dist/suggestion.cjs +22 -8
- package/dist/suggestion.js +22 -8
- package/dist/usage-internals.cjs +3 -2
- package/dist/usage-internals.js +4 -2
- package/dist/usage.cjs +195 -40
- package/dist/usage.d.cts +92 -11
- package/dist/usage.d.ts +92 -11
- package/dist/usage.js +194 -41
- package/dist/validate.cjs +170 -0
- package/dist/validate.js +164 -0
- package/dist/valueparser.cjs +1278 -191
- package/dist/valueparser.d.cts +330 -20
- package/dist/valueparser.d.ts +330 -20
- package/dist/valueparser.js +1277 -192
- package/package.json +9 -9
package/dist/usage.d.ts
CHANGED
|
@@ -10,8 +10,14 @@ import { NonEmptyString } from "./nonempty.js";
|
|
|
10
10
|
* - POSIX-style short options (`-o`) or Java-style options (`-option`)
|
|
11
11
|
* - MS-DOS-style options (`/o`, `/option`)
|
|
12
12
|
* - Plus-prefixed options (`+o`)
|
|
13
|
+
*
|
|
14
|
+
* Each prefix must be followed by at least one character, so bare prefixes
|
|
15
|
+
* like `"-"`, `"/"`, or `"+"` are rejected at compile time. Due to
|
|
16
|
+
* TypeScript template literal limitations, `"--"` still matches the
|
|
17
|
+
* `-${NonEmptyString}` branch and is only rejected at runtime by the
|
|
18
|
+
* `option()` and `flag()` validators.
|
|
13
19
|
*/
|
|
14
|
-
type OptionName = `--${
|
|
20
|
+
type OptionName = `--${NonEmptyString}` | `-${NonEmptyString}` | `/${NonEmptyString}` | `+${NonEmptyString}`;
|
|
15
21
|
/**
|
|
16
22
|
* Visibility control for parser terms.
|
|
17
23
|
*
|
|
@@ -172,6 +178,18 @@ type UsageTerm =
|
|
|
172
178
|
* The literal value that must be provided exactly as written.
|
|
173
179
|
*/
|
|
174
180
|
readonly value: string;
|
|
181
|
+
/**
|
|
182
|
+
* When `true`, this literal was derived from an option's metavar by
|
|
183
|
+
* `appendLiteralToUsage()` in `conditional()` and represents an option
|
|
184
|
+
* value, not a standalone positional token.
|
|
185
|
+
* {@link extractLeadingLiteralValues} and the `skipOptionValueLiterals`
|
|
186
|
+
* mode of `branchConsumesToken()` use this to distinguish option values
|
|
187
|
+
* from real positional literals. {@link extractLeadingOptionNames} and
|
|
188
|
+
* {@link extractLeadingCommandNames} intentionally still treat these
|
|
189
|
+
* literals as positional gates.
|
|
190
|
+
* @since 1.0.0
|
|
191
|
+
*/
|
|
192
|
+
readonly optionValue?: boolean;
|
|
175
193
|
}
|
|
176
194
|
/**
|
|
177
195
|
* A pass-through term, which represents unrecognized options that are
|
|
@@ -214,6 +232,8 @@ type Usage = readonly UsageTerm[];
|
|
|
214
232
|
* multiple, and exclusive terms.
|
|
215
233
|
*
|
|
216
234
|
* @param usage The usage description to extract option names from.
|
|
235
|
+
* @param includeHidden Whether to include fully hidden options (`hidden: true`)
|
|
236
|
+
* in the result. Defaults to `false`.
|
|
217
237
|
* @returns A set containing all option names found in the usage description.
|
|
218
238
|
*
|
|
219
239
|
* @example
|
|
@@ -226,15 +246,17 @@ type Usage = readonly UsageTerm[];
|
|
|
226
246
|
* // names = Set(["--verbose", "-v", "--quiet", "-q"])
|
|
227
247
|
* ```
|
|
228
248
|
*/
|
|
229
|
-
declare function extractOptionNames(usage: Usage): Set<string>;
|
|
249
|
+
declare function extractOptionNames(usage: Usage, includeHidden?: boolean): Set<string>;
|
|
230
250
|
/**
|
|
231
251
|
* Extracts all command names from a Usage array.
|
|
232
252
|
*
|
|
233
253
|
* This function recursively traverses the usage structure and collects
|
|
234
254
|
* all command names, similar to {@link extractOptionNames}.
|
|
235
255
|
*
|
|
236
|
-
* @param usage The usage structure to extract command names from
|
|
237
|
-
* @
|
|
256
|
+
* @param usage The usage structure to extract command names from.
|
|
257
|
+
* @param includeHidden Whether to include fully hidden commands
|
|
258
|
+
* (`hidden: true`) in the result. Defaults to `false`.
|
|
259
|
+
* @returns A set of all command names found in the usage structure.
|
|
238
260
|
*
|
|
239
261
|
* @example
|
|
240
262
|
* ```typescript
|
|
@@ -247,7 +269,20 @@ declare function extractOptionNames(usage: Usage): Set<string>;
|
|
|
247
269
|
* ```
|
|
248
270
|
* @since 0.7.0
|
|
249
271
|
*/
|
|
250
|
-
declare function extractCommandNames(usage: Usage): Set<string>;
|
|
272
|
+
declare function extractCommandNames(usage: Usage, includeHidden?: boolean): Set<string>;
|
|
273
|
+
/**
|
|
274
|
+
* Extracts all literal values from a usage description.
|
|
275
|
+
*
|
|
276
|
+
* This function recursively traverses the usage tree and collects all
|
|
277
|
+
* `literal` term values. Literal values represent fixed strings that
|
|
278
|
+
* the user must type (e.g., conditional discriminator values like
|
|
279
|
+
* `"server"` in `conditional(option("--mode", string()), { server: ... })`).
|
|
280
|
+
*
|
|
281
|
+
* @param usage The usage description to extract literal values from.
|
|
282
|
+
* @returns A set of all literal values found in the usage description.
|
|
283
|
+
* @since 1.0.0
|
|
284
|
+
*/
|
|
285
|
+
declare function extractLiteralValues(usage: Usage): Set<string>;
|
|
251
286
|
/**
|
|
252
287
|
* Extracts all argument metavars from a Usage array.
|
|
253
288
|
*
|
|
@@ -319,6 +354,8 @@ interface UsageFormatOptions {
|
|
|
319
354
|
* @param options Optional formatting options to customize the output.
|
|
320
355
|
* See {@link UsageFormatOptions} for available options.
|
|
321
356
|
* @returns A formatted string representation of the usage description.
|
|
357
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
358
|
+
* whitespace-only, or contains control characters.
|
|
322
359
|
*/
|
|
323
360
|
declare function formatUsage(programName: string, usage: Usage, options?: UsageFormatOptions): string;
|
|
324
361
|
/**
|
|
@@ -326,14 +363,27 @@ declare function formatUsage(programName: string, usage: Usage, options?: UsageF
|
|
|
326
363
|
* sorting terms for better readability, and ensuring consistent structure
|
|
327
364
|
* throughout the usage tree.
|
|
328
365
|
*
|
|
329
|
-
* This function performs
|
|
366
|
+
* This function performs three main operations:
|
|
330
367
|
*
|
|
331
|
-
* 1. *
|
|
368
|
+
* 1. *Stripping*: Removes degenerate terms that would render as empty or
|
|
369
|
+
* malformed output, such as options with no names, commands with empty
|
|
370
|
+
* names, arguments with empty metavars, and container terms (`optional`,
|
|
371
|
+
* `multiple`, `exclusive`) whose top-level terms array is empty after
|
|
372
|
+
* recursive normalization. Exclusive branches representing valid
|
|
373
|
+
* zero-token alternatives (e.g., `conditional()` default branches or
|
|
374
|
+
* `optional(constant(...))`) and empty-value literals are preserved.
|
|
375
|
+
* Only branches that become empty because all their content was
|
|
376
|
+
* malformed are removed.
|
|
377
|
+
*
|
|
378
|
+
* 2. *Flattening*: Recursively processes all usage terms and merges any
|
|
332
379
|
* nested exclusive terms into their parent exclusive term to avoid
|
|
333
380
|
* redundant nesting. For example, an exclusive term containing another
|
|
334
381
|
* exclusive term will have its nested terms flattened into the parent.
|
|
382
|
+
* Similarly, nested optional terms are collapsed:
|
|
383
|
+
* `optional(optional(X))` becomes `optional(X)` when the outer optional
|
|
384
|
+
* contains only a single inner optional term.
|
|
335
385
|
*
|
|
336
|
-
*
|
|
386
|
+
* 3. *Sorting*: Reorders terms to improve readability by placing:
|
|
337
387
|
* - Commands (subcommands) first
|
|
338
388
|
* - Options and other terms in the middle
|
|
339
389
|
* - Positional arguments last (including optional/multiple wrappers around
|
|
@@ -343,10 +393,31 @@ declare function formatUsage(programName: string, usage: Usage, options?: UsageF
|
|
|
343
393
|
* positional arguments and treats them as arguments for sorting purposes.
|
|
344
394
|
*
|
|
345
395
|
* @param usage The usage description to normalize.
|
|
346
|
-
* @returns A normalized usage description with
|
|
347
|
-
* and terms
|
|
396
|
+
* @returns A normalized usage description with degenerate terms removed,
|
|
397
|
+
* nested exclusive and optional terms flattened, and remaining
|
|
398
|
+
* terms sorted for optimal readability.
|
|
348
399
|
*/
|
|
349
400
|
declare function normalizeUsage(usage: Usage): Usage;
|
|
401
|
+
/**
|
|
402
|
+
* Creates a deep clone of a single {@link UsageTerm}. Recursive term
|
|
403
|
+
* variants (`optional`, `multiple`, `exclusive`) are cloned recursively.
|
|
404
|
+
* For `command` terms, a function-valued `usageLine` is preserved by
|
|
405
|
+
* reference (functions are stateless callbacks), while an array-valued
|
|
406
|
+
* `usageLine` is deep-cloned.
|
|
407
|
+
*
|
|
408
|
+
* @param term The usage term to clone.
|
|
409
|
+
* @returns A structurally equal but referentially distinct copy.
|
|
410
|
+
* @since 1.0.0
|
|
411
|
+
*/
|
|
412
|
+
declare function cloneUsageTerm(term: UsageTerm): UsageTerm;
|
|
413
|
+
/**
|
|
414
|
+
* Creates a deep clone of a {@link Usage} array and all of its terms.
|
|
415
|
+
*
|
|
416
|
+
* @param usage The usage array to clone.
|
|
417
|
+
* @returns A mutable array of deeply cloned usage terms.
|
|
418
|
+
* @since 1.0.0
|
|
419
|
+
*/
|
|
420
|
+
declare function cloneUsage(usage: Usage): UsageTerm[];
|
|
350
421
|
/**
|
|
351
422
|
* Options for formatting a single {@link UsageTerm}.
|
|
352
423
|
*/
|
|
@@ -356,6 +427,16 @@ interface UsageTermFormatOptions extends UsageFormatOptions {
|
|
|
356
427
|
* @default `"/"`
|
|
357
428
|
*/
|
|
358
429
|
readonly optionsSeparator?: string;
|
|
430
|
+
/**
|
|
431
|
+
* The rendering context, which determines which hidden visibility values
|
|
432
|
+
* cause terms to be filtered out.
|
|
433
|
+
*
|
|
434
|
+
* - `"usage"` (default): filters terms hidden from usage output
|
|
435
|
+
* - `"doc"`: filters terms hidden from documentation output
|
|
436
|
+
* @default `"usage"`
|
|
437
|
+
* @since 1.0.0
|
|
438
|
+
*/
|
|
439
|
+
readonly context?: "usage" | "doc";
|
|
359
440
|
}
|
|
360
441
|
/**
|
|
361
442
|
* Formats a single {@link UsageTerm} into a string representation
|
|
@@ -368,4 +449,4 @@ interface UsageTermFormatOptions extends UsageFormatOptions {
|
|
|
368
449
|
*/
|
|
369
450
|
declare function formatUsageTerm(term: UsageTerm, options?: UsageTermFormatOptions): string;
|
|
370
451
|
//#endregion
|
|
371
|
-
export { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
|
|
452
|
+
export { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
|
package/dist/usage.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { getDisplayWidth } from "./displaywidth.js";
|
|
2
|
+
import { validateProgramName } from "./validate.js";
|
|
3
|
+
|
|
1
4
|
//#region src/usage.ts
|
|
2
5
|
/**
|
|
3
6
|
* Returns whether the term should be hidden from usage output.
|
|
@@ -39,6 +42,8 @@ function mergeHidden(a, b) {
|
|
|
39
42
|
* multiple, and exclusive terms.
|
|
40
43
|
*
|
|
41
44
|
* @param usage The usage description to extract option names from.
|
|
45
|
+
* @param includeHidden Whether to include fully hidden options (`hidden: true`)
|
|
46
|
+
* in the result. Defaults to `false`.
|
|
42
47
|
* @returns A set containing all option names found in the usage description.
|
|
43
48
|
*
|
|
44
49
|
* @example
|
|
@@ -51,12 +56,12 @@ function mergeHidden(a, b) {
|
|
|
51
56
|
* // names = Set(["--verbose", "-v", "--quiet", "-q"])
|
|
52
57
|
* ```
|
|
53
58
|
*/
|
|
54
|
-
function extractOptionNames(usage) {
|
|
59
|
+
function extractOptionNames(usage, includeHidden) {
|
|
55
60
|
const names = /* @__PURE__ */ new Set();
|
|
56
61
|
function traverseUsage(terms) {
|
|
57
62
|
if (!terms || !Array.isArray(terms)) return;
|
|
58
63
|
for (const term of terms) if (term.type === "option") {
|
|
59
|
-
if (isSuggestionHidden(term.hidden)) continue;
|
|
64
|
+
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
60
65
|
for (const name of term.names) names.add(name);
|
|
61
66
|
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
62
67
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
@@ -70,8 +75,10 @@ function extractOptionNames(usage) {
|
|
|
70
75
|
* This function recursively traverses the usage structure and collects
|
|
71
76
|
* all command names, similar to {@link extractOptionNames}.
|
|
72
77
|
*
|
|
73
|
-
* @param usage The usage structure to extract command names from
|
|
74
|
-
* @
|
|
78
|
+
* @param usage The usage structure to extract command names from.
|
|
79
|
+
* @param includeHidden Whether to include fully hidden commands
|
|
80
|
+
* (`hidden: true`) in the result. Defaults to `false`.
|
|
81
|
+
* @returns A set of all command names found in the usage structure.
|
|
75
82
|
*
|
|
76
83
|
* @example
|
|
77
84
|
* ```typescript
|
|
@@ -84,12 +91,12 @@ function extractOptionNames(usage) {
|
|
|
84
91
|
* ```
|
|
85
92
|
* @since 0.7.0
|
|
86
93
|
*/
|
|
87
|
-
function extractCommandNames(usage) {
|
|
94
|
+
function extractCommandNames(usage, includeHidden) {
|
|
88
95
|
const names = /* @__PURE__ */ new Set();
|
|
89
96
|
function traverseUsage(terms) {
|
|
90
97
|
if (!terms || !Array.isArray(terms)) return;
|
|
91
98
|
for (const term of terms) if (term.type === "command") {
|
|
92
|
-
if (isSuggestionHidden(term.hidden)) continue;
|
|
99
|
+
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
93
100
|
names.add(term.name);
|
|
94
101
|
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
95
102
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
@@ -98,6 +105,29 @@ function extractCommandNames(usage) {
|
|
|
98
105
|
return names;
|
|
99
106
|
}
|
|
100
107
|
/**
|
|
108
|
+
* Extracts all literal values from a usage description.
|
|
109
|
+
*
|
|
110
|
+
* This function recursively traverses the usage tree and collects all
|
|
111
|
+
* `literal` term values. Literal values represent fixed strings that
|
|
112
|
+
* the user must type (e.g., conditional discriminator values like
|
|
113
|
+
* `"server"` in `conditional(option("--mode", string()), { server: ... })`).
|
|
114
|
+
*
|
|
115
|
+
* @param usage The usage description to extract literal values from.
|
|
116
|
+
* @returns A set of all literal values found in the usage description.
|
|
117
|
+
* @since 1.0.0
|
|
118
|
+
*/
|
|
119
|
+
function extractLiteralValues(usage) {
|
|
120
|
+
const values = /* @__PURE__ */ new Set();
|
|
121
|
+
function traverseUsage(terms) {
|
|
122
|
+
if (!terms || !Array.isArray(terms)) return;
|
|
123
|
+
for (const term of terms) if (term.type === "literal") values.add(term.value);
|
|
124
|
+
else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
125
|
+
else if (term.type === "exclusive") for (const branch of term.terms) traverseUsage(branch);
|
|
126
|
+
}
|
|
127
|
+
traverseUsage(usage);
|
|
128
|
+
return values;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
101
131
|
* Extracts all argument metavars from a Usage array.
|
|
102
132
|
*
|
|
103
133
|
* This function recursively traverses the usage structure and collects
|
|
@@ -146,8 +176,11 @@ function extractArgumentMetavars(usage) {
|
|
|
146
176
|
* @param options Optional formatting options to customize the output.
|
|
147
177
|
* See {@link UsageFormatOptions} for available options.
|
|
148
178
|
* @returns A formatted string representation of the usage description.
|
|
179
|
+
* @throws {TypeError} If `programName` is not a string, is empty,
|
|
180
|
+
* whitespace-only, or contains control characters.
|
|
149
181
|
*/
|
|
150
182
|
function formatUsage(programName, usage, options = {}) {
|
|
183
|
+
validateProgramName(programName);
|
|
151
184
|
usage = normalizeUsage(filterUsageForDisplay(usage));
|
|
152
185
|
if (options.expandCommands) {
|
|
153
186
|
const lastTerm = usage.at(-1);
|
|
@@ -159,13 +192,24 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
159
192
|
if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
|
|
160
193
|
lines.push(formatUsage(programName, command, options));
|
|
161
194
|
}
|
|
162
|
-
return lines.join("\n");
|
|
195
|
+
if (lines.length > 0) return lines.join("\n");
|
|
163
196
|
}
|
|
164
197
|
}
|
|
165
|
-
let output = options.colors ? `\x1b[1m${programName}\x1b[0m
|
|
166
|
-
let lineWidth = programName
|
|
198
|
+
let output = options.colors ? `\x1b[1m${programName}\x1b[0m` : programName;
|
|
199
|
+
let lineWidth = getDisplayWidth(programName);
|
|
200
|
+
let first = true;
|
|
167
201
|
for (const { text, width } of formatUsageTerms(usage, options)) {
|
|
168
|
-
if (
|
|
202
|
+
if (first) {
|
|
203
|
+
first = false;
|
|
204
|
+
if (options.maxWidth != null && lineWidth + 1 + width > options.maxWidth) {
|
|
205
|
+
output += "\n";
|
|
206
|
+
lineWidth = 0;
|
|
207
|
+
} else {
|
|
208
|
+
output += " ";
|
|
209
|
+
lineWidth += 1;
|
|
210
|
+
}
|
|
211
|
+
} else if (options.maxWidth != null && lineWidth > 0 && lineWidth + width > options.maxWidth) {
|
|
212
|
+
if (output.endsWith(" ")) output = output.slice(0, -1);
|
|
169
213
|
output += "\n";
|
|
170
214
|
lineWidth = 0;
|
|
171
215
|
if (text === " ") continue;
|
|
@@ -180,14 +224,27 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
180
224
|
* sorting terms for better readability, and ensuring consistent structure
|
|
181
225
|
* throughout the usage tree.
|
|
182
226
|
*
|
|
183
|
-
* This function performs
|
|
227
|
+
* This function performs three main operations:
|
|
228
|
+
*
|
|
229
|
+
* 1. *Stripping*: Removes degenerate terms that would render as empty or
|
|
230
|
+
* malformed output, such as options with no names, commands with empty
|
|
231
|
+
* names, arguments with empty metavars, and container terms (`optional`,
|
|
232
|
+
* `multiple`, `exclusive`) whose top-level terms array is empty after
|
|
233
|
+
* recursive normalization. Exclusive branches representing valid
|
|
234
|
+
* zero-token alternatives (e.g., `conditional()` default branches or
|
|
235
|
+
* `optional(constant(...))`) and empty-value literals are preserved.
|
|
236
|
+
* Only branches that become empty because all their content was
|
|
237
|
+
* malformed are removed.
|
|
184
238
|
*
|
|
185
|
-
*
|
|
239
|
+
* 2. *Flattening*: Recursively processes all usage terms and merges any
|
|
186
240
|
* nested exclusive terms into their parent exclusive term to avoid
|
|
187
241
|
* redundant nesting. For example, an exclusive term containing another
|
|
188
242
|
* exclusive term will have its nested terms flattened into the parent.
|
|
243
|
+
* Similarly, nested optional terms are collapsed:
|
|
244
|
+
* `optional(optional(X))` becomes `optional(X)` when the outer optional
|
|
245
|
+
* contains only a single inner optional term.
|
|
189
246
|
*
|
|
190
|
-
*
|
|
247
|
+
* 3. *Sorting*: Reorders terms to improve readability by placing:
|
|
191
248
|
* - Commands (subcommands) first
|
|
192
249
|
* - Options and other terms in the middle
|
|
193
250
|
* - Positional arguments last (including optional/multiple wrappers around
|
|
@@ -197,11 +254,12 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
197
254
|
* positional arguments and treats them as arguments for sorting purposes.
|
|
198
255
|
*
|
|
199
256
|
* @param usage The usage description to normalize.
|
|
200
|
-
* @returns A normalized usage description with
|
|
201
|
-
* and terms
|
|
257
|
+
* @returns A normalized usage description with degenerate terms removed,
|
|
258
|
+
* nested exclusive and optional terms flattened, and remaining
|
|
259
|
+
* terms sorted for optimal readability.
|
|
202
260
|
*/
|
|
203
261
|
function normalizeUsage(usage) {
|
|
204
|
-
const terms = usage.map(normalizeUsageTerm);
|
|
262
|
+
const terms = usage.map(normalizeUsageTerm).filter(isNonDegenerateTerm);
|
|
205
263
|
terms.sort((a, b) => {
|
|
206
264
|
const aCmd = a.type === "command";
|
|
207
265
|
const bCmd = b.type === "command";
|
|
@@ -212,11 +270,14 @@ function normalizeUsage(usage) {
|
|
|
212
270
|
return terms;
|
|
213
271
|
}
|
|
214
272
|
function normalizeUsageTerm(term) {
|
|
215
|
-
if (term.type === "optional")
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
273
|
+
if (term.type === "optional") {
|
|
274
|
+
const normalized = normalizeUsage(term.terms);
|
|
275
|
+
if (normalized.length === 1 && normalized[0].type === "optional") return normalized[0];
|
|
276
|
+
return {
|
|
277
|
+
type: "optional",
|
|
278
|
+
terms: normalized
|
|
279
|
+
};
|
|
280
|
+
} else if (term.type === "multiple") return {
|
|
220
281
|
type: "multiple",
|
|
221
282
|
terms: normalizeUsage(term.terms),
|
|
222
283
|
min: term.min
|
|
@@ -228,20 +289,111 @@ function normalizeUsageTerm(term) {
|
|
|
228
289
|
if (normalized.length >= 1 && normalized[0].type === "exclusive") {
|
|
229
290
|
const rest = normalized.slice(1);
|
|
230
291
|
for (const subUsage of normalized[0].terms) terms.push([...subUsage, ...rest]);
|
|
231
|
-
} else terms.push(normalized);
|
|
292
|
+
} else if (normalized.length > 0 || !containsMalformedLeaf(usage)) terms.push(normalized);
|
|
232
293
|
}
|
|
233
294
|
return {
|
|
234
295
|
type: "exclusive",
|
|
235
296
|
terms
|
|
236
297
|
};
|
|
237
|
-
} else
|
|
298
|
+
} else {
|
|
299
|
+
if (term.type === "option") return {
|
|
300
|
+
...term,
|
|
301
|
+
names: [...term.names]
|
|
302
|
+
};
|
|
303
|
+
else if (term.type === "command") {
|
|
304
|
+
if (term.usageLine == null || typeof term.usageLine === "function") return { ...term };
|
|
305
|
+
return {
|
|
306
|
+
...term,
|
|
307
|
+
usageLine: cloneUsage(term.usageLine)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return { ...term };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function isNonDegenerateTerm(term) {
|
|
314
|
+
if (term.type === "option") return term.names.length > 0;
|
|
315
|
+
if (term.type === "command") return term.name !== "";
|
|
316
|
+
if (term.type === "argument") return term.metavar.length > 0;
|
|
317
|
+
if (term.type === "optional" || term.type === "multiple" || term.type === "exclusive") return term.terms.length > 0;
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
function containsMalformedLeaf(usage) {
|
|
321
|
+
for (const term of usage) {
|
|
322
|
+
if (term.type === "option" && term.names.length === 0) return true;
|
|
323
|
+
if (term.type === "command" && term.name === "") return true;
|
|
324
|
+
if (term.type === "argument" && term.metavar.length === 0) return true;
|
|
325
|
+
if (term.type === "optional" || term.type === "multiple") {
|
|
326
|
+
if (containsMalformedLeaf(term.terms)) return true;
|
|
327
|
+
}
|
|
328
|
+
if (term.type === "exclusive") {
|
|
329
|
+
for (const branch of term.terms) if (containsMalformedLeaf(branch)) return true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Creates a deep clone of a single {@link UsageTerm}. Recursive term
|
|
336
|
+
* variants (`optional`, `multiple`, `exclusive`) are cloned recursively.
|
|
337
|
+
* For `command` terms, a function-valued `usageLine` is preserved by
|
|
338
|
+
* reference (functions are stateless callbacks), while an array-valued
|
|
339
|
+
* `usageLine` is deep-cloned.
|
|
340
|
+
*
|
|
341
|
+
* @param term The usage term to clone.
|
|
342
|
+
* @returns A structurally equal but referentially distinct copy.
|
|
343
|
+
* @since 1.0.0
|
|
344
|
+
*/
|
|
345
|
+
function cloneUsageTerm(term) {
|
|
346
|
+
switch (term.type) {
|
|
347
|
+
case "argument": return { ...term };
|
|
348
|
+
case "option": return {
|
|
349
|
+
...term,
|
|
350
|
+
names: [...term.names]
|
|
351
|
+
};
|
|
352
|
+
case "command": {
|
|
353
|
+
if (term.usageLine == null || typeof term.usageLine === "function") return { ...term };
|
|
354
|
+
return {
|
|
355
|
+
...term,
|
|
356
|
+
usageLine: term.usageLine.map(cloneUsageTerm)
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
case "optional": return {
|
|
360
|
+
type: "optional",
|
|
361
|
+
terms: term.terms.map(cloneUsageTerm)
|
|
362
|
+
};
|
|
363
|
+
case "multiple": return {
|
|
364
|
+
type: "multiple",
|
|
365
|
+
terms: term.terms.map(cloneUsageTerm),
|
|
366
|
+
min: term.min
|
|
367
|
+
};
|
|
368
|
+
case "exclusive": return {
|
|
369
|
+
type: "exclusive",
|
|
370
|
+
terms: term.terms.map((u) => u.map(cloneUsageTerm))
|
|
371
|
+
};
|
|
372
|
+
case "literal":
|
|
373
|
+
case "passthrough":
|
|
374
|
+
case "ellipsis": return { ...term };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Creates a deep clone of a {@link Usage} array and all of its terms.
|
|
379
|
+
*
|
|
380
|
+
* @param usage The usage array to clone.
|
|
381
|
+
* @returns A mutable array of deeply cloned usage terms.
|
|
382
|
+
* @since 1.0.0
|
|
383
|
+
*/
|
|
384
|
+
function cloneUsage(usage) {
|
|
385
|
+
return usage.map(cloneUsageTerm);
|
|
238
386
|
}
|
|
239
|
-
function filterUsageForDisplay(usage) {
|
|
387
|
+
function filterUsageForDisplay(usage, isHidden = isUsageHidden) {
|
|
240
388
|
const terms = [];
|
|
241
389
|
for (const term of usage) {
|
|
242
|
-
if ((term.type === "argument" || term.type === "option" || term.type === "command" || term.type === "passthrough") &&
|
|
390
|
+
if ((term.type === "argument" || term.type === "option" || term.type === "command" || term.type === "passthrough") && isHidden(term.hidden)) continue;
|
|
391
|
+
if (term.type === "option" && term.names.length === 0) continue;
|
|
392
|
+
if (term.type === "command" && term.name === "") continue;
|
|
393
|
+
if (term.type === "argument" && term.metavar.length === 0) continue;
|
|
394
|
+
if (term.type === "literal" && term.value === "") continue;
|
|
243
395
|
if (term.type === "optional") {
|
|
244
|
-
const filtered = filterUsageForDisplay(term.terms);
|
|
396
|
+
const filtered = filterUsageForDisplay(term.terms, isHidden);
|
|
245
397
|
if (filtered.length > 0) terms.push({
|
|
246
398
|
type: "optional",
|
|
247
399
|
terms: filtered
|
|
@@ -249,7 +401,7 @@ function filterUsageForDisplay(usage) {
|
|
|
249
401
|
continue;
|
|
250
402
|
}
|
|
251
403
|
if (term.type === "multiple") {
|
|
252
|
-
const filtered = filterUsageForDisplay(term.terms);
|
|
404
|
+
const filtered = filterUsageForDisplay(term.terms, isHidden);
|
|
253
405
|
if (filtered.length > 0) terms.push({
|
|
254
406
|
type: "multiple",
|
|
255
407
|
terms: filtered,
|
|
@@ -260,8 +412,8 @@ function filterUsageForDisplay(usage) {
|
|
|
260
412
|
if (term.type === "exclusive") {
|
|
261
413
|
const filteredBranches = term.terms.map((branch) => {
|
|
262
414
|
const first = branch[0];
|
|
263
|
-
if (first?.type === "command" &&
|
|
264
|
-
return filterUsageForDisplay(branch);
|
|
415
|
+
if (first?.type === "command" && isHidden(first.hidden)) return [];
|
|
416
|
+
return filterUsageForDisplay(branch, isHidden);
|
|
265
417
|
}).filter((branch) => branch.length > 0);
|
|
266
418
|
if (filteredBranches.length > 0) terms.push({
|
|
267
419
|
type: "exclusive",
|
|
@@ -276,7 +428,6 @@ function filterUsageForDisplay(usage) {
|
|
|
276
428
|
function* formatUsageTerms(terms, options) {
|
|
277
429
|
let i = 0;
|
|
278
430
|
for (const t of terms) {
|
|
279
|
-
if ("hidden" in t && (t.type === "argument" || t.type === "option" || t.type === "command" || t.type === "passthrough") && isUsageHidden(t.hidden)) continue;
|
|
280
431
|
if (i > 0) yield {
|
|
281
432
|
text: " ",
|
|
282
433
|
width: 1
|
|
@@ -295,12 +446,14 @@ function* formatUsageTerms(terms, options) {
|
|
|
295
446
|
* @returns A formatted string representation of the usage term.
|
|
296
447
|
*/
|
|
297
448
|
function formatUsageTerm(term, options = {}) {
|
|
298
|
-
const
|
|
449
|
+
const hiddenCheck = options.context === "doc" ? isDocHidden : isUsageHidden;
|
|
450
|
+
const visibleTerms = filterUsageForDisplay([term], hiddenCheck);
|
|
299
451
|
if (visibleTerms.length < 1) return "";
|
|
300
452
|
let lineWidth = 0;
|
|
301
453
|
let output = "";
|
|
302
454
|
for (const { text, width } of formatUsageTermInternal(visibleTerms[0], options)) {
|
|
303
|
-
if (options.maxWidth != null && lineWidth + width > options.maxWidth) {
|
|
455
|
+
if (options.maxWidth != null && lineWidth > 0 && lineWidth + width > options.maxWidth) {
|
|
456
|
+
if (output.endsWith(" ")) output = output.slice(0, -1);
|
|
304
457
|
output += "\n";
|
|
305
458
|
lineWidth = 0;
|
|
306
459
|
if (text === " ") continue;
|
|
@@ -314,24 +467,24 @@ function* formatUsageTermInternal(term, options) {
|
|
|
314
467
|
const optionsSeparator = options.optionsSeparator ?? "/";
|
|
315
468
|
if (term.type === "argument") yield {
|
|
316
469
|
text: options?.colors ? `\x1b[4m${term.metavar}\x1b[0m` : term.metavar,
|
|
317
|
-
width: term.metavar
|
|
470
|
+
width: getDisplayWidth(term.metavar)
|
|
318
471
|
};
|
|
319
472
|
else if (term.type === "option") if (options?.onlyShortestOptions) {
|
|
320
|
-
const shortestName = term.names.reduce((a, b) => a
|
|
473
|
+
const shortestName = term.names.reduce((a, b) => getDisplayWidth(a) <= getDisplayWidth(b) ? a : b);
|
|
321
474
|
yield {
|
|
322
475
|
text: options?.colors ? `\x1b[3m${shortestName}\x1b[0m` : shortestName,
|
|
323
|
-
width: shortestName
|
|
476
|
+
width: getDisplayWidth(shortestName)
|
|
324
477
|
};
|
|
325
478
|
} else {
|
|
326
479
|
let i = 0;
|
|
327
480
|
for (const optionName of term.names) {
|
|
328
481
|
if (i > 0) yield {
|
|
329
482
|
text: options?.colors ? `\x1b[2m${optionsSeparator}\x1b[0m` : optionsSeparator,
|
|
330
|
-
width: optionsSeparator
|
|
483
|
+
width: getDisplayWidth(optionsSeparator)
|
|
331
484
|
};
|
|
332
485
|
yield {
|
|
333
486
|
text: options?.colors ? `\x1b[3m${optionName}\x1b[0m` : optionName,
|
|
334
|
-
width: optionName
|
|
487
|
+
width: getDisplayWidth(optionName)
|
|
335
488
|
};
|
|
336
489
|
i++;
|
|
337
490
|
}
|
|
@@ -342,13 +495,13 @@ function* formatUsageTermInternal(term, options) {
|
|
|
342
495
|
};
|
|
343
496
|
yield {
|
|
344
497
|
text: options?.colors ? `\x1b[4m\x1b[2m${term.metavar}\x1b[0m` : term.metavar,
|
|
345
|
-
width: term.metavar
|
|
498
|
+
width: getDisplayWidth(term.metavar)
|
|
346
499
|
};
|
|
347
500
|
}
|
|
348
501
|
}
|
|
349
502
|
else if (term.type === "command") yield {
|
|
350
503
|
text: options?.colors ? `\x1b[1m${term.name}\x1b[0m` : term.name,
|
|
351
|
-
width: term.name
|
|
504
|
+
width: getDisplayWidth(term.name)
|
|
352
505
|
};
|
|
353
506
|
else if (term.type === "optional") {
|
|
354
507
|
yield {
|
|
@@ -410,7 +563,7 @@ function* formatUsageTermInternal(term, options) {
|
|
|
410
563
|
};
|
|
411
564
|
} else if (term.type === "literal") yield {
|
|
412
565
|
text: term.value,
|
|
413
|
-
width: term.value
|
|
566
|
+
width: getDisplayWidth(term.value)
|
|
414
567
|
};
|
|
415
568
|
else if (term.type === "passthrough") {
|
|
416
569
|
const text = "[...]";
|
|
@@ -428,4 +581,4 @@ function* formatUsageTermInternal(term, options) {
|
|
|
428
581
|
}
|
|
429
582
|
|
|
430
583
|
//#endregion
|
|
431
|
-
export { extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
|
|
584
|
+
export { cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
|