@optique/core 1.3.0-dev.2524 → 1.3.0-dev.2530

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 (42) hide show
  1. package/dist/doc.cjs +93 -84
  2. package/dist/doc.d.cts +12 -1
  3. package/dist/doc.d.ts +12 -1
  4. package/dist/doc.js +88 -79
  5. package/dist/facade.cjs +49 -15
  6. package/dist/facade.d.cts +12 -1
  7. package/dist/facade.d.ts +12 -1
  8. package/dist/facade.js +48 -14
  9. package/dist/index.cjs +3 -0
  10. package/dist/index.d.cts +3 -2
  11. package/dist/index.d.ts +3 -2
  12. package/dist/index.js +3 -2
  13. package/dist/internal/terminal.cjs +7 -0
  14. package/dist/internal/terminal.d.cts +4 -0
  15. package/dist/internal/terminal.d.ts +4 -0
  16. package/dist/internal/terminal.js +5 -0
  17. package/dist/message-registry.cjs +6 -0
  18. package/dist/message-registry.d.cts +7 -0
  19. package/dist/message-registry.d.ts +7 -0
  20. package/dist/message-registry.js +5 -0
  21. package/dist/message-renderer.cjs +26 -0
  22. package/dist/message-renderer.d.cts +19 -0
  23. package/dist/message-renderer.d.ts +19 -0
  24. package/dist/message-renderer.js +26 -0
  25. package/dist/message.cjs +49 -85
  26. package/dist/message.d.cts +31 -1
  27. package/dist/message.d.ts +31 -1
  28. package/dist/message.js +49 -86
  29. package/dist/terminal-internal.cjs +259 -0
  30. package/dist/terminal-internal.d.cts +36 -0
  31. package/dist/terminal-internal.d.ts +36 -0
  32. package/dist/terminal-internal.js +253 -0
  33. package/dist/terminal.cjs +74 -0
  34. package/dist/terminal.d.cts +129 -0
  35. package/dist/terminal.d.ts +129 -0
  36. package/dist/terminal.js +73 -0
  37. package/dist/usage.cjs +108 -134
  38. package/dist/usage.d.cts +6 -0
  39. package/dist/usage.d.ts +6 -0
  40. package/dist/usage.js +108 -134
  41. package/package.json +18 -2
  42. package/skills/optique/SKILL.md +3 -3
package/dist/usage.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  const require_displaywidth = require('./displaywidth.cjs');
2
+ const require_terminal_internal = require('./terminal-internal.cjs');
2
3
  const require_validate = require('./validate.cjs');
3
4
 
4
5
  //#region src/usage.ts
