@optique/core 1.4.0-dev.2683 → 1.4.0-dev.2709

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/usage.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { getDisplayWidth } from "./displaywidth.js";
2
2
  import { cacheTerminalTheme, formatTerminalLeaf, fragmentTokens, serializeTokens } from "./terminal-internal.js";
3
+ import { forgetEmptyInput, resolveUsageForDisplay } from "./internal/usage.js";
3
4
  import { validateProgramName } from "./validate.js";
4
5
 
5
6
  //#region src/usage.ts
@@ -190,16 +191,33 @@ function formatUsage(programName, usage, options = {}) {
190
191
  theme: cacheTerminalTheme(options.theme)
191
192
  };
192
193
  validateProgramName(programName);
193
- usage = normalizeUsage(filterUsageForDisplay(usage));
194
+ return formatUsageLines(programName, normalizeUsage(filterUsageForDisplay(resolveUsageForDisplay(usage, isUsageHidden))), normalizeUsage(filterUsageForDisplay(forgetEmptyInput(usage))), options);
195
+ }
196
+ /**
197
+ * Formats a display-ready usage, expanding a trailing command group into one
198
+ * line per command when requested.
199
+ * @param programName The program name.
200
+ * @param usage The usage to draw, already resolved, filtered and normalized.
201
+ * @param declared The same usage as declared, without empty-input records,
202
+ * filtered and normalized. It tells a group that was drawn
203
+ * as optional because it accepts empty input apart from a
204
+ * group declared optional.
205
+ * @param options The formatting options.
206
+ * @returns The formatted usage.
207
+ */
208
+ function formatUsageLines(programName, usage, declared, options) {
194
209
  if (options.expandCommands) {
195
- const lastTerm = usage.at(-1);
210
+ let lastTerm = usage.at(-1);
211
+ const declaredLast = declared.at(-1);
212
+ if (lastTerm?.type === "optional" && lastTerm.terms.length === 1 && lastTerm.terms[0].type === "exclusive" && declaredLast?.type === "exclusive") lastTerm = lastTerm.terms[0];
196
213
  if (usage.length > 0 && usage.slice(0, -1).every((t) => t.type === "command") && lastTerm.type === "exclusive" && lastTerm.terms.every((t) => t.length > 0 && (t[0].type === "command" || t[0].type === "option" || t[0].type === "argument" || t[0].type === "optional" && t[0].terms.length === 1 && (t[0].terms[0].type === "command" || t[0].terms[0].type === "option" || t[0].terms[0].type === "argument")))) {
214
+ const declaredBranches = declaredLast?.type === "exclusive" && declaredLast.terms.length === lastTerm.terms.length ? declaredLast.terms : void 0;
197
215
  const lines = [];
198
- for (let command of lastTerm.terms) {
216
+ for (const [index, command] of lastTerm.terms.entries()) {
199
217
  const firstTerm = command[0];
200
218
  if (firstTerm?.type === "command" && isUsageHidden(firstTerm.hidden)) continue;
201
- if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
202
- lines.push(formatUsage(programName, command, options));
219
+ const line = normalizeUsage(filterUsageForDisplay([...usage.slice(0, -1), ...command]));
220
+ lines.push(formatUsageLines(programName, line, declaredBranches == null ? line : normalizeUsage(filterUsageForDisplay([...declared.slice(0, -1), ...declaredBranches[index]])), options));
203
221
  }
204
222
  if (lines.length > 0) return lines.join("\n");
205
223
  }
@@ -277,7 +295,8 @@ function normalizeUsageTerm(term) {
277
295
  } else if (term.type === "multiple") return {
278
296
  type: "multiple",
279
297
  terms: normalizeUsage(term.terms),
280
- min: term.min
298
+ min: term.min,
299
+ ...acceptsEmptyOf(term)
281
300
  };
282
301
  else if (term.type === "sequence") return {
283
302
  type: "sequence",
@@ -287,14 +306,15 @@ function normalizeUsageTerm(term) {
287
306
  const terms = [];
288
307
  for (const usage of term.terms) {
289
308
  const normalized = normalizeUsage(usage);
290
- if (normalized.length >= 1 && normalized[0].type === "exclusive") {
309
+ if (normalized.length >= 1 && normalized[0].type === "exclusive" && normalized[0].acceptsEmpty == null) {
291
310
  const rest = normalized.slice(1);
292
311
  for (const subUsage of normalized[0].terms) terms.push([...subUsage, ...rest]);
293
312
  } else if (normalized.length > 0 || !containsMalformedLeaf(usage)) terms.push(normalized);
294
313
  }
295
314
  return {
296
315
  type: "exclusive",
297
- terms
316
+ terms,
317
+ ...acceptsEmptyOf(term)
298
318
  };
299
319
  } else {
300
320
  if (term.type === "option") return {
@@ -311,10 +331,14 @@ function normalizeUsageTerm(term) {
311
331
  return { ...term };
312
332
  }
313
333
  }
334
+ function acceptsEmptyOf(term) {
335
+ return term.acceptsEmpty == null ? {} : { acceptsEmpty: term.acceptsEmpty };
336
+ }
314
337
  function isNonDegenerateTerm(term) {
315
338
  if (term.type === "option") return term.names.length > 0;
316
339
  if (term.type === "command") return term.name !== "";
317
340
  if (term.type === "argument") return term.metavar.length > 0;
341
+ if ((term.type === "multiple" || term.type === "exclusive") && term.acceptsEmpty != null) return true;
318
342
  if (term.type === "optional" || term.type === "multiple" || term.type === "exclusive" || term.type === "sequence") return term.terms.length > 0;
319
343
  return true;
320
344
  }
@@ -370,11 +394,13 @@ function cloneUsageTerm(term) {
370
394
  case "multiple": return {
371
395
  type: "multiple",
372
396
  terms: term.terms.map(cloneUsageTerm),
373
- min: term.min
397
+ min: term.min,
398
+ ...acceptsEmptyOf(term)
374
399
  };
375
400
  case "exclusive": return {
376
401
  type: "exclusive",
377
- terms: term.terms.map((u) => u.map(cloneUsageTerm))
402
+ terms: term.terms.map((u) => u.map(cloneUsageTerm)),
403
+ ...acceptsEmptyOf(term)
378
404
  };
379
405
  case "sequence": return {
380
406
  type: "sequence",
@@ -416,7 +442,8 @@ function filterUsageForDisplay(usage, isHidden = isUsageHidden) {
416
442
  if (filtered.length > 0) terms.push({
417
443
  type: "multiple",
418
444
  terms: filtered,
419
- min: term.min
445
+ min: term.min,
446
+ ...acceptsEmptyOf(term)
420
447
  });
421
448
  continue;
422
449
  }
@@ -428,7 +455,8 @@ function filterUsageForDisplay(usage, isHidden = isUsageHidden) {
428
455
  }).filter((branch) => branch.length > 0);
429
456
  if (filteredBranches.length > 0) terms.push({
430
457
  type: "exclusive",
431
- terms: filteredBranches
458
+ terms: filteredBranches,
459
+ ...acceptsEmptyOf(term)
432
460
  });
433
461
  continue;
434
462
  }
@@ -482,7 +510,7 @@ function formatUsageTerm(term, options = {}) {
482
510
  theme: cacheTerminalTheme(options.theme)
483
511
  };
484
512
  const hiddenCheck = options.context === "doc" ? isDocHidden : isUsageHidden;
485
- const visibleTerms = filterUsageForDisplay([term], hiddenCheck);
513
+ const visibleTerms = filterUsageForDisplay(resolveUsageForDisplay([term], hiddenCheck), hiddenCheck);
486
514
  if (visibleTerms.length < 1) return "";
487
515
  return wrapUsageTokens(formatUsageTermInternal(visibleTerms[0], options), options);
488
516
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.4.0-dev.2683",
3
+ "version": "1.4.0-dev.2709",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -227,6 +227,14 @@
227
227
  },
228
228
  "import": "./dist/internal/terminal.js",
229
229
  "require": "./dist/internal/terminal.cjs"
230
+ },
231
+ "./internal/usage": {
232
+ "types": {
233
+ "import": "./dist/internal/usage.d.ts",
234
+ "require": "./dist/internal/usage.d.cts"
235
+ },
236
+ "import": "./dist/internal/usage.js",
237
+ "require": "./dist/internal/usage.cjs"
230
238
  }
231
239
  },
232
240
  "imports": {
@@ -237,7 +245,7 @@
237
245
  },
238
246
  "sideEffects": false,
239
247
  "devDependencies": {
240
- "@optique/env": "1.4.0-dev.2683+bacbadf3",
248
+ "@optique/env": "1.4.0-dev.2709+c0360f12",
241
249
  "@types/node": "^24.0.0",
242
250
  "fast-check": "^4.7.0",
243
251
  "tsdown": "^0.13.0",