@optique/core 1.0.0-dev.921 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 -525
- package/dist/facade.d.cts +59 -15
- package/dist/facade.d.ts +59 -15
- package/dist/facade.js +718 -525
- 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 +1270 -187
- package/dist/valueparser.d.cts +320 -14
- package/dist/valueparser.d.ts +320 -14
- package/dist/valueparser.js +1269 -188
- package/package.json +9 -9
package/dist/usage.cjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const require_displaywidth = require('./displaywidth.cjs');
|
|
2
|
+
const require_validate = require('./validate.cjs');
|
|
1
3
|
|
|
2
4
|
//#region src/usage.ts
|
|
3
5
|
/**
|
|
@@ -40,6 +42,8 @@ function mergeHidden(a, b) {
|
|
|
40
42
|
* multiple, and exclusive terms.
|
|
41
43
|
*
|
|
42
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`.
|
|
43
47
|
* @returns A set containing all option names found in the usage description.
|
|
44
48
|
*
|
|
45
49
|
* @example
|
|
@@ -52,12 +56,12 @@ function mergeHidden(a, b) {
|
|
|
52
56
|
* // names = Set(["--verbose", "-v", "--quiet", "-q"])
|
|
53
57
|
* ```
|
|
54
58
|
*/
|
|
55
|
-
function extractOptionNames(usage) {
|
|
59
|
+
function extractOptionNames(usage, includeHidden) {
|
|
56
60
|
const names = /* @__PURE__ */ new Set();
|
|
57
61
|
function traverseUsage(terms) {
|
|
58
62
|
if (!terms || !Array.isArray(terms)) return;
|
|
59
63
|
for (const term of terms) if (term.type === "option") {
|
|
60
|
-
if (isSuggestionHidden(term.hidden)) continue;
|
|
64
|
+
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
61
65
|
for (const name of term.names) names.add(name);
|
|
62
66
|
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
63
67
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
@@ -71,8 +75,10 @@ function extractOptionNames(usage) {
|
|
|
71
75
|
* This function recursively traverses the usage structure and collects
|
|
72
76
|
* all command names, similar to {@link extractOptionNames}.
|
|
73
77
|
*
|
|
74
|
-
* @param usage The usage structure to extract command names from
|
|
75
|
-
* @
|
|
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.
|
|
76
82
|
*
|
|
77
83
|
* @example
|
|
78
84
|
* ```typescript
|
|
@@ -85,12 +91,12 @@ function extractOptionNames(usage) {
|
|
|
85
91
|
* ```
|
|
86
92
|
* @since 0.7.0
|
|
87
93
|
*/
|
|
88
|
-
function extractCommandNames(usage) {
|
|
94
|
+
function extractCommandNames(usage, includeHidden) {
|
|
89
95
|
const names = /* @__PURE__ */ new Set();
|
|
90
96
|
function traverseUsage(terms) {
|
|
91
97
|
if (!terms || !Array.isArray(terms)) return;
|
|
92
98
|
for (const term of terms) if (term.type === "command") {
|
|
93
|
-
if (isSuggestionHidden(term.hidden)) continue;
|
|
99
|
+
if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
|
|
94
100
|
names.add(term.name);
|
|
95
101
|
} else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
|
|
96
102
|
else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
|
|
@@ -99,6 +105,29 @@ function extractCommandNames(usage) {
|
|
|
99
105
|
return names;
|
|
100
106
|
}
|
|
101
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
|
+
/**
|
|
102
131
|
* Extracts all argument metavars from a Usage array.
|
|
103
132
|
*
|
|
104
133
|
* This function recursively traverses the usage structure and collects
|
|
@@ -147,8 +176,11 @@ function extractArgumentMetavars(usage) {
|
|
|
147
176
|
* @param options Optional formatting options to customize the output.
|
|
148
177
|
* See {@link UsageFormatOptions} for available options.
|
|
149
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.
|
|
150
181
|
*/
|
|
151
182
|
function formatUsage(programName, usage, options = {}) {
|
|
183
|
+
require_validate.validateProgramName(programName);
|
|
152
184
|
usage = normalizeUsage(filterUsageForDisplay(usage));
|
|
153
185
|
if (options.expandCommands) {
|
|
154
186
|
const lastTerm = usage.at(-1);
|
|
@@ -160,13 +192,24 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
160
192
|
if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
|
|
161
193
|
lines.push(formatUsage(programName, command, options));
|
|
162
194
|
}
|
|
163
|
-
return lines.join("\n");
|
|
195
|
+
if (lines.length > 0) return lines.join("\n");
|
|
164
196
|
}
|
|
165
197
|
}
|
|
166
|
-
let output = options.colors ? `\x1b[1m${programName}\x1b[0m
|
|
167
|
-
let lineWidth = programName
|
|
198
|
+
let output = options.colors ? `\x1b[1m${programName}\x1b[0m` : programName;
|
|
199
|
+
let lineWidth = require_displaywidth.getDisplayWidth(programName);
|
|
200
|
+
let first = true;
|
|
168
201
|
for (const { text, width } of formatUsageTerms(usage, options)) {
|
|
169
|
-
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);
|
|
170
213
|
output += "\n";
|
|
171
214
|
lineWidth = 0;
|
|
172
215
|
if (text === " ") continue;
|
|
@@ -181,14 +224,27 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
181
224
|
* sorting terms for better readability, and ensuring consistent structure
|
|
182
225
|
* throughout the usage tree.
|
|
183
226
|
*
|
|
184
|
-
* 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.
|
|
185
238
|
*
|
|
186
|
-
*
|
|
239
|
+
* 2. *Flattening*: Recursively processes all usage terms and merges any
|
|
187
240
|
* nested exclusive terms into their parent exclusive term to avoid
|
|
188
241
|
* redundant nesting. For example, an exclusive term containing another
|
|
189
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.
|
|
190
246
|
*
|
|
191
|
-
*
|
|
247
|
+
* 3. *Sorting*: Reorders terms to improve readability by placing:
|
|
192
248
|
* - Commands (subcommands) first
|
|
193
249
|
* - Options and other terms in the middle
|
|
194
250
|
* - Positional arguments last (including optional/multiple wrappers around
|
|
@@ -198,11 +254,12 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
198
254
|
* positional arguments and treats them as arguments for sorting purposes.
|
|
199
255
|
*
|
|
200
256
|
* @param usage The usage description to normalize.
|
|
201
|
-
* @returns A normalized usage description with
|
|
202
|
-
* 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.
|
|
203
260
|
*/
|
|
204
261
|
function normalizeUsage(usage) {
|
|
205
|
-
const terms = usage.map(normalizeUsageTerm);
|
|
262
|
+
const terms = usage.map(normalizeUsageTerm).filter(isNonDegenerateTerm);
|
|
206
263
|
terms.sort((a, b) => {
|
|
207
264
|
const aCmd = a.type === "command";
|
|
208
265
|
const bCmd = b.type === "command";
|
|
@@ -213,11 +270,14 @@ function normalizeUsage(usage) {
|
|
|
213
270
|
return terms;
|
|
214
271
|
}
|
|
215
272
|
function normalizeUsageTerm(term) {
|
|
216
|
-
if (term.type === "optional")
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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 {
|
|
221
281
|
type: "multiple",
|
|
222
282
|
terms: normalizeUsage(term.terms),
|
|
223
283
|
min: term.min
|
|
@@ -229,20 +289,111 @@ function normalizeUsageTerm(term) {
|
|
|
229
289
|
if (normalized.length >= 1 && normalized[0].type === "exclusive") {
|
|
230
290
|
const rest = normalized.slice(1);
|
|
231
291
|
for (const subUsage of normalized[0].terms) terms.push([...subUsage, ...rest]);
|
|
232
|
-
} else terms.push(normalized);
|
|
292
|
+
} else if (normalized.length > 0 || !containsMalformedLeaf(usage)) terms.push(normalized);
|
|
233
293
|
}
|
|
234
294
|
return {
|
|
235
295
|
type: "exclusive",
|
|
236
296
|
terms
|
|
237
297
|
};
|
|
238
|
-
} 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);
|
|
239
386
|
}
|
|
240
|
-
function filterUsageForDisplay(usage) {
|
|
387
|
+
function filterUsageForDisplay(usage, isHidden = isUsageHidden) {
|
|
241
388
|
const terms = [];
|
|
242
389
|
for (const term of usage) {
|
|
243
|
-
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;
|
|
244
395
|
if (term.type === "optional") {
|
|
245
|
-
const filtered = filterUsageForDisplay(term.terms);
|
|
396
|
+
const filtered = filterUsageForDisplay(term.terms, isHidden);
|
|
246
397
|
if (filtered.length > 0) terms.push({
|
|
247
398
|
type: "optional",
|
|
248
399
|
terms: filtered
|
|
@@ -250,7 +401,7 @@ function filterUsageForDisplay(usage) {
|
|
|
250
401
|
continue;
|
|
251
402
|
}
|
|
252
403
|
if (term.type === "multiple") {
|
|
253
|
-
const filtered = filterUsageForDisplay(term.terms);
|
|
404
|
+
const filtered = filterUsageForDisplay(term.terms, isHidden);
|
|
254
405
|
if (filtered.length > 0) terms.push({
|
|
255
406
|
type: "multiple",
|
|
256
407
|
terms: filtered,
|
|
@@ -261,8 +412,8 @@ function filterUsageForDisplay(usage) {
|
|
|
261
412
|
if (term.type === "exclusive") {
|
|
262
413
|
const filteredBranches = term.terms.map((branch) => {
|
|
263
414
|
const first = branch[0];
|
|
264
|
-
if (first?.type === "command" &&
|
|
265
|
-
return filterUsageForDisplay(branch);
|
|
415
|
+
if (first?.type === "command" && isHidden(first.hidden)) return [];
|
|
416
|
+
return filterUsageForDisplay(branch, isHidden);
|
|
266
417
|
}).filter((branch) => branch.length > 0);
|
|
267
418
|
if (filteredBranches.length > 0) terms.push({
|
|
268
419
|
type: "exclusive",
|
|
@@ -277,7 +428,6 @@ function filterUsageForDisplay(usage) {
|
|
|
277
428
|
function* formatUsageTerms(terms, options) {
|
|
278
429
|
let i = 0;
|
|
279
430
|
for (const t of terms) {
|
|
280
|
-
if ("hidden" in t && (t.type === "argument" || t.type === "option" || t.type === "command" || t.type === "passthrough") && isUsageHidden(t.hidden)) continue;
|
|
281
431
|
if (i > 0) yield {
|
|
282
432
|
text: " ",
|
|
283
433
|
width: 1
|
|
@@ -296,12 +446,14 @@ function* formatUsageTerms(terms, options) {
|
|
|
296
446
|
* @returns A formatted string representation of the usage term.
|
|
297
447
|
*/
|
|
298
448
|
function formatUsageTerm(term, options = {}) {
|
|
299
|
-
const
|
|
449
|
+
const hiddenCheck = options.context === "doc" ? isDocHidden : isUsageHidden;
|
|
450
|
+
const visibleTerms = filterUsageForDisplay([term], hiddenCheck);
|
|
300
451
|
if (visibleTerms.length < 1) return "";
|
|
301
452
|
let lineWidth = 0;
|
|
302
453
|
let output = "";
|
|
303
454
|
for (const { text, width } of formatUsageTermInternal(visibleTerms[0], options)) {
|
|
304
|
-
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);
|
|
305
457
|
output += "\n";
|
|
306
458
|
lineWidth = 0;
|
|
307
459
|
if (text === " ") continue;
|
|
@@ -315,24 +467,24 @@ function* formatUsageTermInternal(term, options) {
|
|
|
315
467
|
const optionsSeparator = options.optionsSeparator ?? "/";
|
|
316
468
|
if (term.type === "argument") yield {
|
|
317
469
|
text: options?.colors ? `\x1b[4m${term.metavar}\x1b[0m` : term.metavar,
|
|
318
|
-
width: term.metavar
|
|
470
|
+
width: require_displaywidth.getDisplayWidth(term.metavar)
|
|
319
471
|
};
|
|
320
472
|
else if (term.type === "option") if (options?.onlyShortestOptions) {
|
|
321
|
-
const shortestName = term.names.reduce((a, b) => a
|
|
473
|
+
const shortestName = term.names.reduce((a, b) => require_displaywidth.getDisplayWidth(a) <= require_displaywidth.getDisplayWidth(b) ? a : b);
|
|
322
474
|
yield {
|
|
323
475
|
text: options?.colors ? `\x1b[3m${shortestName}\x1b[0m` : shortestName,
|
|
324
|
-
width: shortestName
|
|
476
|
+
width: require_displaywidth.getDisplayWidth(shortestName)
|
|
325
477
|
};
|
|
326
478
|
} else {
|
|
327
479
|
let i = 0;
|
|
328
480
|
for (const optionName of term.names) {
|
|
329
481
|
if (i > 0) yield {
|
|
330
482
|
text: options?.colors ? `\x1b[2m${optionsSeparator}\x1b[0m` : optionsSeparator,
|
|
331
|
-
width: optionsSeparator
|
|
483
|
+
width: require_displaywidth.getDisplayWidth(optionsSeparator)
|
|
332
484
|
};
|
|
333
485
|
yield {
|
|
334
486
|
text: options?.colors ? `\x1b[3m${optionName}\x1b[0m` : optionName,
|
|
335
|
-
width: optionName
|
|
487
|
+
width: require_displaywidth.getDisplayWidth(optionName)
|
|
336
488
|
};
|
|
337
489
|
i++;
|
|
338
490
|
}
|
|
@@ -343,13 +495,13 @@ function* formatUsageTermInternal(term, options) {
|
|
|
343
495
|
};
|
|
344
496
|
yield {
|
|
345
497
|
text: options?.colors ? `\x1b[4m\x1b[2m${term.metavar}\x1b[0m` : term.metavar,
|
|
346
|
-
width: term.metavar
|
|
498
|
+
width: require_displaywidth.getDisplayWidth(term.metavar)
|
|
347
499
|
};
|
|
348
500
|
}
|
|
349
501
|
}
|
|
350
502
|
else if (term.type === "command") yield {
|
|
351
503
|
text: options?.colors ? `\x1b[1m${term.name}\x1b[0m` : term.name,
|
|
352
|
-
width: term.name
|
|
504
|
+
width: require_displaywidth.getDisplayWidth(term.name)
|
|
353
505
|
};
|
|
354
506
|
else if (term.type === "optional") {
|
|
355
507
|
yield {
|
|
@@ -411,7 +563,7 @@ function* formatUsageTermInternal(term, options) {
|
|
|
411
563
|
};
|
|
412
564
|
} else if (term.type === "literal") yield {
|
|
413
565
|
text: term.value,
|
|
414
|
-
width: term.value
|
|
566
|
+
width: require_displaywidth.getDisplayWidth(term.value)
|
|
415
567
|
};
|
|
416
568
|
else if (term.type === "passthrough") {
|
|
417
569
|
const text = "[...]";
|
|
@@ -429,8 +581,11 @@ function* formatUsageTermInternal(term, options) {
|
|
|
429
581
|
}
|
|
430
582
|
|
|
431
583
|
//#endregion
|
|
584
|
+
exports.cloneUsage = cloneUsage;
|
|
585
|
+
exports.cloneUsageTerm = cloneUsageTerm;
|
|
432
586
|
exports.extractArgumentMetavars = extractArgumentMetavars;
|
|
433
587
|
exports.extractCommandNames = extractCommandNames;
|
|
588
|
+
exports.extractLiteralValues = extractLiteralValues;
|
|
434
589
|
exports.extractOptionNames = extractOptionNames;
|
|
435
590
|
exports.formatUsage = formatUsage;
|
|
436
591
|
exports.formatUsageTerm = formatUsageTerm;
|
package/dist/usage.d.cts
CHANGED
|
@@ -10,8 +10,14 @@ import { NonEmptyString } from "./nonempty.cjs";
|
|
|
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 };
|