@optique/core 1.3.0-dev.2529 → 1.3.0-dev.2532

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 +268 -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 +262 -0
  33. package/dist/terminal.cjs +73 -0
  34. package/dist/terminal.d.cts +129 -0
  35. package/dist/terminal.d.ts +129 -0
  36. package/dist/terminal.js +72 -0
  37. package/dist/usage.cjs +110 -134
  38. package/dist/usage.d.cts +8 -0
  39. package/dist/usage.d.ts +8 -0
  40. package/dist/usage.js +110 -134
  41. package/package.json +18 -2
  42. package/skills/optique/SKILL.md +3 -3
@@ -0,0 +1,262 @@
1
+ import { getDisplayWidth } from "./displaywidth.js";
2
+ import { defaultTerminalTheme } from "./terminal.js";
3
+
4
+ //#region src/terminal-internal.ts
5
+ function terminalText(text) {
6
+ return {
7
+ type: "text",
8
+ text
9
+ };
10
+ }
11
+ function valuesFormatter(theme) {
12
+ if (theme.values != null && theme.values !== defaultTerminalTheme.values) return theme.values;
13
+ if (theme.value != null && theme.value !== defaultTerminalTheme.value) return defaultTerminalTheme.values;
14
+ return (term, context) => ({
15
+ type: "style",
16
+ style: { foreground: "green" },
17
+ children: [defaultTerminalTheme.values(term, context)]
18
+ });
19
+ }
20
+ function formatTerminalTerm(term, theme = {}, quotes = false, usage) {
21
+ const quote = (s) => quotes ? `\`${s}\`` : s;
22
+ let text;
23
+ switch (term.type) {
24
+ case "text": return terminalText(term.text);
25
+ case "lineBreak": throw new TypeError("Hard breaks must be rendered at the message level.");
26
+ case "optionName":
27
+ text = quote(term.optionName);
28
+ break;
29
+ case "optionNames":
30
+ text = term.optionNames.map(quote).join("/");
31
+ break;
32
+ case "metavar":
33
+ text = quote(term.metavar);
34
+ break;
35
+ case "value":
36
+ text = quotes ? JSON.stringify(term.value) : term.value;
37
+ break;
38
+ case "values":
39
+ text = term.values.map((v) => quotes ? JSON.stringify(v) : v).join(" ");
40
+ break;
41
+ case "envVar":
42
+ text = quote(term.envVar);
43
+ break;
44
+ case "commandLine":
45
+ text = quote(term.commandLine);
46
+ break;
47
+ case "url":
48
+ text = quotes ? `<${term.url.href}>` : term.url.href;
49
+ break;
50
+ case "programName":
51
+ text = term.programName;
52
+ break;
53
+ case "label":
54
+ case "errorLabel":
55
+ text = term.label;
56
+ break;
57
+ case "syntaxPunctuation":
58
+ text = term.punctuation;
59
+ break;
60
+ default: throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
61
+ }
62
+ const ctx = {
63
+ quotes,
64
+ text,
65
+ usage,
66
+ format: (child) => formatTerminalTerm(child, theme, quotes, usage)
67
+ };
68
+ switch (term.type) {
69
+ case "optionName": return (theme.optionName ?? defaultTerminalTheme.optionName)(term, ctx);
70
+ case "optionNames": return (theme.optionNames ?? defaultTerminalTheme.optionNames)(term, ctx);
71
+ case "metavar": return (theme.metavar ?? defaultTerminalTheme.metavar)(term, ctx);
72
+ case "value": return (theme.value ?? defaultTerminalTheme.value)(term, ctx);
73
+ case "values": return valuesFormatter(theme)(term, ctx);
74
+ case "envVar": return (theme.envVar ?? defaultTerminalTheme.envVar)(term, ctx);
75
+ case "commandLine": return (theme.commandLine ?? defaultTerminalTheme.commandLine)(term, ctx);
76
+ case "url": return (theme.url ?? defaultTerminalTheme.url)(term, ctx);
77
+ case "programName": return (theme.programName ?? defaultTerminalTheme.programName)(term, ctx);
78
+ case "label": return (theme.label ?? defaultTerminalTheme.label)(term, ctx);
79
+ case "syntaxPunctuation": return (theme.syntaxPunctuation ?? defaultTerminalTheme.syntaxPunctuation)(term, ctx);
80
+ case "errorLabel": return (theme.errorLabel ?? defaultTerminalTheme.errorLabel)(term, ctx);
81
+ }
82
+ }
83
+ const colorNames = [
84
+ "black",
85
+ "red",
86
+ "green",
87
+ "yellow",
88
+ "blue",
89
+ "magenta",
90
+ "cyan",
91
+ "white",
92
+ "brightBlack",
93
+ "brightRed",
94
+ "brightGreen",
95
+ "brightYellow",
96
+ "brightBlue",
97
+ "brightMagenta",
98
+ "brightCyan",
99
+ "brightWhite"
100
+ ];
101
+ function colorCode(color, background) {
102
+ const channel = (n) => {
103
+ if (!Number.isInteger(n) || n < 0 || n > 255) throw new RangeError("Terminal color channels must be integers from 0 to 255.");
104
+ return n;
105
+ };
106
+ if (typeof color === "string") {
107
+ const i = colorNames.indexOf(color);
108
+ if (i < 0) throw new RangeError("Unknown terminal color.");
109
+ return String((background ? 40 : 30) + (i >= 8 ? 60 + i - 8 : i));
110
+ }
111
+ const prefix = background ? 48 : 38;
112
+ if ("index" in color) return `${prefix};5;${channel(color.index)}`;
113
+ return `${prefix};2;${color.map(channel).join(";")}`;
114
+ }
115
+ function styleCode(style) {
116
+ const codes = [];
117
+ if (style.bold) codes.push(1);
118
+ if (style.dim) codes.push(2);
119
+ if (style.italic) codes.push(3);
120
+ if (style.underline) codes.push(4);
121
+ if (style.foreground != null) codes.push(colorCode(style.foreground, false));
122
+ if (style.background != null) codes.push(colorCode(style.background, true));
123
+ return codes.length ? `\x1b[${codes.join(";")}m` : "";
124
+ }
125
+ function sameStyle(a, b) {
126
+ return a.bold === b.bold && a.dim === b.dim && a.italic === b.italic && a.underline === b.underline && JSON.stringify(a.foreground) === JSON.stringify(b.foreground) && JSON.stringify(a.background) === JSON.stringify(b.background);
127
+ }
128
+ function* fragmentTokens(fragment, scopes = [], inherited = {}) {
129
+ if (fragment.type === "text") {
130
+ yield {
131
+ text: fragment.text,
132
+ width: getDisplayWidth(fragment.text),
133
+ scopes
134
+ };
135
+ return;
136
+ }
137
+ let next = scopes;
138
+ let style = inherited;
139
+ if (fragment.type === "style") {
140
+ styleCode(fragment.style);
141
+ style = {
142
+ ...inherited,
143
+ ...fragment.style
144
+ };
145
+ if (!sameStyle(style, inherited)) next = [...scopes, {
146
+ kind: "style",
147
+ style: fragment.style
148
+ }];
149
+ } else if (fragment.type === "link") next = [...scopes, {
150
+ kind: "link",
151
+ href: fragment.href
152
+ }];
153
+ for (const child of fragment.children) yield* fragmentTokens(child, next, style);
154
+ }
155
+ function serializeTokens(tokens, colors = false, resetSuffix = "", ambient = {}) {
156
+ let output = "";
157
+ let active = [];
158
+ function transition(next) {
159
+ let common = 0;
160
+ while (common < active.length && common < next.length && active[common] === next[common]) common++;
161
+ const surviving = active.slice(0, common);
162
+ const restored = surviving.reduce((style, scope) => ({
163
+ ...style,
164
+ ...scope.style
165
+ }), ambient);
166
+ let reset = false;
167
+ let closedLink = false;
168
+ for (let i = active.length - 1; i >= common; i--) if (active[i].kind === "link") {
169
+ output += `\x1b]8;;\x1b\\${resetSuffix}${styleCode(restored)}`;
170
+ closedLink = true;
171
+ } else reset = true;
172
+ active = surviving;
173
+ if (closedLink) {
174
+ const parentLink = active.findLast((scope) => scope.kind === "link");
175
+ if (parentLink != null) output += `\x1b]8;;${parentLink.href}\x1b\\`;
176
+ }
177
+ if (reset) output += "\x1B[0m" + resetSuffix + styleCode(restored);
178
+ for (const scope of next.slice(common)) {
179
+ if (scope.kind === "link") output += `\x1b]8;;${scope.href}\x1b\\`;
180
+ else if (scope.style) if (Object.values(scope.style).includes(false)) {
181
+ const inherited = active.reduce((s, x) => ({
182
+ ...s,
183
+ ...x.style
184
+ }), ambient);
185
+ output += "\x1B[0m" + resetSuffix + styleCode({
186
+ ...inherited,
187
+ ...scope.style
188
+ });
189
+ } else output += styleCode(scope.style);
190
+ active = [...active, scope];
191
+ }
192
+ }
193
+ const items = [...tokens];
194
+ for (let i = 0; i < items.length; i++) {
195
+ const token = items[i];
196
+ if (colors && token.width === -1) {
197
+ const next = items.slice(i + 1).find((t) => t.width !== -1)?.scopes ?? [];
198
+ let common = 0;
199
+ while (common < active.length && common < next.length && active[common] === next[common]) common++;
200
+ transition(active.slice(0, common));
201
+ } else if (colors) transition(token.scopes);
202
+ output += token.text;
203
+ }
204
+ if (colors) transition([]);
205
+ return output;
206
+ }
207
+ /**
208
+ * Serializes one semantic leaf for a terminal surface outside a message.
209
+ * @param term The semantic leaf to render.
210
+ * @param theme Optional overrides for the default terminal theme.
211
+ * @param colors Whether to emit styles and hyperlinks.
212
+ * @param usage The leaf's usage context, when applicable.
213
+ * @param ambient The style surrounding the rendered leaf.
214
+ * @returns The leaf's styled or plain representation.
215
+ * @throws {TypeError} If the term is a hard break or has an unknown type.
216
+ * @throws {RangeError} If a color channel or palette index is invalid.
217
+ * @since 1.3.0
218
+ * @internal
219
+ */
220
+ function renderTerminalTerm(term, theme, colors, usage, ambient) {
221
+ return serializeTokens(fragmentTokens(formatTerminalTerm(term, theme, false, usage)), colors, "", ambient);
222
+ }
223
+ /** Memoizes semantic leaves for one layout pass, including measurement. */
224
+ function cacheTerminalTheme(theme = {}) {
225
+ function cache(formatter) {
226
+ const fragments = /* @__PURE__ */ new Map();
227
+ return (term, context) => {
228
+ const key = JSON.stringify([
229
+ term,
230
+ context.quotes,
231
+ context.usage
232
+ ]);
233
+ let fragment = fragments.get(key);
234
+ if (fragment == null) {
235
+ fragment = formatter(term, context);
236
+ fragments.set(key, fragment);
237
+ }
238
+ return fragment;
239
+ };
240
+ }
241
+ return {
242
+ optionName: cache(theme.optionName ?? defaultTerminalTheme.optionName),
243
+ optionNames: cache(theme.optionNames ?? defaultTerminalTheme.optionNames),
244
+ metavar: cache(theme.metavar ?? defaultTerminalTheme.metavar),
245
+ value: cache(theme.value ?? defaultTerminalTheme.value),
246
+ values: cache(valuesFormatter(theme)),
247
+ envVar: cache(theme.envVar ?? defaultTerminalTheme.envVar),
248
+ commandLine: cache(theme.commandLine ?? defaultTerminalTheme.commandLine),
249
+ url: cache(theme.url ?? defaultTerminalTheme.url),
250
+ programName: cache(theme.programName ?? defaultTerminalTheme.programName),
251
+ label: cache(theme.label ?? defaultTerminalTheme.label),
252
+ syntaxPunctuation: cache(theme.syntaxPunctuation ?? defaultTerminalTheme.syntaxPunctuation),
253
+ errorLabel: cache(theme.errorLabel ?? defaultTerminalTheme.errorLabel),
254
+ annotationStyles: theme.annotationStyles == null ? void 0 : {
255
+ default: theme.annotationStyles.default == null ? void 0 : { ...theme.annotationStyles.default },
256
+ choices: theme.annotationStyles.choices == null ? void 0 : { ...theme.annotationStyles.choices }
257
+ }
258
+ };
259
+ }
260
+
261
+ //#endregion
262
+ export { cacheTerminalTheme, formatTerminalTerm, fragmentTokens, renderTerminalTerm, serializeTokens, styleCode, terminalText };
@@ -0,0 +1,73 @@
1
+
2
+ //#region src/terminal.ts
3
+ const plain = (text) => ({
4
+ type: "text",
5
+ text
6
+ });
7
+ const styled = (text, style) => ({
8
+ type: "style",
9
+ style,
10
+ children: [plain(text)]
11
+ });
12
+ /**
13
+ * Optique's built-in terminal theme, with every formatter available for delegation.
14
+ * @since 1.3.0
15
+ */
16
+ const defaultTerminalTheme = {
17
+ optionName: (_term, ctx) => styled(ctx.text, ctx.usage === "command" ? { bold: true } : { italic: true }),
18
+ optionNames: (term, ctx) => ({
19
+ type: "concat",
20
+ children: term.optionNames.flatMap((name, i) => [...i === 0 ? [] : [ctx.format({
21
+ type: "syntaxPunctuation",
22
+ punctuation: "/",
23
+ kind: "messageOptionSeparator"
24
+ })], ctx.format({
25
+ type: "optionName",
26
+ optionName: name
27
+ })])
28
+ }),
29
+ metavar: (_term, ctx) => ctx.usage === "optionValue" ? {
30
+ type: "style",
31
+ style: { underline: true },
32
+ children: [styled(ctx.text, { dim: true })]
33
+ } : styled(ctx.text, ctx.usage === "argument" ? { underline: true } : { bold: true }),
34
+ value: (_term, ctx) => ctx.usage === "literal" ? plain(ctx.text) : styled(ctx.text, { foreground: "green" }),
35
+ values: (term, ctx) => ({
36
+ type: "concat",
37
+ children: term.values.flatMap((value, i) => [...i === 0 ? [] : [plain(" ")], ctx.format({
38
+ type: "value",
39
+ value
40
+ })])
41
+ }),
42
+ envVar: (_term, ctx) => styled(ctx.text, {
43
+ bold: true,
44
+ underline: true
45
+ }),
46
+ commandLine: (_term, ctx) => styled(ctx.text, { foreground: "cyan" }),
47
+ url: (term, ctx) => ({
48
+ type: "link",
49
+ href: term.url.href,
50
+ children: [plain(ctx.text)]
51
+ }),
52
+ programName: (_term, ctx) => styled(ctx.text, { bold: true }),
53
+ label: (term, ctx) => term.kind === "usageSummary" || term.kind === "choices" ? plain(ctx.text) : styled(ctx.text, {
54
+ bold: true,
55
+ dim: true
56
+ }),
57
+ syntaxPunctuation: (term, ctx) => [
58
+ "alternative",
59
+ "messageOptionSeparator",
60
+ "defaultPrefix",
61
+ "defaultSuffix",
62
+ "choicesPrefix",
63
+ "choicesSuffix"
64
+ ].includes(term.kind) ? plain(ctx.text) : styled(ctx.text, { dim: true }),
65
+ errorLabel: (_term, ctx) => plain(ctx.text),
66
+ annotationStyles: {
67
+ default: { dim: true },
68
+ choices: { dim: true }
69
+ }
70
+ };
71
+
72
+ //#endregion
73
+ exports.defaultTerminalTheme = defaultTerminalTheme;
@@ -0,0 +1,129 @@
1
+ import { MessageTerm } from "./message.cjs";
2
+
3
+ //#region src/terminal.d.ts
4
+
5
+ /**
6
+ * A program's executable name.
7
+ * @since 1.3.0
8
+ */
9
+ interface ProgramNameTerm {
10
+ readonly type: "programName";
11
+ readonly programName: string;
12
+ }
13
+ /**
14
+ * A heading or annotation label.
15
+ * @since 1.3.0
16
+ */
17
+ interface LabelTerm {
18
+ readonly type: "label";
19
+ readonly label: string;
20
+ readonly kind: "usage" | "usageSummary" | "section" | "examples" | "author" | "bugs" | "choices";
21
+ }
22
+ /**
23
+ * Punctuation supplied by a terminal renderer.
24
+ * @since 1.3.0
25
+ */
26
+ interface SyntaxPunctuationTerm {
27
+ readonly type: "syntaxPunctuation";
28
+ readonly punctuation: string;
29
+ readonly kind: "optionalOpen" | "optionalClose" | "groupOpen" | "groupClose" | "alternative" | "ellipsis" | "passthrough" | "optionSeparator" | "messageOptionSeparator" | "defaultPrefix" | "defaultSuffix" | "choicesPrefix" | "choicesSuffix";
30
+ }
31
+ /**
32
+ * The label preceding an error message.
33
+ * @since 1.3.0
34
+ */
35
+ interface ErrorLabelTerm {
36
+ readonly type: "errorLabel";
37
+ readonly label: string;
38
+ }
39
+ /**
40
+ * Semantic leaves shared by message, usage, and help renderers.
41
+ * @since 1.3.0
42
+ */
43
+ type TerminalTerm = MessageTerm | ProgramNameTerm | LabelTerm | SyntaxPunctuationTerm | ErrorLabelTerm;
44
+ /**
45
+ * A terminal color. RGB channels and palette indices are integers from 0 to 255.
46
+ * @since 1.3.0
47
+ */
48
+ type TerminalColor = "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "brightBlack" | "brightRed" | "brightGreen" | "brightYellow" | "brightBlue" | "brightMagenta" | "brightCyan" | "brightWhite" | readonly [number, number, number] | {
49
+ readonly index: number;
50
+ };
51
+ /**
52
+ * Inherited terminal styling; false disables an inherited attribute.
53
+ * @since 1.3.0
54
+ */
55
+ interface TerminalStyle {
56
+ readonly foreground?: TerminalColor;
57
+ readonly background?: TerminalColor;
58
+ readonly bold?: boolean;
59
+ readonly dim?: boolean;
60
+ readonly italic?: boolean;
61
+ readonly underline?: boolean;
62
+ }
63
+ /**
64
+ * Structured output. Each text leaf is an indivisible wrapping unit.
65
+ * @since 1.3.0
66
+ */
67
+ type TerminalFragment = {
68
+ readonly type: "text";
69
+ readonly text: string;
70
+ } | {
71
+ readonly type: "concat";
72
+ readonly children: readonly TerminalFragment[];
73
+ } | {
74
+ readonly type: "style";
75
+ readonly style: TerminalStyle;
76
+ readonly children: readonly TerminalFragment[];
77
+ } | {
78
+ readonly type: "link";
79
+ readonly href: string;
80
+ readonly children: readonly TerminalFragment[];
81
+ };
82
+ /**
83
+ * Context supplied to a semantic formatter.
84
+ * @since 1.3.0
85
+ */
86
+ interface TerminalFormatContext {
87
+ /** Whether the default display text includes quotes. */
88
+ readonly quotes: boolean;
89
+ /** Default display text, quoted but not styled. */
90
+ readonly text: string;
91
+ /** The purpose of a leaf originating in a usage description. */
92
+ readonly usage?: "argument" | "optionValue" | "command" | "literal";
93
+ /** Formats a child through the effective theme. Same-role recursion is the author's responsibility. */
94
+ readonly format: (term: Exclude<TerminalTerm, {
95
+ readonly type: "lineBreak";
96
+ }>) => TerminalFragment;
97
+ }
98
+ /**
99
+ * Formats a semantic leaf without emitting ANSI.
100
+ * @since 1.3.0
101
+ */
102
+ type TerminalFormatter<T extends TerminalTerm = TerminalTerm> = (term: T, context: TerminalFormatContext) => TerminalFragment;
103
+ type Role = Exclude<TerminalTerm["type"], "text" | "lineBreak">;
104
+ type Formatters = { readonly [K in Role]: TerminalFormatter<Extract<TerminalTerm, {
105
+ readonly type: K;
106
+ }>> };
107
+ /**
108
+ * A partial theme; omitted roles retain their defaults.
109
+ * @since 1.3.0
110
+ */
111
+ type TerminalTheme = Partial<Formatters> & {
112
+ /** Surrounding annotation styles. Missing styles are dim; {} disables dim. */
113
+ readonly annotationStyles?: {
114
+ readonly default?: TerminalStyle;
115
+ readonly choices?: TerminalStyle;
116
+ };
117
+ };
118
+ /**
119
+ * Optique's built-in terminal theme, with every formatter available for delegation.
120
+ * @since 1.3.0
121
+ */
122
+ declare const defaultTerminalTheme: Formatters & {
123
+ readonly annotationStyles: {
124
+ readonly default: TerminalStyle;
125
+ readonly choices: TerminalStyle;
126
+ };
127
+ };
128
+ //#endregion
129
+ export { ErrorLabelTerm, LabelTerm, ProgramNameTerm, SyntaxPunctuationTerm, TerminalColor, TerminalFormatContext, TerminalFormatter, TerminalFragment, TerminalStyle, TerminalTerm, TerminalTheme, defaultTerminalTheme };
@@ -0,0 +1,129 @@
1
+ import { MessageTerm } from "./message.js";
2
+
3
+ //#region src/terminal.d.ts
4
+
5
+ /**
6
+ * A program's executable name.
7
+ * @since 1.3.0
8
+ */
9
+ interface ProgramNameTerm {
10
+ readonly type: "programName";
11
+ readonly programName: string;
12
+ }
13
+ /**
14
+ * A heading or annotation label.
15
+ * @since 1.3.0
16
+ */
17
+ interface LabelTerm {
18
+ readonly type: "label";
19
+ readonly label: string;
20
+ readonly kind: "usage" | "usageSummary" | "section" | "examples" | "author" | "bugs" | "choices";
21
+ }
22
+ /**
23
+ * Punctuation supplied by a terminal renderer.
24
+ * @since 1.3.0
25
+ */
26
+ interface SyntaxPunctuationTerm {
27
+ readonly type: "syntaxPunctuation";
28
+ readonly punctuation: string;
29
+ readonly kind: "optionalOpen" | "optionalClose" | "groupOpen" | "groupClose" | "alternative" | "ellipsis" | "passthrough" | "optionSeparator" | "messageOptionSeparator" | "defaultPrefix" | "defaultSuffix" | "choicesPrefix" | "choicesSuffix";
30
+ }
31
+ /**
32
+ * The label preceding an error message.
33
+ * @since 1.3.0
34
+ */
35
+ interface ErrorLabelTerm {
36
+ readonly type: "errorLabel";
37
+ readonly label: string;
38
+ }
39
+ /**
40
+ * Semantic leaves shared by message, usage, and help renderers.
41
+ * @since 1.3.0
42
+ */
43
+ type TerminalTerm = MessageTerm | ProgramNameTerm | LabelTerm | SyntaxPunctuationTerm | ErrorLabelTerm;
44
+ /**
45
+ * A terminal color. RGB channels and palette indices are integers from 0 to 255.
46
+ * @since 1.3.0
47
+ */
48
+ type TerminalColor = "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "brightBlack" | "brightRed" | "brightGreen" | "brightYellow" | "brightBlue" | "brightMagenta" | "brightCyan" | "brightWhite" | readonly [number, number, number] | {
49
+ readonly index: number;
50
+ };
51
+ /**
52
+ * Inherited terminal styling; false disables an inherited attribute.
53
+ * @since 1.3.0
54
+ */
55
+ interface TerminalStyle {
56
+ readonly foreground?: TerminalColor;
57
+ readonly background?: TerminalColor;
58
+ readonly bold?: boolean;
59
+ readonly dim?: boolean;
60
+ readonly italic?: boolean;
61
+ readonly underline?: boolean;
62
+ }
63
+ /**
64
+ * Structured output. Each text leaf is an indivisible wrapping unit.
65
+ * @since 1.3.0
66
+ */
67
+ type TerminalFragment = {
68
+ readonly type: "text";
69
+ readonly text: string;
70
+ } | {
71
+ readonly type: "concat";
72
+ readonly children: readonly TerminalFragment[];
73
+ } | {
74
+ readonly type: "style";
75
+ readonly style: TerminalStyle;
76
+ readonly children: readonly TerminalFragment[];
77
+ } | {
78
+ readonly type: "link";
79
+ readonly href: string;
80
+ readonly children: readonly TerminalFragment[];
81
+ };
82
+ /**
83
+ * Context supplied to a semantic formatter.
84
+ * @since 1.3.0
85
+ */
86
+ interface TerminalFormatContext {
87
+ /** Whether the default display text includes quotes. */
88
+ readonly quotes: boolean;
89
+ /** Default display text, quoted but not styled. */
90
+ readonly text: string;
91
+ /** The purpose of a leaf originating in a usage description. */
92
+ readonly usage?: "argument" | "optionValue" | "command" | "literal";
93
+ /** Formats a child through the effective theme. Same-role recursion is the author's responsibility. */
94
+ readonly format: (term: Exclude<TerminalTerm, {
95
+ readonly type: "lineBreak";
96
+ }>) => TerminalFragment;
97
+ }
98
+ /**
99
+ * Formats a semantic leaf without emitting ANSI.
100
+ * @since 1.3.0
101
+ */
102
+ type TerminalFormatter<T extends TerminalTerm = TerminalTerm> = (term: T, context: TerminalFormatContext) => TerminalFragment;
103
+ type Role = Exclude<TerminalTerm["type"], "text" | "lineBreak">;
104
+ type Formatters = { readonly [K in Role]: TerminalFormatter<Extract<TerminalTerm, {
105
+ readonly type: K;
106
+ }>> };
107
+ /**
108
+ * A partial theme; omitted roles retain their defaults.
109
+ * @since 1.3.0
110
+ */
111
+ type TerminalTheme = Partial<Formatters> & {
112
+ /** Surrounding annotation styles. Missing styles are dim; {} disables dim. */
113
+ readonly annotationStyles?: {
114
+ readonly default?: TerminalStyle;
115
+ readonly choices?: TerminalStyle;
116
+ };
117
+ };
118
+ /**
119
+ * Optique's built-in terminal theme, with every formatter available for delegation.
120
+ * @since 1.3.0
121
+ */
122
+ declare const defaultTerminalTheme: Formatters & {
123
+ readonly annotationStyles: {
124
+ readonly default: TerminalStyle;
125
+ readonly choices: TerminalStyle;
126
+ };
127
+ };
128
+ //#endregion
129
+ export { ErrorLabelTerm, LabelTerm, ProgramNameTerm, SyntaxPunctuationTerm, TerminalColor, TerminalFormatContext, TerminalFormatter, TerminalFragment, TerminalStyle, TerminalTerm, TerminalTheme, defaultTerminalTheme };
@@ -0,0 +1,72 @@
1
+ //#region src/terminal.ts
2
+ const plain = (text) => ({
3
+ type: "text",
4
+ text
5
+ });
6
+ const styled = (text, style) => ({
7
+ type: "style",
8
+ style,
9
+ children: [plain(text)]
10
+ });
11
+ /**
12
+ * Optique's built-in terminal theme, with every formatter available for delegation.
13
+ * @since 1.3.0
14
+ */
15
+ const defaultTerminalTheme = {
16
+ optionName: (_term, ctx) => styled(ctx.text, ctx.usage === "command" ? { bold: true } : { italic: true }),
17
+ optionNames: (term, ctx) => ({
18
+ type: "concat",
19
+ children: term.optionNames.flatMap((name, i) => [...i === 0 ? [] : [ctx.format({
20
+ type: "syntaxPunctuation",
21
+ punctuation: "/",
22
+ kind: "messageOptionSeparator"
23
+ })], ctx.format({
24
+ type: "optionName",
25
+ optionName: name
26
+ })])
27
+ }),
28
+ metavar: (_term, ctx) => ctx.usage === "optionValue" ? {
29
+ type: "style",
30
+ style: { underline: true },
31
+ children: [styled(ctx.text, { dim: true })]
32
+ } : styled(ctx.text, ctx.usage === "argument" ? { underline: true } : { bold: true }),
33
+ value: (_term, ctx) => ctx.usage === "literal" ? plain(ctx.text) : styled(ctx.text, { foreground: "green" }),
34
+ values: (term, ctx) => ({
35
+ type: "concat",
36
+ children: term.values.flatMap((value, i) => [...i === 0 ? [] : [plain(" ")], ctx.format({
37
+ type: "value",
38
+ value
39
+ })])
40
+ }),
41
+ envVar: (_term, ctx) => styled(ctx.text, {
42
+ bold: true,
43
+ underline: true
44
+ }),
45
+ commandLine: (_term, ctx) => styled(ctx.text, { foreground: "cyan" }),
46
+ url: (term, ctx) => ({
47
+ type: "link",
48
+ href: term.url.href,
49
+ children: [plain(ctx.text)]
50
+ }),
51
+ programName: (_term, ctx) => styled(ctx.text, { bold: true }),
52
+ label: (term, ctx) => term.kind === "usageSummary" || term.kind === "choices" ? plain(ctx.text) : styled(ctx.text, {
53
+ bold: true,
54
+ dim: true
55
+ }),
56
+ syntaxPunctuation: (term, ctx) => [
57
+ "alternative",
58
+ "messageOptionSeparator",
59
+ "defaultPrefix",
60
+ "defaultSuffix",
61
+ "choicesPrefix",
62
+ "choicesSuffix"
63
+ ].includes(term.kind) ? plain(ctx.text) : styled(ctx.text, { dim: true }),
64
+ errorLabel: (_term, ctx) => plain(ctx.text),
65
+ annotationStyles: {
66
+ default: { dim: true },
67
+ choices: { dim: true }
68
+ }
69
+ };
70
+
71
+ //#endregion
72
+ export { defaultTerminalTheme };