@reliverse/relico 1.4.1 → 2.2.7
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/README.md +16 -20
- package/dist/mod.d.ts +60 -0
- package/dist/mod.js +455 -0
- package/package.json +17 -20
- package/.vscode/extensions.json +0 -8
- package/.vscode/settings.json +0 -45
- package/LICENSE +0 -21
- package/biome.json +0 -86
- package/examples/benchmarks/bundle-size.ts +0 -143
- package/examples/benchmarks/config.example.ts +0 -55
- package/examples/benchmarks/config.ts +0 -93
- package/examples/benchmarks/performance.ts +0 -172
- package/examples/core.ts +0 -62
- package/reliverse.ts +0 -399
- package/reltypes.ts +0 -1490
- package/src/mod.ts +0 -537
- package/tsconfig.json +0 -33
package/src/mod.ts
DELETED
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
/* @reliverse/relico - Tiny, type-safe terminal color library with chainable API
|
|
2
|
-
- Levels: 0 (off), 1 (ANSI 8/bright), 2 (ANSI 256), 3 (Truecolor)
|
|
3
|
-
- Named palettes (std, web, grayscale), Bright & Pastel variants, bg-variants
|
|
4
|
-
- Chainable: re.bold.red.underline("text"), chain(re.bold, re.red)("text")
|
|
5
|
-
- Multiline-safe: styles applied per line with reset to prevent bleed
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
type ColorLevel = 0 | 1 | 2 | 3;
|
|
9
|
-
|
|
10
|
-
interface Rgb {
|
|
11
|
-
r: number;
|
|
12
|
-
g: number;
|
|
13
|
-
b: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type SgrOp =
|
|
17
|
-
| { kind: "style"; open: number[] } // closed by global reset at line end
|
|
18
|
-
| { kind: "fg-basic"; idx: number; bright: boolean }
|
|
19
|
-
| { kind: "bg-basic"; idx: number; bright: boolean }
|
|
20
|
-
| { kind: "fg-256"; code: number }
|
|
21
|
-
| { kind: "bg-256"; code: number }
|
|
22
|
-
| { kind: "fg-true"; rgb: Rgb }
|
|
23
|
-
| { kind: "bg-true"; rgb: Rgb };
|
|
24
|
-
|
|
25
|
-
type ApplyInput = string | number;
|
|
26
|
-
|
|
27
|
-
type FormatCallable = ((input: ApplyInput) => string) & { readonly [OP_SYMBOL]: SgrOp[] };
|
|
28
|
-
|
|
29
|
-
export type BaseColorName =
|
|
30
|
-
| "black"
|
|
31
|
-
| "red"
|
|
32
|
-
| "green"
|
|
33
|
-
| "yellow"
|
|
34
|
-
| "blue"
|
|
35
|
-
| "magenta"
|
|
36
|
-
| "cyan"
|
|
37
|
-
| "white"
|
|
38
|
-
| "gray"
|
|
39
|
-
| "orange"
|
|
40
|
-
| "pink"
|
|
41
|
-
| "purple"
|
|
42
|
-
| "teal"
|
|
43
|
-
| "lime"
|
|
44
|
-
| "brown"
|
|
45
|
-
| "navy"
|
|
46
|
-
| "maroon"
|
|
47
|
-
| "olive"
|
|
48
|
-
| "silver";
|
|
49
|
-
|
|
50
|
-
export type ColorName = BaseColorName | BrightColorName | BgColorName;
|
|
51
|
-
|
|
52
|
-
export type BrightColorName =
|
|
53
|
-
| "blackBright"
|
|
54
|
-
| "redBright"
|
|
55
|
-
| "greenBright"
|
|
56
|
-
| "yellowBright"
|
|
57
|
-
| "blueBright"
|
|
58
|
-
| "magentaBright"
|
|
59
|
-
| "cyanBright"
|
|
60
|
-
| "whiteBright"
|
|
61
|
-
| "orangeBright"
|
|
62
|
-
| "pinkBright"
|
|
63
|
-
| "purpleBright"
|
|
64
|
-
| "tealBright"
|
|
65
|
-
| "limeBright"
|
|
66
|
-
| "brownBright"
|
|
67
|
-
| "navyBright"
|
|
68
|
-
| "maroonBright"
|
|
69
|
-
| "oliveBright"
|
|
70
|
-
| "silverBright";
|
|
71
|
-
|
|
72
|
-
export type BgColorName = `bg${Capitalize<BaseColorName>}` | `bg${Capitalize<BrightColorName>}`;
|
|
73
|
-
|
|
74
|
-
export type ReStyleKey =
|
|
75
|
-
| "reset"
|
|
76
|
-
| "bold"
|
|
77
|
-
| "dim"
|
|
78
|
-
| "italic"
|
|
79
|
-
| "underline"
|
|
80
|
-
| "inverse"
|
|
81
|
-
| "hidden"
|
|
82
|
-
| "strikethrough";
|
|
83
|
-
|
|
84
|
-
export type Re = FormatCallable & {
|
|
85
|
-
readonly [K in ReStyleKey]: Re;
|
|
86
|
-
} & {
|
|
87
|
-
readonly [K in ColorName]: Re;
|
|
88
|
-
} & {
|
|
89
|
-
readonly [K in BgColorName]: Re;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const ESC = "\x1B[";
|
|
93
|
-
const RESET = `${ESC}0m`;
|
|
94
|
-
const OP_SYMBOL: unique symbol = Symbol("re.ops");
|
|
95
|
-
|
|
96
|
-
// Color level constants
|
|
97
|
-
const COLOR_LEVEL_OFF = 0;
|
|
98
|
-
const COLOR_LEVEL_BASIC = 1;
|
|
99
|
-
const COLOR_LEVEL_256 = 2;
|
|
100
|
-
const COLOR_LEVEL_TRUECOLOR = 3;
|
|
101
|
-
|
|
102
|
-
// RGB and byte constants
|
|
103
|
-
const MIN_BYTE = 0;
|
|
104
|
-
const MAX_BYTE = 255;
|
|
105
|
-
const WHITE_RGB = 255;
|
|
106
|
-
|
|
107
|
-
// ANSI 256 color constants
|
|
108
|
-
const ANSI_256_GRAYSCALE_MIN = 8;
|
|
109
|
-
const ANSI_256_GRAYSCALE_MAX = 248;
|
|
110
|
-
const ANSI_256_BASE_OFFSET = 16;
|
|
111
|
-
const ANSI_256_GRAYSCALE_BASE = 232;
|
|
112
|
-
const ANSI_256_GRAYSCALE_RANGE = 247;
|
|
113
|
-
const ANSI_256_GRAYSCALE_STEPS = 24;
|
|
114
|
-
const ANSI_256_BRIGHT_THRESHOLD = 231;
|
|
115
|
-
const ANSI_256_RGB_LEVELS = 5;
|
|
116
|
-
const ANSI_256_RGB_RED_MULTIPLIER = 36;
|
|
117
|
-
const ANSI_256_RGB_GREEN_MULTIPLIER = 6;
|
|
118
|
-
|
|
119
|
-
// SGR code constants
|
|
120
|
-
const SGR_FG_BASE = 30;
|
|
121
|
-
const SGR_BG_BASE = 40;
|
|
122
|
-
const SGR_FG_BRIGHT_BASE = 90;
|
|
123
|
-
const SGR_BG_BRIGHT_BASE = 100;
|
|
124
|
-
|
|
125
|
-
// Style SGR codes
|
|
126
|
-
const SGR_RESET = 0;
|
|
127
|
-
const SGR_BOLD = 1;
|
|
128
|
-
const SGR_DIM = 2;
|
|
129
|
-
const SGR_ITALIC = 3;
|
|
130
|
-
const SGR_UNDERLINE = 4;
|
|
131
|
-
const SGR_INVERSE = 7;
|
|
132
|
-
const SGR_HIDDEN = 8;
|
|
133
|
-
const SGR_STRIKETHROUGH = 9;
|
|
134
|
-
|
|
135
|
-
// Hex parsing constants
|
|
136
|
-
const HEX_BYTE_LENGTH = 2;
|
|
137
|
-
const HEX_RED_START = 0;
|
|
138
|
-
const HEX_GREEN_START = 2;
|
|
139
|
-
const HEX_BLUE_START = 4;
|
|
140
|
-
const HEX_BLUE_END = 6;
|
|
141
|
-
const HEX_RADIX = 16;
|
|
142
|
-
|
|
143
|
-
// String processing constants
|
|
144
|
-
const BRIGHT_SUFFIX_LENGTH = 6;
|
|
145
|
-
const BG_PREFIX_LENGTH = 2;
|
|
146
|
-
const BG_COLOR_START = 3;
|
|
147
|
-
|
|
148
|
-
// Color mixing constants
|
|
149
|
-
const BRIGHT_MIX_FACTOR = 0.25;
|
|
150
|
-
|
|
151
|
-
// Regex constants
|
|
152
|
-
const BRIGHT_SUFFIX_REGEX = /Bright$/u;
|
|
153
|
-
|
|
154
|
-
let CURRENT_LEVEL: ColorLevel = COLOR_LEVEL_TRUECOLOR;
|
|
155
|
-
|
|
156
|
-
export const setColorLevel = (level: ColorLevel): void => {
|
|
157
|
-
if (
|
|
158
|
-
level !== COLOR_LEVEL_OFF &&
|
|
159
|
-
level !== COLOR_LEVEL_BASIC &&
|
|
160
|
-
level !== COLOR_LEVEL_256 &&
|
|
161
|
-
level !== COLOR_LEVEL_TRUECOLOR
|
|
162
|
-
) {
|
|
163
|
-
throw new Error("Invalid color level");
|
|
164
|
-
}
|
|
165
|
-
CURRENT_LEVEL = level;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const clampByte = (n: number): number => {
|
|
169
|
-
if (!Number.isFinite(n)) {
|
|
170
|
-
return MIN_BYTE;
|
|
171
|
-
}
|
|
172
|
-
if (n < MIN_BYTE) {
|
|
173
|
-
return MIN_BYTE;
|
|
174
|
-
}
|
|
175
|
-
if (n > MAX_BYTE) {
|
|
176
|
-
return MAX_BYTE;
|
|
177
|
-
}
|
|
178
|
-
return Math.round(n);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Base 8-color RGB anchors (non-bright)
|
|
182
|
-
const BASIC8: Rgb[] = [
|
|
183
|
-
{ r: 0, g: 0, b: 0 }, // black
|
|
184
|
-
{ r: 205, g: 0, b: 0 }, // red
|
|
185
|
-
{ r: 0, g: 205, b: 0 }, // green
|
|
186
|
-
{ r: 205, g: 205, b: 0 }, // yellow
|
|
187
|
-
{ r: 0, g: 0, b: 238 }, // blue
|
|
188
|
-
{ r: 205, g: 0, b: 205 }, // magenta
|
|
189
|
-
{ r: 0, g: 205, b: 205 }, // cyan
|
|
190
|
-
{ r: 229, g: 229, b: 229 }, // white (light gray)
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
// SGR code builders
|
|
194
|
-
const sgr = (codes: number[]): string => `${ESC}${codes.join(";")}m`;
|
|
195
|
-
|
|
196
|
-
// RGB → closest of BASIC8 index (0..7)
|
|
197
|
-
const nearestBasicIndex = (rgb: Rgb): number => {
|
|
198
|
-
let best = 0;
|
|
199
|
-
let bestDist = Number.POSITIVE_INFINITY;
|
|
200
|
-
for (let i = 0; i < BASIC8.length; i++) {
|
|
201
|
-
const c = BASIC8[i];
|
|
202
|
-
const dr = c.r - rgb.r;
|
|
203
|
-
const dg = c.g - rgb.g;
|
|
204
|
-
const db = c.b - rgb.b;
|
|
205
|
-
const d = dr * dr + dg * dg + db * db;
|
|
206
|
-
if (d < bestDist) {
|
|
207
|
-
bestDist = d;
|
|
208
|
-
best = i;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return best;
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// RGB → ANSI 256 index
|
|
215
|
-
const rgbToAnsi256 = (rgb: Rgb): number => {
|
|
216
|
-
// Try grayscale if r≈g≈b
|
|
217
|
-
if (rgb.r === rgb.g && rgb.g === rgb.b) {
|
|
218
|
-
if (rgb.r < ANSI_256_GRAYSCALE_MIN) {
|
|
219
|
-
return ANSI_256_BASE_OFFSET;
|
|
220
|
-
}
|
|
221
|
-
if (rgb.r > ANSI_256_GRAYSCALE_MAX) {
|
|
222
|
-
return ANSI_256_BRIGHT_THRESHOLD;
|
|
223
|
-
}
|
|
224
|
-
const step = Math.round(
|
|
225
|
-
((rgb.r - ANSI_256_GRAYSCALE_MIN) / ANSI_256_GRAYSCALE_RANGE) * ANSI_256_GRAYSCALE_STEPS,
|
|
226
|
-
);
|
|
227
|
-
return ANSI_256_GRAYSCALE_BASE + step;
|
|
228
|
-
}
|
|
229
|
-
const r = Math.round((rgb.r / MAX_BYTE) * ANSI_256_RGB_LEVELS);
|
|
230
|
-
const g = Math.round((rgb.g / MAX_BYTE) * ANSI_256_RGB_LEVELS);
|
|
231
|
-
const b = Math.round((rgb.b / MAX_BYTE) * ANSI_256_RGB_LEVELS);
|
|
232
|
-
return (
|
|
233
|
-
ANSI_256_BASE_OFFSET + ANSI_256_RGB_RED_MULTIPLIER * r + ANSI_256_RGB_GREEN_MULTIPLIER * g + b
|
|
234
|
-
);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// Color data
|
|
238
|
-
const NAMED_COLORS: Record<BaseColorName, string> = {
|
|
239
|
-
black: "#000000",
|
|
240
|
-
red: "#ff0000",
|
|
241
|
-
green: "#00ff00",
|
|
242
|
-
yellow: "#ffff00",
|
|
243
|
-
blue: "#0000ff",
|
|
244
|
-
magenta: "#ff00ff",
|
|
245
|
-
cyan: "#00ffff",
|
|
246
|
-
white: "#ffffff",
|
|
247
|
-
gray: "#808080",
|
|
248
|
-
orange: "#ffa500",
|
|
249
|
-
pink: "#ffc0cb",
|
|
250
|
-
purple: "#800080",
|
|
251
|
-
teal: "#008080",
|
|
252
|
-
lime: "#00ff00",
|
|
253
|
-
brown: "#a52a2a",
|
|
254
|
-
navy: "#000080",
|
|
255
|
-
maroon: "#800000",
|
|
256
|
-
olive: "#808000",
|
|
257
|
-
silver: "#c0c0c0",
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const mixWithWhite = (rgb: Rgb, factor: number): Rgb => {
|
|
261
|
-
const t = factor;
|
|
262
|
-
return {
|
|
263
|
-
r: clampByte(rgb.r * (1 - t) + WHITE_RGB * t),
|
|
264
|
-
g: clampByte(rgb.g * (1 - t) + WHITE_RGB * t),
|
|
265
|
-
b: clampByte(rgb.b * (1 - t) + WHITE_RGB * t),
|
|
266
|
-
};
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const fromNamed = (name: BaseColorName): Rgb => {
|
|
270
|
-
const hex = NAMED_COLORS[name];
|
|
271
|
-
if (!hex || typeof hex !== "string") {
|
|
272
|
-
// Return black as fallback for invalid color names
|
|
273
|
-
return { r: 0, g: 0, b: 0 };
|
|
274
|
-
}
|
|
275
|
-
// Simple hex to RGB conversion for named colors only
|
|
276
|
-
const clean = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
277
|
-
if (clean.length !== HEX_BLUE_END && clean.length !== 3) {
|
|
278
|
-
// Return black as fallback for invalid hex format
|
|
279
|
-
return { r: 0, g: 0, b: 0 };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
let rHex: string, gHex: string, bHex: string;
|
|
283
|
-
if (clean.length === 3) {
|
|
284
|
-
// Expand short hex format (e.g., "abc" -> "aabbcc")
|
|
285
|
-
rHex = clean[0].repeat(2);
|
|
286
|
-
gHex = clean[1].repeat(2);
|
|
287
|
-
bHex = clean[2].repeat(2);
|
|
288
|
-
} else {
|
|
289
|
-
rHex = clean.slice(HEX_RED_START, HEX_BYTE_LENGTH);
|
|
290
|
-
gHex = clean.slice(HEX_GREEN_START, HEX_BLUE_START);
|
|
291
|
-
bHex = clean.slice(HEX_BLUE_START, HEX_BLUE_END);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const r = Number.parseInt(rHex, HEX_RADIX);
|
|
295
|
-
const g = Number.parseInt(gHex, HEX_RADIX);
|
|
296
|
-
const b = Number.parseInt(bHex, HEX_RADIX);
|
|
297
|
-
|
|
298
|
-
// Validate parsed RGB values
|
|
299
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
300
|
-
return { r: 0, g: 0, b: 0 };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return { r, g, b };
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const toBaseName = (compound: BrightColorName): BaseColorName => {
|
|
307
|
-
if (!compound || typeof compound !== "string") {
|
|
308
|
-
return "black"; // fallback for invalid input
|
|
309
|
-
}
|
|
310
|
-
const base = compound.replace(BRIGHT_SUFFIX_REGEX, "");
|
|
311
|
-
if (!base) {
|
|
312
|
-
return "black"; // fallback for empty result
|
|
313
|
-
}
|
|
314
|
-
const key = base.charAt(0).toLowerCase() + base.slice(1);
|
|
315
|
-
return key as BaseColorName;
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
const parseColorName = (name: ColorName): { rgb: Rgb; wantBright: boolean } => {
|
|
319
|
-
if (!name || typeof name !== "string") {
|
|
320
|
-
// Return black as fallback for invalid input
|
|
321
|
-
return { rgb: { r: 0, g: 0, b: 0 }, wantBright: false };
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (name.endsWith("Bright")) {
|
|
325
|
-
const base = toBaseName(name as BrightColorName);
|
|
326
|
-
const rgb = fromNamed(base);
|
|
327
|
-
// Lighten a bit in high levels; level 1 will use bright SGR
|
|
328
|
-
const rgbAdj = mixWithWhite(rgb, BRIGHT_MIX_FACTOR);
|
|
329
|
-
return { rgb: rgbAdj, wantBright: true };
|
|
330
|
-
}
|
|
331
|
-
return { rgb: fromNamed(name as BaseColorName), wantBright: false };
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
const openForOp = (op: SgrOp): string => {
|
|
335
|
-
if (CURRENT_LEVEL === COLOR_LEVEL_OFF) {
|
|
336
|
-
return "";
|
|
337
|
-
}
|
|
338
|
-
switch (op.kind) {
|
|
339
|
-
case "style":
|
|
340
|
-
return sgr(op.open);
|
|
341
|
-
case "fg-basic":
|
|
342
|
-
return sgr([(op.bright ? SGR_FG_BRIGHT_BASE : SGR_FG_BASE) + op.idx]);
|
|
343
|
-
case "bg-basic":
|
|
344
|
-
return sgr([(op.bright ? SGR_BG_BRIGHT_BASE : SGR_BG_BASE) + op.idx]);
|
|
345
|
-
case "fg-256":
|
|
346
|
-
return `${ESC}38;5;${op.code}m`;
|
|
347
|
-
case "bg-256":
|
|
348
|
-
return `${ESC}48;5;${op.code}m`;
|
|
349
|
-
case "fg-true":
|
|
350
|
-
return `${ESC}38;2;${op.rgb.r};${op.rgb.g};${op.rgb.b}m`;
|
|
351
|
-
case "bg-true":
|
|
352
|
-
return `${ESC}48;2;${op.rgb.r};${op.rgb.g};${op.rgb.b}m`;
|
|
353
|
-
default:
|
|
354
|
-
return "";
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
const opsToOpen = (ops: SgrOp[]): string => {
|
|
359
|
-
if (CURRENT_LEVEL === COLOR_LEVEL_OFF) {
|
|
360
|
-
return "";
|
|
361
|
-
}
|
|
362
|
-
let out = "";
|
|
363
|
-
for (const op of ops) {
|
|
364
|
-
out += openForOp(op);
|
|
365
|
-
}
|
|
366
|
-
return out;
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
// Optimized multiline processing with fewer allocations and branches
|
|
370
|
-
const applyOpsToText = (ops: SgrOp[], input: ApplyInput): string => {
|
|
371
|
-
const text = String(input);
|
|
372
|
-
if (CURRENT_LEVEL === COLOR_LEVEL_OFF || ops.length === 0 || text.length === 0) {
|
|
373
|
-
return text;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const open = opsToOpen(ops);
|
|
377
|
-
|
|
378
|
-
// Fast path for single-line text (most common case)
|
|
379
|
-
if (!text.includes("\n")) {
|
|
380
|
-
return `${open}${text}${RESET}`;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Optimized multiline handling with pre-calculated string lengths
|
|
384
|
-
const lines = text.split("\n");
|
|
385
|
-
const result = new Array(lines.length);
|
|
386
|
-
|
|
387
|
-
for (let i = 0; i < lines.length; i++) {
|
|
388
|
-
const line = lines[i];
|
|
389
|
-
if (line.endsWith("\r")) {
|
|
390
|
-
result[i] = `${open}${line.slice(0, -1)}\r${RESET}`;
|
|
391
|
-
} else {
|
|
392
|
-
result[i] = `${open}${line}${RESET}`;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return result.join("\n");
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// Build operations for a color request according to CURRENT_LEVEL
|
|
400
|
-
const mkFgOpsFromRgb = (rgb: Rgb, wantBright = false): SgrOp[] => {
|
|
401
|
-
if (CURRENT_LEVEL === COLOR_LEVEL_BASIC) {
|
|
402
|
-
const idx = nearestBasicIndex(rgb);
|
|
403
|
-
return [{ kind: "fg-basic", idx, bright: wantBright }];
|
|
404
|
-
}
|
|
405
|
-
if (CURRENT_LEVEL === COLOR_LEVEL_256) {
|
|
406
|
-
return [{ kind: "fg-256", code: rgbToAnsi256(rgb) }];
|
|
407
|
-
}
|
|
408
|
-
return [{ kind: "fg-true", rgb }];
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
const mkBgOpsFromRgb = (rgb: Rgb, wantBright = false): SgrOp[] => {
|
|
412
|
-
if (CURRENT_LEVEL === COLOR_LEVEL_BASIC) {
|
|
413
|
-
const idx = nearestBasicIndex(rgb);
|
|
414
|
-
return [{ kind: "bg-basic", idx, bright: wantBright }];
|
|
415
|
-
}
|
|
416
|
-
if (CURRENT_LEVEL === COLOR_LEVEL_256) {
|
|
417
|
-
return [{ kind: "bg-256", code: rgbToAnsi256(rgb) }];
|
|
418
|
-
}
|
|
419
|
-
return [{ kind: "bg-true", rgb }];
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
// Style ops
|
|
423
|
-
const STYLE_TABLE: Record<ReStyleKey, SgrOp> = {
|
|
424
|
-
reset: { kind: "style", open: [SGR_RESET] },
|
|
425
|
-
bold: { kind: "style", open: [SGR_BOLD] },
|
|
426
|
-
dim: { kind: "style", open: [SGR_DIM] },
|
|
427
|
-
italic: { kind: "style", open: [SGR_ITALIC] },
|
|
428
|
-
underline: { kind: "style", open: [SGR_UNDERLINE] },
|
|
429
|
-
inverse: { kind: "style", open: [SGR_INVERSE] },
|
|
430
|
-
hidden: { kind: "style", open: [SGR_HIDDEN] },
|
|
431
|
-
strikethrough: { kind: "style", open: [SGR_STRIKETHROUGH] },
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
// Lookup maps
|
|
435
|
-
const STYLE_KEYS = new Set([
|
|
436
|
-
"reset",
|
|
437
|
-
"bold",
|
|
438
|
-
"dim",
|
|
439
|
-
"italic",
|
|
440
|
-
"underline",
|
|
441
|
-
"inverse",
|
|
442
|
-
"hidden",
|
|
443
|
-
"strikethrough",
|
|
444
|
-
]);
|
|
445
|
-
|
|
446
|
-
// Direct color/bg key checks
|
|
447
|
-
const isColorKey = (key: string): boolean => {
|
|
448
|
-
if (!key || typeof key !== "string") {
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
|
-
// Base colors and extended colors
|
|
452
|
-
if (key in NAMED_COLORS) {
|
|
453
|
-
return true;
|
|
454
|
-
}
|
|
455
|
-
// Bright variants
|
|
456
|
-
if (key.endsWith("Bright") && key.length > BRIGHT_SUFFIX_LENGTH) {
|
|
457
|
-
const baseName = key.slice(0, -BRIGHT_SUFFIX_LENGTH);
|
|
458
|
-
return baseName in NAMED_COLORS;
|
|
459
|
-
}
|
|
460
|
-
return false;
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
const isBgKey = (key: string): boolean => {
|
|
464
|
-
if (!key || typeof key !== "string" || !key.startsWith("bg") || key.length <= BG_PREFIX_LENGTH) {
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
const colorPart = key.charAt(BG_PREFIX_LENGTH).toLowerCase() + key.slice(BG_COLOR_START);
|
|
468
|
-
return isColorKey(colorPart);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// Proxy with performance through pre-computed lookups
|
|
472
|
-
const callableProxy = (ops: SgrOp[]): Re => {
|
|
473
|
-
const base = ((input: ApplyInput) => applyOpsToText(ops, input)) as FormatCallable;
|
|
474
|
-
Object.defineProperty(base, OP_SYMBOL, {
|
|
475
|
-
value: ops,
|
|
476
|
-
enumerable: false,
|
|
477
|
-
configurable: false,
|
|
478
|
-
writable: false,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
return new Proxy(base as unknown as Re, {
|
|
482
|
-
apply(_target, _thisArg, argArray) {
|
|
483
|
-
const [input] = argArray as [ApplyInput];
|
|
484
|
-
return applyOpsToText(ops, input);
|
|
485
|
-
},
|
|
486
|
-
get(_target, prop) {
|
|
487
|
-
const key = String(prop);
|
|
488
|
-
|
|
489
|
-
// Ops extractor for chain()
|
|
490
|
-
if (prop === OP_SYMBOL) {
|
|
491
|
-
return ops;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Fast path for styles using Set lookup
|
|
495
|
-
if (STYLE_KEYS.has(key)) {
|
|
496
|
-
const op = STYLE_TABLE[key as ReStyleKey];
|
|
497
|
-
return callableProxy([...ops, op]);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// Fast path for colors
|
|
501
|
-
if (isBgKey(key)) {
|
|
502
|
-
const raw = key.slice(BG_PREFIX_LENGTH); // remove 'bg'
|
|
503
|
-
if (!raw) {
|
|
504
|
-
return callableProxy(ops); // no-op for empty color name
|
|
505
|
-
}
|
|
506
|
-
const colorName = raw.charAt(0).toLowerCase() + raw.slice(1);
|
|
507
|
-
const { rgb, wantBright } = parseColorName(colorName as ColorName);
|
|
508
|
-
return callableProxy([...ops, ...mkBgOpsFromRgb(rgb, wantBright)]);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (isColorKey(key)) {
|
|
512
|
-
const { rgb, wantBright } = parseColorName(key as ColorName);
|
|
513
|
-
return callableProxy([...ops, ...mkFgOpsFromRgb(rgb, wantBright)]);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Unknown key → return self (no-op), keeps chain resilient
|
|
517
|
-
return callableProxy(ops);
|
|
518
|
-
},
|
|
519
|
-
});
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
// Public root
|
|
523
|
-
export const re: Re = callableProxy([]);
|
|
524
|
-
|
|
525
|
-
// chain(re.bold, re.red, re.underline)("text")
|
|
526
|
-
export const chain = (...parts: FormatCallable[]): Re => {
|
|
527
|
-
const collected: SgrOp[] = [];
|
|
528
|
-
for (const p of parts) {
|
|
529
|
-
const ops = (p as FormatCallable)[OP_SYMBOL] as SgrOp[] | undefined;
|
|
530
|
-
if (ops && ops.length > 0) {
|
|
531
|
-
for (const op of ops) {
|
|
532
|
-
collected.push(op);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return callableProxy(collected);
|
|
537
|
-
};
|
package/tsconfig.json
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"paths": {
|
|
4
|
-
"~/*": ["./src/*"],
|
|
5
|
-
"@/*": ["example/*"]
|
|
6
|
-
},
|
|
7
|
-
"baseUrl": ".",
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"target": "ESNext",
|
|
11
|
-
"lib": ["ESNext"],
|
|
12
|
-
"allowJs": true,
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"moduleDetection": "force",
|
|
15
|
-
"isolatedModules": true,
|
|
16
|
-
"verbatimModuleSyntax": true,
|
|
17
|
-
"strict": true,
|
|
18
|
-
"noUncheckedIndexedAccess": true,
|
|
19
|
-
"noImplicitOverride": true,
|
|
20
|
-
"module": "preserve",
|
|
21
|
-
"moduleResolution": "bundler",
|
|
22
|
-
"noEmit": true,
|
|
23
|
-
"exactOptionalPropertyTypes": false,
|
|
24
|
-
"noFallthroughCasesInSwitch": false,
|
|
25
|
-
"noImplicitAny": false,
|
|
26
|
-
"noImplicitReturns": false,
|
|
27
|
-
"noUnusedLocals": false,
|
|
28
|
-
"noUnusedParameters": false,
|
|
29
|
-
"strictNullChecks": false
|
|
30
|
-
},
|
|
31
|
-
"include": ["src/**/*.ts", "reliverse.ts", "reltypes.ts"],
|
|
32
|
-
"exclude": ["node_modules", "dist*"]
|
|
33
|
-
}
|