cstyler 2.0.0 → 3.0.0
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/index.js +221 -56
- package/package.json +6 -3
package/index.js
CHANGED
|
@@ -1,107 +1,272 @@
|
|
|
1
1
|
class cstyler {
|
|
2
2
|
constructor() {
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
createStyler(styles) {
|
|
7
|
-
const styler = (text) => {
|
|
8
|
-
return styles.reduce((str, styleCode) => styleCode.open + str + styleCode.close, text);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const addStyle = (open, close) => this.createStyler([...styles, { open, close }]);
|
|
12
|
-
|
|
13
|
-
const styleMap = {
|
|
3
|
+
this.styleMap = {
|
|
14
4
|
// Text styles
|
|
15
5
|
bold: ['\x1b[1m', '\x1b[22m'],
|
|
16
6
|
italic: ['\x1b[3m', '\x1b[23m'],
|
|
17
7
|
underline: ['\x1b[4m', '\x1b[24m'],
|
|
18
8
|
dark: ['\x1b[2m', '\x1b[22m'],
|
|
9
|
+
|
|
19
10
|
// Foreground colors
|
|
20
11
|
red: ['\x1b[31m', '\x1b[39m'],
|
|
21
12
|
green: ['\x1b[32m', '\x1b[39m'],
|
|
22
13
|
yellow: ['\x1b[33m', '\x1b[39m'],
|
|
23
14
|
blue: ['\x1b[34m', '\x1b[39m'],
|
|
24
|
-
|
|
25
|
-
gray: ['\x1b[
|
|
15
|
+
purple: ['\x1b[35m', '\x1b[39m'],
|
|
16
|
+
gray: ['\x1b[90m', '\x1b[39m'],
|
|
17
|
+
white: ['\x1b[97m', '\x1b[39m'],
|
|
18
|
+
cyan: ['\x1b[36m', '\x1b[39m'],
|
|
19
|
+
magenta: ['\x1b[35m', '\x1b[39m'],
|
|
20
|
+
|
|
26
21
|
// Background colors
|
|
27
22
|
bgRed: ['\x1b[41m', '\x1b[49m'],
|
|
28
23
|
bgGreen: ['\x1b[42m', '\x1b[49m'],
|
|
29
24
|
bgYellow: ['\x1b[43m', '\x1b[49m'],
|
|
30
25
|
bgBlue: ['\x1b[44m', '\x1b[49m'],
|
|
31
|
-
|
|
32
|
-
bgGray: ['\x1b[
|
|
26
|
+
bgPurple: ['\x1b[45m', '\x1b[49m'],
|
|
27
|
+
bgGray: ['\x1b[100m', '\x1b[49m'],
|
|
33
28
|
};
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
return this.createStyler([]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
createStyler(styles) {
|
|
34
|
+
const styler = (text) =>
|
|
35
|
+
styles.reduce((str, { open, close }) => open + str + close, text);
|
|
36
|
+
|
|
37
|
+
const addStyle = (open, close) =>
|
|
38
|
+
this.createStyler([...styles, { open, close }]);
|
|
39
|
+
|
|
40
|
+
for (const [name, [open, close]] of Object.entries(this.styleMap)) {
|
|
37
41
|
Object.defineProperty(styler, name, {
|
|
38
|
-
get: () => addStyle(open, close)
|
|
42
|
+
get: () => addStyle(open, close),
|
|
39
43
|
});
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
// RGB
|
|
46
|
+
// RGB foreground color
|
|
43
47
|
styler.rgb = (r, g, b) => {
|
|
44
|
-
if (![r, g, b].every(n => Number.isInteger(n) && n >= 0 && n <= 255)) {
|
|
48
|
+
if (![r, g, b].every((n) => Number.isInteger(n) && n >= 0 && n <= 255)) {
|
|
45
49
|
console.error('Invalid RGB value. Falling back to white.');
|
|
46
|
-
return addStyle('\x1b[37m', '\x1b[39m');
|
|
50
|
+
return addStyle('\x1b[37m', '\x1b[39m');
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
const close = '\x1b[39m';
|
|
50
|
-
return addStyle(open, close);
|
|
52
|
+
return addStyle(`\x1b[38;2;${r};${g};${b}m`, '\x1b[39m');
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
// Hex color
|
|
55
|
+
// Hex foreground color
|
|
54
56
|
styler.hex = (hex) => {
|
|
55
57
|
try {
|
|
56
58
|
if (typeof hex !== 'string') throw new Error();
|
|
57
|
-
hex = hex.replace(
|
|
59
|
+
hex = hex.replace(/^#/, '').slice(0, 6);
|
|
58
60
|
const r = parseInt(hex.slice(0, 2), 16);
|
|
59
61
|
const g = parseInt(hex.slice(2, 4), 16);
|
|
60
62
|
const b = parseInt(hex.slice(4, 6), 16);
|
|
61
63
|
return styler.rgb(r, g, b);
|
|
62
|
-
} catch
|
|
64
|
+
} catch {
|
|
63
65
|
console.error('Invalid hex color. Falling back to white.');
|
|
64
|
-
return addStyle('\x1b[37m', '\x1b[39m');
|
|
66
|
+
return addStyle('\x1b[37m', '\x1b[39m');
|
|
65
67
|
}
|
|
66
68
|
};
|
|
67
69
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
// HSL to RGB conversion helper
|
|
71
|
+
function hslToRgb(h, s, l) {
|
|
72
|
+
s /= 100;
|
|
73
|
+
l /= 100;
|
|
74
|
+
const k = (n) => (n + h / 30) % 12;
|
|
75
|
+
const a = s * Math.min(l, 1 - l);
|
|
76
|
+
const f = (n) =>
|
|
77
|
+
l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
78
|
+
return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
styler.hsl = (h, s, l) => {
|
|
82
|
+
if (
|
|
83
|
+
typeof h !== 'number' ||
|
|
84
|
+
h < 0 ||
|
|
85
|
+
h > 360 ||
|
|
86
|
+
typeof s !== 'number' ||
|
|
87
|
+
s < 0 ||
|
|
88
|
+
s > 100 ||
|
|
89
|
+
typeof l !== 'number' ||
|
|
90
|
+
l < 0 ||
|
|
91
|
+
l > 100
|
|
92
|
+
) {
|
|
93
|
+
console.error('Invalid HSL value. Falling back to white.');
|
|
94
|
+
return addStyle('\x1b[37m', '\x1b[39m');
|
|
73
95
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
return addStyle(open, close);
|
|
96
|
+
const [r, g, b] = hslToRgb(h, s, l);
|
|
97
|
+
return styler.rgb(r, g, b);
|
|
77
98
|
};
|
|
78
99
|
|
|
79
|
-
//
|
|
80
|
-
styler.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
86
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
87
|
-
return styler.bgrgb(r, g, b);
|
|
88
|
-
} catch (e) {
|
|
89
|
-
console.error('Invalid background hex. Falling back to white.');
|
|
90
|
-
return addStyle('\x1b[47m', '\x1b[49m');
|
|
100
|
+
// Parse tagged template literals with inline nested styles
|
|
101
|
+
styler.tag = (strings, ...values) => {
|
|
102
|
+
let fullText = '';
|
|
103
|
+
for (let i = 0; i < strings.length; i++) {
|
|
104
|
+
fullText += strings[i];
|
|
105
|
+
if (i < values.length) fullText += values[i];
|
|
91
106
|
}
|
|
107
|
+
return this.parseInlineStyles(fullText);
|
|
92
108
|
};
|
|
93
109
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
// Helper: parse parameters string to array for hex, rgb, hsl
|
|
111
|
+
this.parseParameters = (paramStr) => {
|
|
112
|
+
// Remove spaces outside quotes, split by commas respecting quotes
|
|
113
|
+
const params = [];
|
|
114
|
+
let current = '';
|
|
115
|
+
let inQuotes = false;
|
|
116
|
+
for (let c of paramStr.trim()) {
|
|
117
|
+
if (c === "'" || c === '"') {
|
|
118
|
+
inQuotes = !inQuotes;
|
|
119
|
+
current += c;
|
|
120
|
+
} else if (c === ',' && !inQuotes) {
|
|
121
|
+
if (current.length) params.push(current.trim());
|
|
122
|
+
current = '';
|
|
99
123
|
} else {
|
|
100
|
-
|
|
101
|
-
console.error(`Invalid property accessor used: ${String(prop)}`);
|
|
102
|
-
return target; // Return unstyled version to continue chain safely
|
|
124
|
+
current += c;
|
|
103
125
|
}
|
|
104
126
|
}
|
|
127
|
+
if (current.length) params.push(current.trim());
|
|
128
|
+
// Remove quotes from strings
|
|
129
|
+
return params.map((p) => p.replace(/^['"]|['"]$/g, ''));
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Recursive parser for nested inline styles with support for parameterized styles
|
|
133
|
+
this.parseInlineStyles = (text) => {
|
|
134
|
+
const parse = (str, start = 0) => {
|
|
135
|
+
let result = '';
|
|
136
|
+
let i = start;
|
|
137
|
+
|
|
138
|
+
while (i < str.length) {
|
|
139
|
+
if (str[i] === '{') {
|
|
140
|
+
// Find matching closing brace for nested block
|
|
141
|
+
let level = 1;
|
|
142
|
+
let j = i + 1;
|
|
143
|
+
while (j < str.length && level > 0) {
|
|
144
|
+
if (str[j] === '{') level++;
|
|
145
|
+
else if (str[j] === '}') level--;
|
|
146
|
+
j++;
|
|
147
|
+
}
|
|
148
|
+
if (level !== 0) {
|
|
149
|
+
// Unmatched brace, treat literally
|
|
150
|
+
result += str.slice(i);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Extract content between braces
|
|
155
|
+
const content = str.slice(i + 1, j - 1);
|
|
156
|
+
|
|
157
|
+
// Find first space to separate styles from text
|
|
158
|
+
const spaceIdx = content.indexOf(' ');
|
|
159
|
+
|
|
160
|
+
if (spaceIdx === -1) {
|
|
161
|
+
// No space, treat as literal text with braces
|
|
162
|
+
result += '{' + content + '}';
|
|
163
|
+
} else {
|
|
164
|
+
const styleStr = content.slice(0, spaceIdx);
|
|
165
|
+
const innerText = content.slice(spaceIdx + 1);
|
|
166
|
+
|
|
167
|
+
// Parse styles separated by '.'
|
|
168
|
+
// But styles may have params, like hex('#ff00ff'), rgb(255,0,0)
|
|
169
|
+
const styleParts = [];
|
|
170
|
+
|
|
171
|
+
let currentStyle = '';
|
|
172
|
+
let parenLevel = 0;
|
|
173
|
+
for (let ch of styleStr) {
|
|
174
|
+
if (ch === '.' && parenLevel === 0) {
|
|
175
|
+
if (currentStyle) styleParts.push(currentStyle);
|
|
176
|
+
currentStyle = '';
|
|
177
|
+
} else {
|
|
178
|
+
if (ch === '(') parenLevel++;
|
|
179
|
+
else if (ch === ')') parenLevel--;
|
|
180
|
+
currentStyle += ch;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (currentStyle) styleParts.push(currentStyle);
|
|
184
|
+
|
|
185
|
+
// Parse inner text recursively (to support nested styles)
|
|
186
|
+
const parsedInner = parse(innerText, 0);
|
|
187
|
+
|
|
188
|
+
// Apply styles in order (left to right)
|
|
189
|
+
let styledText = parsedInner;
|
|
190
|
+
|
|
191
|
+
for (let style of styleParts.reverse()) {
|
|
192
|
+
// Check if style has parameters (like hex(...))
|
|
193
|
+
let name = style;
|
|
194
|
+
let params = null;
|
|
195
|
+
|
|
196
|
+
const paramStart = style.indexOf('(');
|
|
197
|
+
if (paramStart !== -1 && style.endsWith(')')) {
|
|
198
|
+
name = style.slice(0, paramStart);
|
|
199
|
+
const paramStr = style.slice(paramStart + 1, -1);
|
|
200
|
+
params = this.parseParameters(paramStr);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Handle styles
|
|
204
|
+
if (name === 'hex' && params && params.length === 1) {
|
|
205
|
+
styledText = this.styleMap[name]
|
|
206
|
+
? this.applyStyle(styledText, this.styleMap[name])
|
|
207
|
+
: styler.hex(params[0])(styledText);
|
|
208
|
+
} else if (
|
|
209
|
+
name === 'rgb' &&
|
|
210
|
+
params &&
|
|
211
|
+
params.length === 3 &&
|
|
212
|
+
params.every((p) => !isNaN(p))
|
|
213
|
+
) {
|
|
214
|
+
styledText = styler.rgb(
|
|
215
|
+
parseInt(params[0]),
|
|
216
|
+
parseInt(params[1]),
|
|
217
|
+
parseInt(params[2])
|
|
218
|
+
)(styledText);
|
|
219
|
+
} else if (
|
|
220
|
+
name === 'hsl' &&
|
|
221
|
+
params &&
|
|
222
|
+
params.length === 3 &&
|
|
223
|
+
params.every((p) => !isNaN(p))
|
|
224
|
+
) {
|
|
225
|
+
styledText = styler.hsl(
|
|
226
|
+
parseFloat(params[0]),
|
|
227
|
+
parseFloat(params[1]),
|
|
228
|
+
parseFloat(params[2])
|
|
229
|
+
)(styledText);
|
|
230
|
+
} else if (this.styleMap[name]) {
|
|
231
|
+
const [open, close] = this.styleMap[name];
|
|
232
|
+
styledText = open + styledText + close;
|
|
233
|
+
} else {
|
|
234
|
+
console.warn(`Unknown style: ${style}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
result += styledText;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
i = j;
|
|
242
|
+
} else {
|
|
243
|
+
result += str[i];
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result;
|
|
249
|
+
};
|
|
250
|
+
return parse(text);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Helper to apply raw style array (for internal use)
|
|
254
|
+
this.applyStyle = (text, [open, close]) => open + text + close;
|
|
255
|
+
|
|
256
|
+
return new Proxy(styler, {
|
|
257
|
+
apply: (target, thisArg, args) => {
|
|
258
|
+
// Tagged template literal usage
|
|
259
|
+
if (Array.isArray(args[0]) && 'raw' in args[0]) {
|
|
260
|
+
return styler.tag(...args);
|
|
261
|
+
}
|
|
262
|
+
// Direct call with string
|
|
263
|
+
return styler(...args);
|
|
264
|
+
},
|
|
265
|
+
get: (target, prop) => {
|
|
266
|
+
if (prop in styler) return styler[prop];
|
|
267
|
+
console.warn(`Invalid style: ${String(prop)}`);
|
|
268
|
+
return styler;
|
|
269
|
+
},
|
|
105
270
|
});
|
|
106
271
|
}
|
|
107
272
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cstyler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Chainable terminal text styling tool (bold, colors, etc.)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
"author": "MD Nasiruddin Ahmed",
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"type": "commonjs",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "echo \"No tests specified\" && exit 0"
|
|
20
|
+
},
|
|
18
21
|
"repository": {
|
|
19
22
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/kormoi/cstyler"
|
|
23
|
+
"url": "git+https://github.com/kormoi/cstyler.git"
|
|
21
24
|
},
|
|
22
25
|
"bugs": {
|
|
23
26
|
"url": "https://github.com/kormoi/cstyler/issues"
|
|
24
27
|
},
|
|
25
28
|
"homepage": "https://github.com/kormoi/cstyler#readme"
|
|
26
|
-
}
|
|
29
|
+
}
|