-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
385 lines (352 loc) · 10.7 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/// <reference types="./index.d.ts">
/**
* 仿照 colors的api,借助ansi字符,做一个带有颜色的输出
* 对于不支持颜色的控制台来说,可能会输出一些奇怪的ansi字符
*/
import util from 'util'; // nodejs 端,先不考虑 browser 端
import debug from 'debug';
const log = debug('colorcc');
const foreAnsiCode = {
black: 30,
red: 31,
green: 32,
yellow: 33,
blue: 34,
magenta: 35,
cyan: 36,
white: 37,
gray: 90,
grey: 90,
brightBlack: 90,
brightRed: 91,
brightGreen: 92,
brightYellow: 99,
brightBlue: 94,
brightMagenta: 95,
brightCyan: 96,
brightWhite: 97,
};
const backAnsiCode = {
bgBlack: 40,
bgRed: 41,
bgGreen: 42,
bgYellow: 43,
bgBlue: 44,
bgMagenta: 45,
bgCyan: 46,
bgWhite: 47,
bgGray: 100,
bgGrey: 100,
bgBrightBlack: 100,
bgBrightRed: 101,
bgBrightGreen: 102,
bgBrightYellow: 103,
bgBrightBlue: 104,
bgBrightMagenta: 105,
bgBrightCyan: 106,
bgBrightWhite: 107,
};
const styleAnsiCode = {
bold: 1,
underline: 4,
blink: 5,
strike: 9,
};
export type ForeCodePropName = keyof typeof foreAnsiCode;
export type BackCodePropName = keyof typeof backAnsiCode;
export type StyleCodePropName = keyof typeof styleAnsiCode;
export type AllCodePropName = ForeCodePropName | BackCodePropName | StyleCodePropName;
// 函数名集合,完全和上面的属性值保持一致,目前暂时没想到比较好的办法,先穷举
const foreFuns: ForeCodePropName[] = [
'black',
'red',
'green',
'yellow',
'blue',
'magenta',
'cyan',
'white',
'gray',
'grey',
// 特有
'brightBlack',
'brightRed',
'brightGreen',
'brightYellow',
'brightBlue',
'brightMagenta',
'brightCyan',
'brightWhite',
];
const backFuns: BackCodePropName[] = [
'bgBlack',
'bgRed',
'bgGreen',
'bgYellow',
'bgBlue',
'bgMagenta',
'bgCyan',
'bgWhite',
'bgGray',
'bgGrey',
// 特有
'bgWhite',
'bgBrightBlack',
'bgBrightRed',
'bgBrightGreen',
'bgBrightYellow',
'bgBrightBlue',
'bgBrightMagenta',
'bgBrightCyan',
'bgBrightWhite',
];
const styleFuns: StyleCodePropName[] = [
'bold',
'underline',
'blink',
'strike',
// 'normal'
// 'reset',
// 'dim',
// 'italic',
// 'inverse',
// 'hidden',
// 'strikethrough',
];
export interface ColorifyOptions {
fore: ForeCodePropName;
back: BackCodePropName;
style: StyleCodePropName;
}
/**
* 使得输出带上颜色
* @param {string} text
* @param {object} options
* @returns {string}
*/
export function colorify(text: string | object, options?: Partial<ColorifyOptions>) {
//
const content = isObject(text) ? JSON.stringify(text) : text;
//
const { fore, back, style } = options || {};
// if (!fore) {
// return content as string;
// }
const codes: number[] = [];
const foreCode = foreAnsiCode[fore];
isCorrectCode(foreCode) && codes.push(foreCode);
const backCode = backAnsiCode[back];
isCorrectCode(backCode) && codes.push(backCode);
const styleCode = styleAnsiCode[style];
isCorrectCode(styleCode) && codes.push(styleCode);
const FIX_CHARS = '\x1b'; // "\033";
const outStr = `${FIX_CHARS}[${codes.join(';')}m${content}${FIX_CHARS}[0m`; // eg: '\x1b[31m 我会变成红色文字 \x1b[m'
// log('colorify::ansiCode:', { foreCode, backCode, styleCode });
// log('colorify::', JSON.stringify(outStr));
return outStr;
}
/**
* 生成转换函数
* @param {Partial<ColorifyOptions>} options
* @returns {Function}
*/
function genFn(options?: Partial<ColorifyOptions>) {
const { fore, back, style } = options || {};
const hasNoParam = fore === undefined && back === undefined && style === undefined;
const raw = (str: any) => str as string; // 不处理,原封不动返回
//
return function getColorfulText(...args: any[]) {
// log('getColorfulText::', JSON.stringify({ hasNoParam, fore, back, style }));
const aLen = args.length;
if (args.length <= 1) {
const res = aLen ? args[0] : '';
return hasNoParam ? raw(res) : colorify(res, { fore, back, style });
} else {
const result = args.map(argItem => (hasNoParam ? raw(argItem) : colorify(argItem, { fore, back, style })));
return result.join(' ');
}
};
}
// 单层函数
type ColorFunc = (...args: any[]) => string;
type StyleMethods = {
[k in StyleCodePropName]: ColorFunc;
};
type BackMethods = {
[k in BackCodePropName]: ColorFunc & StyleMethods;
};
type ForeMethods = {
[k in ForeCodePropName]: ColorFunc & BackMethods & StyleMethods;
};
type AllMethods = ForeMethods & BackMethods & StyleMethods;
/**
* type
*/
export type ColorTool = AllMethods & {
//
colorify: typeof colorify;
//
warn: ColorFunc; // [warn] xxx
warning: ColorFunc; // [warn] xxx, (alias for warn)
error: ColorFunc; // [error] xxx
success: ColorFunc; // ✔️ xxxxx
fail: ColorFunc; // ✘ xxxxx
def: ColorFunc; // > xxx
};
/**
* expose
* @param {boolean} useColor 是否需要颜色
* @returns {ColorTool} colorsTool
* @example
* let colors = createColorfulTool(true);
* const msg = colors.fore.back.style(text); //
* console.log(msg);
* @description
* 注意:fore,bg,style 可以反向逐级缺省,但是前后顺序不能乱 fore > back > style
* eg:
* - colors.red.bgRed.bold
* - colors.red.bold
* - colors.bgRed
* - colors.red
*/
export function createColorfulTool(useColor?: boolean) {
const colorsTool: Partial<ColorTool> = {
colorify,
};
const isProdEnv = process.env.NODE_ENV === 'production';
//参数缺省时,非production环境变量,都需要带上颜色
const needColor = useColor === undefined ? !isProdEnv : !!useColor;
// 1.[colors.fore?.xxx?.xxx]
for (const fKey of foreFuns) {
// 1.1.[colors.fore], eg: colors.red
colorsTool[fKey] = (needColor ? genFn({ fore: fKey }) : genNoop()) as ColorFunc & BackMethods & StyleMethods;
log(`F: colors.${fKey}`);
// 1.2.[colors.fore.back], eg: colors.red.bgYellow
for (const bKey of backFuns) {
colorsTool[fKey][bKey] = (needColor ? genFn({ fore: fKey, back: bKey }) : genNoop()) as ColorFunc & StyleMethods;
log(`FB: colors.${fKey}.${bKey}`);
// 1.3.[colors.fore.back.style], eg: colors.red.bgYellow.bold
for (const sKey of styleFuns) {
colorsTool[fKey][bKey][sKey] = needColor ? genFn({ fore: fKey, back: bKey, style: sKey }) : genNoop();
log(`FBS: colors.${fKey}.${bKey}.${sKey}`);
}
}
// 1.4.[colors.fore.style], eg: colors.red.bold
for (const sKey of styleFuns) {
(colorsTool[fKey] as StyleMethods)[sKey] = needColor ? genFn({ fore: fKey, style: sKey }) : genNoop();
log(`FS: colors.${fKey}.${sKey}`);
}
}
// [colors.back?.xx]
for (const bKey of backFuns) {
// [colors.back], eg: colors.bgYellow
colorsTool[bKey] = (needColor ? genFn({ back: bKey }) : genNoop()) as ColorFunc & StyleMethods;
log(`B: colors.${bKey}`);
// [colors.back.style], eg: colors.bgYellow.bold
for (const sKey of styleFuns) {
colorsTool[bKey][sKey] = needColor ? genFn({ back: bKey, style: sKey }) : genNoop();
log(`BS: colors.${bKey}.${sKey}`);
}
}
// [colors.style], eg: colors.bold
for (const sKey of styleFuns) {
(colorsTool as StyleMethods)[sKey] = needColor ? genFn({ style: sKey }) : genNoop();
log(`S: colors.${sKey}`);
}
// 常规组合:warn error success fail
// [warning] xxxxx
colorsTool.warn = colorsTool.warning = function (str: any) {
str = typeof str === 'string' ? str : JSON.stringify(str);
const prefix = '[warn]';
const colos = colorsTool as ForeMethods;
const prefixColor = (colos.red as BackMethods).bgYellow(prefix);
const space = ' '.repeat(prefix.length + 1);
str = str?.replace('\r', '');
return `${prefixColor} ${colorsTool.yellow(str.split('\n').join(`\n${space}`))}`;
};
// [error] xxxxx
colorsTool.error = function (str: any) {
str = typeof str === 'string' ? str : JSON.stringify(str);
const prefix = '[error]';
const colors = colorsTool as ForeMethods;
const prefixColor = ((colors.red as BackMethods).bgBrightYellow as StyleMethods).bold(prefix);
const space = ' '.repeat(prefix.length + 1);
str = str?.replace('\r', '');
return `${prefixColor} ${colorsTool.red.bold(str.split('\n').join(`\n${space}`))}`;
};
// ✔ xxxxx
colorsTool.success = function (str: any) {
str = typeof str === 'string' ? str : JSON.stringify(str);
return `${colorsTool.green('✔')} ${str?.replace('\r', '')}`;
};
// ✘ xxxxx
colorsTool.fail = function (str: any) {
str = typeof str === 'string' ? str : JSON.stringify(str);
return `${colorsTool.red('✘')} ${str?.replace('\r', '')}`;
};
// > xxxxx
colorsTool.def = function (str: any) {
str = typeof str === 'string' ? str : JSON.stringify(str);
return `${colorsTool.gray('>')} ${str?.replace('\r', '')}`;
};
return colorsTool as ColorTool;
}
/**
* ColorCC
*/
export const ColorCC = createColorfulTool(true);
//
export default ColorCC;
/*
// com
console.log('');
console.log(ColorCC.error('error text...'));
console.log(ColorCC.warn('warning text...'));
console.log(ColorCC.warning('warning text...'));
console.log(ColorCC.success('success text...'));
console.log(ColorCC.fail('fail text...'));
// F, F.B, F.S, F.B.S
console.log('');
console.log(ColorCC.red('red'));
console.log(ColorCC.red.bgYellow('red.bgYellow'));
console.log(ColorCC.red.strike('red.strike'));
console.log(ColorCC.red.bgYellow.strike('red.bgYellow.strike'));
// B, B.S
console.log('');
console.log(ColorCC.bgYellow('bgYellow'));
console.log(ColorCC.bgYellow.strike('bgYellow.strike'));
// S
console.log('');
console.log(ColorCC.bold('bold'));
console.log(ColorCC.underline('underline'));
console.log(ColorCC.blink('blink'));
console.log(ColorCC.strike('strike'));
*/
// =================================== common defines ==============================================
/**
* 每次执行都产生一个新的空函数,主要是本案例中颜色函数本身上面还会挂颜色函数,
* - 如果使用一个noop,会导致其子属性和自己形成循环依赖(noop.noop=noop, noop.noop.noop=noop)
* @returns {Function} noop
*/
function genNoop() {
// 原样输出
const noop = (...args: any) => util.format(...args); // 每次都产生一个新的 noop 函数
return noop;
}
/**
* 判断是否具有正常的 ansicode
* @param {unknown} code
* @returns {boolean}
*/
function isCorrectCode(code: unknown): code is NonNullable<number> {
// 原生 isNaN 对 understand 和 自定义 class 的实例会误判,不过这里不影响
return !(code === undefined || code === null) && !isNaN(code as any);
}
/**
* 判断 object/array,会排除 null
* @param {unknown} obj
* @returns {boolean}
*/
function isObject(obj: unknown): obj is NonNullable<object> {
return obj !== null && typeof obj === 'object';
}