@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.
Files changed (109) hide show
  1. package/dist/annotation-state.cjs +425 -0
  2. package/dist/annotation-state.d.cts +24 -0
  3. package/dist/annotation-state.d.ts +24 -0
  4. package/dist/annotation-state.js +414 -0
  5. package/dist/annotations.cjs +2 -248
  6. package/dist/annotations.d.cts +2 -137
  7. package/dist/annotations.d.ts +2 -137
  8. package/dist/annotations.js +2 -238
  9. package/dist/completion.cjs +611 -100
  10. package/dist/completion.d.cts +1 -1
  11. package/dist/completion.d.ts +1 -1
  12. package/dist/completion.js +611 -100
  13. package/dist/constructs.cjs +3338 -827
  14. package/dist/constructs.d.cts +48 -7
  15. package/dist/constructs.d.ts +48 -7
  16. package/dist/constructs.js +3338 -827
  17. package/dist/context.cjs +0 -23
  18. package/dist/context.d.cts +119 -53
  19. package/dist/context.d.ts +119 -53
  20. package/dist/context.js +0 -22
  21. package/dist/dependency-metadata.cjs +139 -0
  22. package/dist/dependency-metadata.d.cts +112 -0
  23. package/dist/dependency-metadata.d.ts +112 -0
  24. package/dist/dependency-metadata.js +138 -0
  25. package/dist/dependency-runtime.cjs +698 -0
  26. package/dist/dependency-runtime.d.cts +149 -0
  27. package/dist/dependency-runtime.d.ts +149 -0
  28. package/dist/dependency-runtime.js +687 -0
  29. package/dist/dependency.cjs +7 -928
  30. package/dist/dependency.d.cts +2 -794
  31. package/dist/dependency.d.ts +2 -794
  32. package/dist/dependency.js +2 -899
  33. package/dist/displaywidth.cjs +44 -0
  34. package/dist/displaywidth.js +43 -0
  35. package/dist/doc.cjs +285 -23
  36. package/dist/doc.d.cts +57 -2
  37. package/dist/doc.d.ts +57 -2
  38. package/dist/doc.js +283 -25
  39. package/dist/execution-context.cjs +56 -0
  40. package/dist/execution-context.js +53 -0
  41. package/dist/extension.cjs +87 -0
  42. package/dist/extension.d.cts +97 -0
  43. package/dist/extension.d.ts +97 -0
  44. package/dist/extension.js +76 -0
  45. package/dist/facade.cjs +718 -523
  46. package/dist/facade.d.cts +87 -18
  47. package/dist/facade.d.ts +87 -18
  48. package/dist/facade.js +718 -523
  49. package/dist/index.cjs +14 -29
  50. package/dist/index.d.cts +10 -10
  51. package/dist/index.d.ts +10 -10
  52. package/dist/index.js +7 -7
  53. package/dist/input-trace.cjs +56 -0
  54. package/dist/input-trace.d.cts +77 -0
  55. package/dist/input-trace.d.ts +77 -0
  56. package/dist/input-trace.js +55 -0
  57. package/dist/internal/annotations.cjs +316 -0
  58. package/dist/internal/annotations.d.cts +140 -0
  59. package/dist/internal/annotations.d.ts +140 -0
  60. package/dist/internal/annotations.js +306 -0
  61. package/dist/internal/dependency.cjs +984 -0
  62. package/dist/internal/dependency.d.cts +539 -0
  63. package/dist/internal/dependency.d.ts +539 -0
  64. package/dist/internal/dependency.js +964 -0
  65. package/dist/{mode-dispatch.cjs → internal/mode-dispatch.cjs} +1 -3
  66. package/dist/{mode-dispatch.d.cts → internal/mode-dispatch.d.cts} +3 -7
  67. package/dist/{mode-dispatch.d.ts → internal/mode-dispatch.d.ts} +3 -7
  68. package/dist/{mode-dispatch.js → internal/mode-dispatch.js} +1 -3
  69. package/dist/internal/parser.cjs +728 -0
  70. package/dist/internal/parser.d.cts +947 -0
  71. package/dist/internal/parser.d.ts +947 -0
  72. package/dist/internal/parser.js +711 -0
  73. package/dist/message.cjs +84 -26
  74. package/dist/message.d.cts +49 -9
  75. package/dist/message.d.ts +49 -9
  76. package/dist/message.js +84 -27
  77. package/dist/modifiers.cjs +1023 -240
  78. package/dist/modifiers.d.cts +42 -1
  79. package/dist/modifiers.d.ts +42 -1
  80. package/dist/modifiers.js +1023 -240
  81. package/dist/parser.cjs +11 -463
  82. package/dist/parser.d.cts +3 -537
  83. package/dist/parser.d.ts +3 -537
  84. package/dist/parser.js +2 -433
  85. package/dist/phase2-seed.cjs +59 -0
  86. package/dist/phase2-seed.js +56 -0
  87. package/dist/primitives.cjs +557 -208
  88. package/dist/primitives.d.cts +10 -14
  89. package/dist/primitives.d.ts +10 -14
  90. package/dist/primitives.js +557 -208
  91. package/dist/program.cjs +5 -1
  92. package/dist/program.d.cts +5 -3
  93. package/dist/program.d.ts +5 -3
  94. package/dist/program.js +6 -1
  95. package/dist/suggestion.cjs +22 -8
  96. package/dist/suggestion.js +22 -8
  97. package/dist/usage-internals.cjs +3 -2
  98. package/dist/usage-internals.js +4 -2
  99. package/dist/usage.cjs +195 -40
  100. package/dist/usage.d.cts +92 -11
  101. package/dist/usage.d.ts +92 -11
  102. package/dist/usage.js +194 -41
  103. package/dist/validate.cjs +170 -0
  104. package/dist/validate.js +164 -0
  105. package/dist/valueparser.cjs +1278 -191
  106. package/dist/valueparser.d.cts +330 -20
  107. package/dist/valueparser.d.ts +330 -20
  108. package/dist/valueparser.js +1277 -192
  109. 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
- * @returns A Set of all command names found in the usage structure
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 ` : `${programName} `;
167
- let lineWidth = programName.length + 1;
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 (options.maxWidth != null && lineWidth + width > options.maxWidth) {
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 two main operations:
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
- * 1. *Flattening*: Recursively processes all usage terms and merges any
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
- * 2. *Sorting*: Reorders terms to improve readability by placing:
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 flattened exclusive terms
202
- * and terms sorted for optimal readability.
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") return {
217
- type: "optional",
218
- terms: normalizeUsage(term.terms)
219
- };
220
- else if (term.type === "multiple") return {
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 return term;
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") && isUsageHidden(term.hidden)) continue;
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" && isUsageHidden(first.hidden)) return [];
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 visibleTerms = filterUsageForDisplay([term]);
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.length
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.length <= b.length ? a : b);
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.length
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.length
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.length
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.length
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.length
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.length
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 = `--${string}` | `-${string}` | `/${string}` | `+${string}`;
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
- * @returns A Set of all command names found in the usage structure
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 two main operations:
366
+ * This function performs three main operations:
330
367
  *
331
- * 1. *Flattening*: Recursively processes all usage terms and merges any
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
- * 2. *Sorting*: Reorders terms to improve readability by placing:
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 flattened exclusive terms
347
- * and terms sorted for optimal readability.
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 };