简易版chalk
贴一下项目地址:https://github.com/xumeng03/chalk
“ANSI 转义序列(ANSI escape sequences)是一种带内信号的转义序列标准,用于控制视频文本终端上的光标位置、颜色和其他选项。在文本中嵌入确定的字节序列,大部分以 ESC 转义字符和"["字符开始,终端会把这些字节序列解释为相应的指令,而不是普通的字符编码。”
根据描述我们知道需要使用一些特殊的转义字符来实现不同字体风格、颜色、背景色。
1、字体风格
根据wiki
选择图形再现(SGR)参数
的描述编写 Ansi 码表,同时获取一下码表的key
/**
* ESC 按键的 Ansi 码
*/
export const ESC_CODE: string = "\u001B"
/**
* 图形再现参数的 Ansi 码表1(开启)
*/
const Style1 = {
// 前景色
'foreground': '38;2;',
// 背景色
'background': '48;2;',
// 加粗
'bold': '1',
// 暗淡
'dim': '2',
// 倾斜
'italic': '3',
// 下划线
'underline': '4',
// 闪烁
'flick': '5',
// 快速闪烁
'fast_flick': '6',
// 反转
'inverse': '7',
// 隐藏
'hidden': '8',
// 删除线
'strikethrough': '9',
// 上划线
'overline': '53',
};
/**
* 图形再现参数的 Ansi 码表2(关闭)
*/
const Style2 = {
// 前景色
'foreground': '39',
// 背景色
'background': '49',
// 加粗
'bold': '22',
// 暗淡
'dim': '22',
// 倾斜
'italic': '23',
// 下划线
'underline': '24',
// 闪烁
'flick': '25',
// 快速闪烁
'fast_flick': '25',
// 反转
'inverse': '27',
// 隐藏
'hidden': '28',
// 删除线
'strikethrough': '29',
// 上划线
'overline': '55',
};
export type Style1 = keyof typeof Style1
export const EscapeSequence1 = (key: Style1, ...args: any[]) => {
const code = Style1[key];
// return String.raw`${ESC_CODE}[${code}${args.join(';')}m`;
return `${ESC_CODE}[${code}${args.join(';')}m`;
}
export type Style2 = keyof typeof Style2
export const EscapeSequence2 = (key: Style2) => {
const code = Style2[key]
// return String.raw`${ESC_CODE}[${code}m`;
return `${ESC_CODE}[${code}m`;
}
export type Styles = Style1 & Style2
2、颜色
这里不再像chalk一样支持3/4位颜色、8位颜色,而是仅仅支持真彩色(TrueColor),根据wiki
颜色
的描述补充 Ansi 码表的
foreground
、
background
。
这里需要先判断一下终端是否支持真彩色(并未给出所有平台的判断方案)。
import process from "node:process";
import os from "node:os";
import {execSync} from 'child_process';
/**
* 判断是否支持当前环境是否支持 TrueColor
* @returns {boolean} 是否支持 TrueColor
*/
export default (): boolean => {
if (process.platform === 'win32') {
// Windows 10 build 14931 is the first release that supports 16m/TrueColor.
const osRelease = os.release().split('.');
return Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 14_931;
} else if (process.platform === 'darwin') {
const result = execSync('tput colors');
const numColors = parseInt(result.toString().trim(), 10);
return numColors >= 256;
}
return false;
}
颜色名称根据 CSS3 种的 147 个名称去定义。
/**
* CSS3 标准中的 147 中颜色定义
*/
export const ColorMapping = {
"aliceblue": "#f0f8ff",
"antiquewhite": "#faebd7",
"aqua": "#00ffff",
"aquamarine": "#7fffd4",
"azure": "#f0ffff",
"beige": "#f5f5dc",
"bisque": "#ffe4c4",
"black": "#000000",
"blanchedalmond": "#ffebcd",
"blue": "#0000ff",
"blueviolet": "#8a2be2",
"brown": "#a52a2a",
"burlywood": "#deb887",
"cadetblue": "#5f9ea0",
"chartreuse": "#7fff00",
"chocolate": "#d2691e",
"coral": "#ff7f50",
"cornflowerblue": "#6495ed",
"cornsilk": "#fff8dc",
"crimson": "#dc143c",
"cyan": "#00ffff",
"darkblue": "#00008b",
"darkcyan": "#008b8b",
"darkgoldenrod": "#b8860b",
"darkgray": "#a9a9a9",
"darkgreen": "#006400",
"darkgrey": "#a9a9a9",
"darkkhaki": "#bdb76b",
"darkmagenta": "#8b008b",
"darkolivegreen": "#556b2f",
"darkorange": "#ff8c00",
"darkorchid": "#9932cc",
"darkred": "#8b0000",
"darksalmon": "#e9967a",
"darkseagreen": "#8fbc8f",
"darkslateblue": "#483d8b",
"darkslategray": "#2f4f4f",
"darkslategrey": "#2f4f4f",
"darkturquoise": "#00ced1",
"darkviolet": "#9400d3",
"deeppink": "#ff1493",
"deepskyblue": "#00bfff",
"dimgray": "#696969",
"dimgrey": "#696969",
"dodgerblue": "#1e90ff",
"firebrick": "#b22222",
"floralwhite": "#fffaf0",
"forestgreen": "#228b22",
"fuchsia": "#ff00ff",
"gainsboro": "#dcdcdc",
"ghostwhite": "#f8f8ff",
"gold": "#ffd700",
"goldenrod": "#daa520",
"gray": "#808080",
"green": "#008000",
"greenyellow": "#adff2f",
"grey": "#808080",
"honeydew": "#f0fff0",
"hotpink": "#ff69b4",
"indianred": "#cd5c5c",
"indigo": "#4b0082",
"ivory": "#fffff0",
"khaki": "#f0e68c",
"lavender": "#e6e6fa",
"lavenderblush": "#fff0f5",
"lawngreen": "#7cfc00",
"lemonchiffon": "#fffacd",
"lightblue": "#add8e6",
"lightcoral": "#f08080",
"lightcyan": "#e0ffff",
"lightgoldenrodyellow": "#fafad2",
"lightgray": "#d3d3d3",
"lightgreen": "#90ee90",
"lightgrey": "#d3d3d3",
"lightpink": "#ffb6c1",
"lightsalmon": "#ffa07a",
"lightseagreen": "#20b2aa",
"lightskyblue": "#87cefa",
"lightslategray": "#778899",
"lightslategrey": "#778899",
"lightsteelblue": "#b0c4de",
"lightyellow": "#ffffe0",
"lime": "#00ff00",
"limegreen": "#32cd32",
"linen": "#faf0e6",
"magenta": "#ff00ff",
"maroon": "#800000",
"mediumaquamarine": "#66cdaa",
"mediumblue": "#0000cd",
"mediumorchid": "#ba55d3",
"mediumpurple": "#9370db",
"mediumseagreen": "#3cb371",
"mediumslateblue": "#7b68ee",
"mediumspringgreen": "#00fa9a",
"mediumturquoise": "#48d1cc",
"mediumvioletred": "#c71585",
"midnightblue": "#191970",
"mintcream": "#f5fffa",
"mistyrose": "#ffe4e1",
"moccasin": "#ffe4b5",
"navajowhite": "#ffdead",
"navy": "#000080",
"oldlace": "#fdf5e6",
"olive": "#808000",
"olivedrab": "#6b8e23",
"orange": "#ffa500",
"orangered": "#ff4500",
"orchid": "#da70d6",
"palegoldenrod": "#eee8aa",
"palegreen": "#98fb98",
"paleturquoise": "#afeeee",
"palevioletred": "#db7093",
"papayawhip": "#ffefd5",
"peachpuff": "#ffdab9",
"peru": "#cd853f",
"pink": "#ffc0cb",
"plum": "#dda0dd",
"powderblue": "#b0e0e6",
"purple": "#800080",
"red": "#ff0000",
"rosybrown": "#bc8f8f",
"royalblue": "#4169e1",
"saddlebrown": "#8b4513",
"salmon": "#fa8072",
"sandybrown": "#f4a460",
"seagreen": "#2e8b57",
"seashell": "#fff5ee",
"sienna": "#a0522d",
"silver": "#c0c0c0",
"skyblue": "#87ceeb",
"slateblue": "#6a5acd",
"slategray": "#708090",
"slategrey": "#708090",
"snow": "#fffafa",
"springgreen": "#00ff7f",
"steelblue": "#4682b4",
"tan": "#d2b48c",
"teal": "#008080",
"thistle": "#d8bfd8",
"tomato": "#ff6347",
"turquoise": "#40e0d0",
"violet": "#ee82ee",
"wheat": "#f5deb3",
"white": "#ffffff",
"whitesmoke": "#f5f5f5",
"yellow": "#ffff00",
"yellowgreen": "#9acd32"
}
export type Colors = keyof typeof ColorMapping
export const isColorName = (str: string) => {
return (str in ColorMapping);
}
根据wiki上的描述,需要把16进制的颜色转换为RGB才能正确的设置样式,这里需要写一个工具类。
/**
* 将16进制的颜色转换为RGB返回
* @param hex - 16进制的颜色
* @returns {number, number, number} 16进制的颜色的RGB表示
*/
export const hexToRgb = (hex: string) => {
const matches = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
return {
r: parseInt(matches[1], 16),
g: parseInt(matches[2], 16),
b: parseInt(matches[3], 16)
}
}
3、实现简易版chalk
import supportTrueColor from "./supportTrueColor.js";
import {EscapeSequence1, EscapeSequence2, Styles} from "./ansi.js";
import {ColorMapping, Colors, isColorName} from "./color.js";
import {hexToRgb} from "./utils.js";
type colorType = "foreground" | "background"
export class Chalk {
private prefix: string
private suffix: string
constructor() {
if (!supportTrueColor()) {
throw new Error("Not Supported TrueColor!")
}
this.prefix = ''
this.suffix = ''
}
private escapeSequence(key: Styles, ...args: any[]) {
this.prefix += EscapeSequence1(key, ...args)
this.suffix += EscapeSequence2(key)
}
private rgb(t: colorType, r: number, g: number, b: number): this;
private rgb(t: colorType, r: number, g: number, b: number, ...args: string[]): string;
private rgb(t: colorType, r: number, g: number, b: number, ...args: string[]): string | this {
this.escapeSequence(t as Styles, r, g, b);
if (args.length > 0) {
return this.build(...args);
}
return this;
}
build(...args: string[]) {
const text = this.prefix + args.join(' ') + this.suffix;
this.prefix = ''
this.suffix = ''
return text;
}
// style
style(s: Styles): this;
style(s: Styles, ...args: string[]): string;
style(s: Styles, ...args: string[]): string | this {
this.escapeSequence(s)
if (args.length > 0) {
return this.build(...args);
}
return this;
}
// color
color(name: Colors): this;
color(name: Colors, ...args: string[]): string;
color(name: string, ...args: string[]): string;
color(name: Colors, ...args: string[]): string | this {
const {r, g, b} = isColorName(name) ? hexToRgb(ColorMapping[name as Colors]) : hexToRgb(name);
return this.rgb("foreground", r, g, b, ...args)
}
// bgColor
bgColor(name: Colors | string): this;
bgColor(name: Colors | string, ...args: string[]): string;
bgColor(name: Colors | string, ...args: string[]): string | this {
const {r, g, b} = isColorName(name) ? hexToRgb(ColorMapping[name as Colors]) : hexToRgb(name);
return this.rgb("background", r, g, b, ...args)
}
}
export default new Chalk();
// const chalk = new Chalk();
// console.log(chalk.color("blue").bgColor("skyblue").style("bold", "hello chalk!"));
// console.log(chalk.color("red").bgColor("pink").style("bold").build("hello chalk!"));