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