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.
Files changed (2) hide show
  1. package/index.js +221 -56
  2. package/package.json +6 -3
package/index.js CHANGED
@@ -1,107 +1,272 @@
1
1
  class cstyler {
2
2
  constructor() {
3
- return this.createStyler([]);
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
- purpal: ['\x1b[35m', '\x1b[39m'],
25
- gray: ['\x1b[30m', '\x1b[39m'],
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
- bgPurpal: ['\x1b[45m', '\x1b[49m'],
32
- bgGray: ['\x1b[40m', '\x1b[49m']
26
+ bgPurple: ['\x1b[45m', '\x1b[49m'],
27
+ bgGray: ['\x1b[100m', '\x1b[49m'],
33
28
  };
34
29
 
35
- // Dynamically define valid style accessors
36
- for (const [name, [open, close]] of Object.entries(styleMap)) {
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 support
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'); // white
50
+ return addStyle('\x1b[37m', '\x1b[39m');
47
51
  }
48
- const open = `\x1b[38;2;${r};${g};${b}m`;
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 support
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('#', '').slice(0, 6);
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 (e) {
64
+ } catch {
63
65
  console.error('Invalid hex color. Falling back to white.');
64
- return addStyle('\x1b[37m', '\x1b[39m'); // white
66
+ return addStyle('\x1b[37m', '\x1b[39m');
65
67
  }
66
68
  };
67
69
 
68
- // Background RGB
69
- styler.bgrgb = (r, g, b) => {
70
- if (![r, g, b].every(n => Number.isInteger(n) && n >= 0 && n <= 255)) {
71
- console.error('Invalid background RGB value. Falling back to white.');
72
- return addStyle('\x1b[47m', '\x1b[49m');
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 open = `\x1b[48;2;${r};${g};${b}m`;
75
- const close = '\x1b[49m';
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
- // Background Hex
80
- styler.bghex = (hex) => {
81
- try {
82
- if (typeof hex !== 'string') throw new Error();
83
- hex = hex.replace('#', '').slice(0, 6);
84
- const r = parseInt(hex.slice(0, 2), 16);
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
- // Use Proxy to catch invalid property access
95
- return new Proxy(styler, {
96
- get(target, prop) {
97
- if (prop in target) {
98
- return target[prop];
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
- console.log(`Wrong style: ${String(prop)}`);
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": "2.0.0",
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
+ }