@@ -182,6 +183,10 @@ function extractArgumentMetavars(usage) {
182
183
  * whitespace-only, or contains control characters.
183
184
  */
184
185
  function formatUsage(programName, usage, options = {}) {
186
+ options = {
187
+ ...options,
188
+ theme: require_terminal_internal.cacheTerminalTheme(options.theme)
189
+ };
185
190
  require_validate.validateProgramName(programName);
186
191
  usage = normalizeUsage(filterUsageForDisplay(usage));
187
192
  if (options.expandCommands) {
@@ -197,29 +202,23 @@ function formatUsage(programName, usage, options = {}) {
197
202
  if (lines.length > 0) return lines.join("\n");
198
203
  }
199
204
  }
200
- let output = options.colors ? `\x1b[1m${programName}\x1b[0m` : programName;
201
- let lineWidth = require_displaywidth.getDisplayWidth(programName);
202
- let first = true;
203
- for (const { text, width } of formatUsageTerms(usage, options)) {
204
- if (first) {
205
- first = false;
206
- if (options.maxWidth != null && lineWidth + 1 + width > options.maxWidth) {
207
- output += "\n";
208
- lineWidth = 0;
209
- } else {
210
- output += " ";
211
- lineWidth += 1;
212
- }
213
- } else if (options.maxWidth != null && lineWidth > 0 && lineWidth + width > options.maxWidth) {
214
- if (output.endsWith(" ")) output = output.slice(0, -1);
215
- output += "\n";
216
- lineWidth = 0;
217
- if (text === " ") continue;
218
- }
219
- output += text;
220
- lineWidth += width;
221
- }
222
- return output;
205
+ const programTokens = layoutUsageTokens(require_terminal_internal.fragmentTokens(require_terminal_internal.formatTerminalTerm({
206
+ type: "programName",
207
+ programName
208
+ }, options.theme)), options);
209
+ const terms = [...formatUsageTerms(usage, options)];
210
+ if (terms.length === 0) return require_terminal_internal.serializeTokens(programTokens, options.colors);
211
+ const width = programTokens.reduce((n, token) => token.width === -1 ? 0 : n + token.width, 0);
212
+ const separator = options.maxWidth != null && width + 1 + terms[0].width > options.maxWidth ? {
213
+ text: "\n",
214
+ width: -1,
215
+ scopes: []
216
+ } : {
217
+ text: " ",
218
+ width: 1,
219
+ scopes: []
220
+ };
221
+ return require_terminal_internal.serializeTokens([...programTokens, separator], options.colors) + wrapUsageTokens(terms, options, separator.width === -1 ? 0 : width + 1);
223
222
  }
224
223
  /**
225
224
  * Normalizes a usage description by flattening nested exclusive terms,
@@ -454,7 +453,8 @@ function* formatUsageTerms(terms, options) {
454
453
  for (const t of terms) {
455
454
  if (i > 0) yield {
456
455
  text: " ",
457
- width: 1
456
+ width: 1,
457
+ scopes: []
458
458
  };
459
459
  yield* formatUsageTermInternal(t, options);
460
460
  i++;
@@ -470,139 +470,113 @@ function* formatUsageTerms(terms, options) {
470
470
  * @returns A formatted string representation of the usage term.
471
471
  */
472
472
  function formatUsageTerm(term, options = {}) {
473
+ options = {
474
+ ...options,
475
+ theme: require_terminal_internal.cacheTerminalTheme(options.theme)
476
+ };
473
477
  const hiddenCheck = options.context === "doc" ? isDocHidden : isUsageHidden;
474
478
  const visibleTerms = filterUsageForDisplay([term], hiddenCheck);
475
479
  if (visibleTerms.length < 1) return "";
476
- let lineWidth = 0;
477
- let output = "";
478
- for (const { text, width } of formatUsageTermInternal(visibleTerms[0], options)) {
479
- if (options.maxWidth != null && lineWidth > 0 && lineWidth + width > options.maxWidth) {
480
- if (output.endsWith(" ")) output = output.slice(0, -1);
481
- output += "\n";
480
+ return wrapUsageTokens(formatUsageTermInternal(visibleTerms[0], options), options);
481
+ }
482
+ function wrapUsageTokens(input, options, initialWidth = 0) {
483
+ return require_terminal_internal.serializeTokens(layoutUsageTokens(input, options, initialWidth), options.colors);
484
+ }
485
+ function layoutUsageTokens(input, options, initialWidth = 0) {
486
+ let lineWidth = initialWidth;
487
+ const output = [];
488
+ for (const token of input) {
489
+ if (options.maxWidth != null && lineWidth > 0 && lineWidth + token.width > options.maxWidth) {
490
+ const last = output.at(-1);
491
+ if (last?.text.endsWith(" ") && (!options.colors || last.scopes.length === 0)) output[output.length - 1] = {
492
+ ...last,
493
+ text: last.text.slice(0, -1),
494
+ width: last.width - 1
495
+ };
496
+ output.push({
497
+ text: "\n",
498
+ width: -1,
499
+ scopes: []
500
+ });
482
501
  lineWidth = 0;
483
- if (text === " ") continue;
502
+ if (token.text === " ") continue;
484
503
  }
485
- output += text;
486
- lineWidth += width;
504
+ output.push(token);
505
+ lineWidth += token.width;
487
506
  }
488
507
  return output;
489
508
  }
490
509
  function* formatUsageTermInternal(term, options) {
491
- const optionsSeparator = options.optionsSeparator ?? "/";
492
- if (term.type === "argument") yield {
493
- text: options?.colors ? `\x1b[4m${term.metavar}\x1b[0m` : term.metavar,
494
- width: require_displaywidth.getDisplayWidth(term.metavar)
510
+ function leaf(t, usage) {
511
+ return require_terminal_internal.fragmentTokens(require_terminal_internal.formatTerminalTerm(t, options.theme, false, usage));
512
+ }
513
+ function punctuation(punctuation$1, kind) {
514
+ return leaf({
515
+ type: "syntaxPunctuation",
516
+ punctuation: punctuation$1,
517
+ kind
518
+ });
519
+ }
520
+ const space = {
521
+ text: " ",
522
+ width: 1,
523
+ scopes: []
495
524
  };
496
- else if (term.type === "option") if (options?.onlyShortestOptions) {
497
- const shortestName = term.names.reduce((a, b) => require_displaywidth.getDisplayWidth(a) <= require_displaywidth.getDisplayWidth(b) ? a : b);
498
- yield {
499
- text: options?.colors ? `\x1b[3m${shortestName}\x1b[0m` : shortestName,
500
- width: require_displaywidth.getDisplayWidth(shortestName)
501
- };
502
- } else {
503
- let i = 0;
504
- for (const optionName of term.names) {
505
- if (i > 0) yield {
506
- text: options?.colors ? `\x1b[2m${optionsSeparator}\x1b[0m` : optionsSeparator,
507
- width: require_displaywidth.getDisplayWidth(optionsSeparator)
508
- };
509
- yield {
510
- text: options?.colors ? `\x1b[3m${optionName}\x1b[0m` : optionName,
511
- width: require_displaywidth.getDisplayWidth(optionName)
512
- };
513
- i++;
525
+ if (term.type === "argument") yield* leaf({
526
+ type: "metavar",
527
+ metavar: term.metavar
528
+ }, "argument");
529
+ else if (term.type === "option") {
530
+ const names = options.onlyShortestOptions ? [term.names.reduce((a, b) => require_displaywidth.getDisplayWidth(a) <= require_displaywidth.getDisplayWidth(b) ? a : b)] : term.names;
531
+ for (let i = 0; i < names.length; i++) {
532
+ if (i > 0) yield* punctuation(options.optionsSeparator ?? "/", "optionSeparator");
533
+ yield* leaf({
534
+ type: "optionName",
535
+ optionName: names[i]
536
+ });
514
537
  }
515
- if (term.metavar != null) {
516
- yield {
517
- text: " ",
518
- width: 1
519
- };
520
- yield {
521
- text: options?.colors ? `\x1b[4m\x1b[2m${term.metavar}\x1b[0m` : term.metavar,
522
- width: require_displaywidth.getDisplayWidth(term.metavar)
523
- };
538
+ if (!options.onlyShortestOptions && term.metavar != null) {
539
+ yield space;
540
+ yield* leaf({
541
+ type: "metavar",
542
+ metavar: term.metavar
543
+ }, "optionValue");
524
544
  }
525
- }
526
- else if (term.type === "command") yield {
527
- text: options?.colors ? `\x1b[1m${term.name}\x1b[0m` : term.name,
528
- width: require_displaywidth.getDisplayWidth(term.name)
529
- };
545
+ } else if (term.type === "command") yield* leaf({
546
+ type: "optionName",
547
+ optionName: term.name
548
+ }, "command");
530
549
  else if (term.type === "optional") {
531
- yield {
532
- text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
533
- width: 1
534
- };
550
+ yield* punctuation("[", "optionalOpen");
535
551
  yield* formatUsageTerms(term.terms, options);
536
- yield {
537
- text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
538
- width: 1
539
- };
552
+ yield* punctuation("]", "optionalClose");
540
553
  } else if (term.type === "exclusive") {
541
- yield {
542
- text: options?.colors ? `\x1b[2m(\x1b[0m` : "(",
543
- width: 1
544
- };
545
- let i = 0;
546
- for (const termGroup of term.terms) {
554
+ yield* punctuation("(", "groupOpen");
555
+ for (let i = 0; i < term.terms.length; i++) {
547
556
  if (i > 0) {
548
- yield {
549
- text: " ",
550
- width: 1
551
- };
552
- yield {
553
- text: "|",
554
- width: 1
555
- };
556
- yield {
557
- text: " ",
558
- width: 1
559
- };
557
+ yield space;
558
+ yield* punctuation("|", "alternative");
559
+ yield space;
560
560
  }
561
- yield* formatUsageTerms(termGroup, options);
562
- i++;
561
+ yield* formatUsageTerms(term.terms[i], options);
563
562
  }
564
- yield {
565
- text: options?.colors ? `\x1b[2m)\x1b[0m` : ")",
566
- width: 1
567
- };
563
+ yield* punctuation(")", "groupClose");
568
564
  } else if (term.type === "sequence") yield* formatUsageTerms(term.terms, options);
569
565
  else if (term.type === "multiple") {
570
- if (term.min < 1) yield {
571
- text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
572
- width: 1
573
- };
566
+ if (term.min < 1) yield* punctuation("[", "optionalOpen");
574
567
  for (let i = 0; i < Math.max(1, term.min); i++) {
575
- if (i > 0) yield {
576
- text: " ",
577
- width: 1
578
- };
568
+ if (i > 0) yield space;
579
569
  yield* formatUsageTerms(term.terms, options);
580
570
  }
581
- yield {
582
- text: options?.colors ? `\x1b[2m...\x1b[0m` : "...",
583
- width: 3
584
- };
585
- if (term.min < 1) yield {
586
- text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
587
- width: 1
588
- };
589
- } else if (term.type === "literal") yield {
590
- text: term.value,
591
- width: require_displaywidth.getDisplayWidth(term.value)
592
- };
593
- else if (term.type === "passthrough") {
594
- const text = "[...]";
595
- yield {
596
- text: options?.colors ? `\x1b[2m${text}\x1b[0m` : text,
597
- width: 5
598
- };
599
- } else if (term.type === "ellipsis") {
600
- const text = "...";
601
- yield {
602
- text: options?.colors ? `\x1b[2m${text}\x1b[0m` : text,
603
- width: 3
604
- };
605
- } else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
571
+ yield* punctuation("...", "ellipsis");
572
+ if (term.min < 1) yield* punctuation("]", "optionalClose");
573
+ } else if (term.type === "literal") yield* leaf({
574
+ type: "value",
575
+ value: term.value
576
+ }, "literal");
577
+ else if (term.type === "passthrough") yield* punctuation("[...]", "passthrough");
578
+ else if (term.type === "ellipsis") yield* punctuation("...", "ellipsis");
579
+ else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
606
580
  }
607
581
 
608
582
  //#endregion
package/dist/usage.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { NonEmptyString } from "./nonempty.cjs";
2
+ import { TerminalTheme } from "./terminal.cjs";
2
3
 
3
4
  //#region src/usage.d.ts
4
5
 
@@ -340,6 +341,11 @@ declare function extractArgumentMetavars(usage: Usage): Set<string>;
340
341
  * Options for formatting usage descriptions.
341
342
  */
342
343
  interface UsageFormatOptions {
344
+ /**
345
+ * Semantic terminal theme.
346
+ * @since 1.3.0
347
+ */
348
+ readonly theme?: TerminalTheme;
343
349
  /**
344
350
  * When `true`, expands commands in the usage description
345
351
  * to multiple lines, showing each command on a new line.
package/dist/usage.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { NonEmptyString } from "./nonempty.js";
2
+ import { TerminalTheme } from "./terminal.js";
2
3
 
3
4
  //#region src/usage.d.ts
4
5
 
@@ -340,6 +341,11 @@ declare function extractArgumentMetavars(usage: Usage): Set<string>;
340
341
  * Options for formatting usage descriptions.
341
342
  */
342
343
  interface UsageFormatOptions {
344
+ /**
345
+ * Semantic terminal theme.
346
+ * @since 1.3.0
347
+ */
348
+ readonly theme?: TerminalTheme;
343
349
  /**
344
350
  * When `true`, expands commands in the usage description
345
351
  * to multiple lines, showing each command on a new line.
package/dist/usage.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { getDisplayWidth } from "./displaywidth.js";
2
+ import { cacheTerminalTheme, formatTerminalTerm, fragmentTokens, serializeTokens } from "./terminal-internal.js";
2
3
  import { validateProgramName } from "./validate.js";
3
4
 
4
5
  //#region src/usage.ts
@@ -182,6 +183,10 @@ function extractArgumentMetavars(usage) {
182
183
  * whitespace-only, or contains control characters.
183
184
  */
184
185
  function formatUsage(programName, usage, options = {}) {
186
+ options = {
187
+ ...options,
188
+ theme: cacheTerminalTheme(options.theme)
189
+ };
185
190
  validateProgramName(programName);
186
191
  usage = normalizeUsage(filterUsageForDisplay(usage));
187
192
  if (options.expandCommands) {
@@ -197,29 +202,23 @@ function formatUsage(programName, usage, options = {}) {
197
202
  if (lines.length > 0) return lines.join("\n");
198
203
  }
199
204
  }
200
- let output = options.colors ? `\x1b[1m${programName}\x1b[0m` : programName;
201
- let lineWidth = getDisplayWidth(programName);
202
- let first = true;
203
- for (const { text, width } of formatUsageTerms(usage, options)) {
204
- if (first) {
205
- first = false;
206
- if (options.maxWidth != null && lineWidth + 1 + width > options.maxWidth) {
207
- output += "\n";
208
- lineWidth = 0;
209
- } else {
210
- output += " ";
211
- lineWidth += 1;
212
- }
213
- } else if (options.maxWidth != null && lineWidth > 0 && lineWidth + width > options.maxWidth) {
214
- if (output.endsWith(" ")) output = output.slice(0, -1);
215
- output += "\n";
216
- lineWidth = 0;
217
- if (text === " ") continue;
218
- }
219
- output += text;
220
- lineWidth += width;
221
- }
222
- return output;
205
+ const programTokens = layoutUsageTokens(fragmentTokens(formatTerminalTerm({
206
+ type: "programName",
207
+ programName
208
+ }, options.theme)), options);
209
+ const terms = [...formatUsageTerms(usage, options)];
210
+ if (terms.length === 0) return serializeTokens(programTokens, options.colors);
211
+ const width = programTokens.reduce((n, token) => token.width === -1 ? 0 : n + token.width, 0);
212
+ const separator = options.maxWidth != null && width + 1 + terms[0].width > options.maxWidth ? {
213
+ text: "\n",
214
+ width: -1,
215
+ scopes: []
216
+ } : {
217
+ text: " ",
218
+ width: 1,
219
+ scopes: []
220
+ };
221
+ return serializeTokens([...programTokens, separator], options.colors) + wrapUsageTokens(terms, options, separator.width === -1 ? 0 : width + 1);
223
222
  }
224
223
  /**
225
224
  * Normalizes a usage description by flattening nested exclusive terms,
@@ -454,7 +453,8 @@ function* formatUsageTerms(terms, options) {
454
453
  for (const t of terms) {
455
454
  if (i > 0) yield {
456
455
  text: " ",
457
- width: 1
456
+ width: 1,
457
+ scopes: []
458
458
  };
459
459
  yield* formatUsageTermInternal(t, options);
460
460
  i++;
@@ -470,139 +470,113 @@ function* formatUsageTerms(terms, options) {
470
470
  * @returns A formatted string representation of the usage term.
471
471
  */
472
472
  function formatUsageTerm(term, options = {}) {
473
+ options = {
474
+ ...options,
475
+ theme: cacheTerminalTheme(options.theme)
476
+ };
473
477
  const hiddenCheck = options.context === "doc" ? isDocHidden : isUsageHidden;
474
478
  const visibleTerms = filterUsageForDisplay([term], hiddenCheck);
475
479
  if (visibleTerms.length < 1) return "";
476
- let lineWidth = 0;
477
- let output = "";
478
- for (const { text, width } of formatUsageTermInternal(visibleTerms[0], options)) {
479
- if (options.maxWidth != null && lineWidth > 0 && lineWidth + width > options.maxWidth) {
480
- if (output.endsWith(" ")) output = output.slice(0, -1);
481
- output += "\n";
480
+ return wrapUsageTokens(formatUsageTermInternal(visibleTerms[0], options), options);
481
+ }
482
+ function wrapUsageTokens(input, options, initialWidth = 0) {
483
+ return serializeTokens(layoutUsageTokens(input, options, initialWidth), options.colors);
484
+ }
485
+ function layoutUsageTokens(input, options, initialWidth = 0) {
486
+ let lineWidth = initialWidth;
487
+ const output = [];
488
+ for (const token of input) {
489
+ if (options.maxWidth != null && lineWidth > 0 && lineWidth + token.width > options.maxWidth) {
490
+ const last = output.at(-1);
491
+ if (last?.text.endsWith(" ") && (!options.colors || last.scopes.length === 0)) output[output.length - 1] = {
492
+ ...last,
493
+ text: last.text.slice(0, -1),
494
+ width: last.width - 1
495
+ };
496
+ output.push({
497
+ text: "\n",
498
+ width: -1,
499
+ scopes: []
500
+ });
482
501
  lineWidth = 0;
483
- if (text === " ") continue;
502
+ if (token.text === " ") continue;
484
503
  }
485
- output += text;
486
- lineWidth += width;
504
+ output.push(token);
505
+ lineWidth += token.width;
487
506
  }
488
507
  return output;
489
508
  }
490
509
  function* formatUsageTermInternal(term, options) {
491
- const optionsSeparator = options.optionsSeparator ?? "/";
492
- if (term.type === "argument") yield {
493
- text: options?.colors ? `\x1b[4m${term.metavar}\x1b[0m` : term.metavar,
494
- width: getDisplayWidth(term.metavar)
510
+ function leaf(t, usage) {
511
+ return fragmentTokens(formatTerminalTerm(t, options.theme, false, usage));
512
+ }
513
+ function punctuation(punctuation$1, kind) {
514
+ return leaf({
515
+ type: "syntaxPunctuation",
516
+ punctuation: punctuation$1,
517
+ kind
518
+ });
519
+ }
520
+ const space = {
521
+ text: " ",
522
+ width: 1,
523
+ scopes: []
495
524
  };
496
- else if (term.type === "option") if (options?.onlyShortestOptions) {
497
- const shortestName = term.names.reduce((a, b) => getDisplayWidth(a) <= getDisplayWidth(b) ? a : b);
498
- yield {
499
- text: options?.colors ? `\x1b[3m${shortestName}\x1b[0m` : shortestName,
500
- width: getDisplayWidth(shortestName)
501
- };
502
- } else {
503
- let i = 0;
504
- for (const optionName of term.names) {
505
- if (i > 0) yield {
506
- text: options?.colors ? `\x1b[2m${optionsSeparator}\x1b[0m` : optionsSeparator,
507
- width: getDisplayWidth(optionsSeparator)
508
- };
509
- yield {
510
- text: options?.colors ? `\x1b[3m${optionName}\x1b[0m` : optionName,
511
- width: getDisplayWidth(optionName)
512
- };
513
- i++;
525
+ if (term.type === "argument") yield* leaf({
526
+ type: "metavar",
527
+ metavar: term.metavar
528
+ }, "argument");
529
+ else if (term.type === "option") {
530
+ const names = options.onlyShortestOptions ? [term.names.reduce((a, b) => getDisplayWidth(a) <= getDisplayWidth(b) ? a : b)] : term.names;
531
+ for (let i = 0; i < names.length; i++) {
532
+ if (i > 0) yield* punctuation(options.optionsSeparator ?? "/", "optionSeparator");
533
+ yield* leaf({
534
+ type: "optionName",
535
+ optionName: names[i]
536
+ });
514
537
  }
515
- if (term.metavar != null) {
516
- yield {
517
- text: " ",
518
- width: 1
519
- };
520
- yield {
521
- text: options?.colors ? `\x1b[4m\x1b[2m${term.metavar}\x1b[0m` : term.metavar,
522
- width: getDisplayWidth(term.metavar)
523
- };
538
+ if (!options.onlyShortestOptions && term.metavar != null) {
539
+ yield space;
540
+ yield* leaf({
541
+ type: "metavar",
542
+ metavar: term.metavar
543
+ }, "optionValue");
524
544
  }
525
- }
526
- else if (term.type === "command") yield {
527
- text: options?.colors ? `\x1b[1m${term.name}\x1b[0m` : term.name,
528
- width: getDisplayWidth(term.name)
529
- };
545
+ } else if (term.type === "command") yield* leaf({
546
+ type: "optionName",
547
+ optionName: term.name
548
+ }, "command");
530
549
  else if (term.type === "optional") {
531
- yield {
532
- text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
533
- width: 1
534
- };
550
+ yield* punctuation("[", "optionalOpen");
535
551
  yield* formatUsageTerms(term.terms, options);
536
- yield {
537
- text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
538
- width: 1
539
- };
552
+ yield* punctuation("]", "optionalClose");
540
553
  } else if (term.type === "exclusive") {
541
- yield {
542
- text: options?.colors ? `\x1b[2m(\x1b[0m` : "(",
543
- width: 1
544
- };
545
- let i = 0;
546
- for (const termGroup of term.terms) {
554
+ yield* punctuation("(", "groupOpen");
555
+ for (let i = 0; i < term.terms.length; i++) {
547
556
  if (i > 0) {
548
- yield {
549
- text: " ",
550
- width: 1
551
- };
552
- yield {
553
- text: "|",
554
- width: 1
555
- };
556
- yield {
557
- text: " ",
558
- width: 1
559
- };
557
+ yield space;
558
+ yield* punctuation("|", "alternative");
559
+ yield space;
560
560
  }
561
- yield* formatUsageTerms(termGroup, options);
562
- i++;
561
+ yield* formatUsageTerms(term.terms[i], options);
563
562
  }
564
- yield {
565
- text: options?.colors ? `\x1b[2m)\x1b[0m` : ")",
566
- width: 1
567
- };
563
+ yield* punctuation(")", "groupClose");
568
564
  } else if (term.type === "sequence") yield* formatUsageTerms(term.terms, options);
569
565
  else if (term.type === "multiple") {
570
- if (term.min < 1) yield {
571
- text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
572
- width: 1
573
- };
566
+ if (term.min < 1) yield* punctuation("[", "optionalOpen");
574
567
  for (let i = 0; i < Math.max(1, term.min); i++) {
575
- if (i > 0) yield {
576
- text: " ",
577
- width: 1
578
- };
568
+ if (i > 0) yield space;
579
569
  yield* formatUsageTerms(term.terms, options);
580
570
  }
581
- yield {
582
- text: options?.colors ? `\x1b[2m...\x1b[0m` : "...",
583
- width: 3
584
- };
585
- if (term.min < 1) yield {
586
- text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
587
- width: 1
588
- };
589
- } else if (term.type === "literal") yield {
590
- text: term.value,
591
- width: getDisplayWidth(term.value)
592
- };
593
- else if (term.type === "passthrough") {
594
- const text = "[...]";
595
- yield {
596
- text: options?.colors ? `\x1b[2m${text}\x1b[0m` : text,
597
- width: 5
598
- };
599
- } else if (term.type === "ellipsis") {
600
- const text = "...";
601
- yield {
602
- text: options?.colors ? `\x1b[2m${text}\x1b[0m` : text,
603
- width: 3
604
- };
605
- } else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
571
+ yield* punctuation("...", "ellipsis");
572
+ if (term.min < 1) yield* punctuation("]", "optionalClose");
573
+ } else if (term.type === "literal") yield* leaf({
574
+ type: "value",
575
+ value: term.value
576
+ }, "literal");
577
+ else if (term.type === "passthrough") yield* punctuation("[...]", "passthrough");
578
+ else if (term.type === "ellipsis") yield* punctuation("...", "ellipsis");
579
+ else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
606
580
  }
607
581
 
608
582
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.3.0-dev.2524",
3
+ "version": "1.3.0-dev.2530",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -211,6 +211,22 @@
211
211
  },
212
212
  "import": "./dist/valueparser.js",
213
213
  "require": "./dist/valueparser.cjs"
214
+ },
215
+ "./terminal": {
216
+ "types": {
217
+ "import": "./dist/terminal.d.ts",
218
+ "require": "./dist/terminal.d.cts"
219
+ },
220
+ "import": "./dist/terminal.js",
221
+ "require": "./dist/terminal.cjs"
222
+ },
223
+ "./internal/terminal": {
224
+ "types": {
225
+ "import": "./dist/internal/terminal.d.ts",
226
+ "require": "./dist/internal/terminal.d.cts"
227
+ },
228
+ "import": "./dist/internal/terminal.js",
229
+ "require": "./dist/internal/terminal.cjs"
214
230
  }
215
231
  },
216
232
  "imports": {
@@ -221,7 +237,7 @@
221
237
  },
222
238
  "sideEffects": false,
223
239
  "devDependencies": {
224
- "@optique/env": "1.3.0-dev.2524+9fa9a0d7",
240
+ "@optique/env": "1.3.0-dev.2530+970f0e16",
225
241
  "@types/node": "^24.0.0",
226
242
  "fast-check": "^4.7.0",
227
243
  "tsdown": "^0.13.0",