近期忙了一阵less的二次开发的工作,期间遇到了不少须要向控制台输出彩色文字的需求。翻了下曾经同事的代码,发现要么自己拼转义字符串,要么使用一些不太好用的第三方库。总之都不是非常合自己的口味。
依照自己的口味,一个好的第三方库应该满足例如以下需求: 要支持丰富的颜色设置,同一时候设置颜色又不能太累赘,并且要支持console.log的通配符表示方法以降低拼字符串的工作。cli-color和colors的语法类似,都是採用方法来设定字符串颜色:
// colors
console.log("this is an error".error);
// cli-color
console.log(clc.red(‘red‘) + ‘ plain ‘ + clc.blue(‘blue‘));
假设字符串中的颜色比較多,并且字符串中还包括动态数据,那么就须要大量的拼字符串的工作,丑陋且easy出错。因此这样的坑爹的方案直接忽略。
既然这些现成的库不好用。干脆就自己写一个。作为一个前端平时自己接触的最多的是html,受html语法的启示。打算採用标签的形式来设置字符颜色,而不是採用方法的形式。比方要输出红绿两种颜色的文本,能够採用例如以下方式:Foo.log(‘<red>red
。这样的方式有两个长处: 第一,便于表现丰富的颜色,尤其是颜色嵌套的情况。假设採用cli-color那种方式来表现多个颜色的嵌套,预计拼字符串会让你想吐;第二。省去了记忆方法名和拼字符串。
color <green>green color</green></red>‘)
那么这样的设计是否easy实现呢? 在回答这个问题之前我们先简单说一下实现彩色输出的原理。向控制台输出彩色文字主要利用了ansi 中的转义字符(ANSI转义字符表)。
众多的转义字符中有一部分是设置控制台的渲染方式的,当中输出控制採用例如以下语法来声明:\x1b[nm
。
x1b的值是27,在ASC码表中表示转义字符。后面的[nm是模式设置,[和m是常量。n为变量。通过设置N的值能够实现不同的输出设置,以下为经常使用的N值
- 0 開始以暗色模式显示文本, 文字颜色为用户设置的控制台默认颜色。一般为白色
- 1 開始以亮色模式显示文本。 文字颜色为用户设置的控制台默认颜色。一般为白色
- 30 ~ 27 分别以black、red、green、 yellow、blue、pink、cyan、white颜色来显示文本
须要特别注意的一点:过这些输出设置不仅对本次输出起效,并且将一直起效。直至遇到新的设置或控制台退出! 所以在使用的时候一定要记得重置颜色设置。免得影响后面的控制台输出。我们通过以下的demo来检验下这些转义字符的功能。注意划红线的语句部分。尽管这条语句中没有对输出进行不论什么设置,但由于上一条命令中设置了控制台颜色,所以这次输出依旧採用的上次的设置。
叙述了这么多。最终能够回答上面的那个问题了:那么这样的设计是否easy实现呢?答案是:非常easy。我们只须要用这则处理3类字符串就能够了: 转义字符、颜色開始tag。颜色结束tag。
处理策略也非常easy:
- 遇到由斜线開始的转义字符, 直接返回斜线后面的字符
- 遇到開始Tag, 查看是否为支持的颜色,若不支持。不做处理原样返回。若支持。返回tag相应到 颜色转义字符,并将该颜色转义字符压栈。
- 遇到结束Tag。 查看是否是支持的颜色。若不支持。不做处理原样返回;若支持。弹栈并返回栈顶颜色相应的转义字符。若栈为空,则设置为系统默认颜色。
- 其它情况一律不做处理,原样返回(这一步主要是预防自己没有预料的一些匹配出现,这个样例中应该用不到,为了保险起见还是留着吧)。
- 为了防止用户标签没有闭合而影响其它控制台输出,在最后预防行的设置颜色为默认颜色。
这部分的逻辑已经封并公布到了npm的rich-console模块,以下为详细的实现代码和demo执行结果截图。顺便说一下ANSI中支持的转义内容还非常多比方设置背景色、设置加粗、下划线等,但这些支持的并不好,未能动物所下非常多都不支持,再加上这些功能更用的比較少。因此这些功能被有意忽略了。
/**
* 获得带颜色转义字符的控制台输出模板.
* @param {String}tmpl 包括标签的模板字符串
* @param {boolean}isBright 是否高亮,default false
* @return {String}
* @public
*/
function getRichTmpl(tmpl, isBright){
if(typeof tmpl == ‘object‘){ return tmpl; }
var fontStyle = isBright == true ? ‘\u001b[1m‘ : ‘‘;
var ESCAPES = {
black : (fontStyle + ‘\u001b[30m‘),
red : (fontStyle + ‘\u001b[31m‘),
green : (fontStyle + ‘\u001b[32m‘),
yellow : (fontStyle + ‘\u001b[33m‘),
orange : (fontStyle + ‘\u001b[33m‘),
blue : (fontStyle + ‘\u001b[34m‘),
pink : (fontStyle + ‘\u001b[35m‘),
cyan : (fontStyle + ‘\u001b[36m‘),
white : (fontStyle + ‘\u001b[37m‘),
noColor: ‘\u001b[0m‘
}
var NO_COLOR = ESCAPES.noColor;
var styleStack = [];
var reg = new RegExp((
‘(\\\\.)‘ // 由\表示的转义字符
+ ‘|<(\\w+)>‘ // 样式開始标签
+ ‘|</(\\w+)>‘ // 样式结束标签
), ‘g‘);
var handleTag = function(str){
return str.replace(reg, function(m, $1, $2, $3){
// 若是转义字符之间返回\后面的字符
if ($1) { return $1.slice(1); }
// 若为不支持的颜色直接忽略,否则返回样式開始字符并将样式压栈
if ($2) {
var style = ESCAPES[$2];
if(style){
styleStack.push(style);
return style;
}else{
return m;
}
}
// 若为不支持的颜色直接忽略,否则从样式栈中弹出当前样式并返回
// 栈顶样式。若栈为空返回系统默认样式
if ($3) {
if(ESCAPES[$3]){
styleStack.pop();
var len = styleStack.length;
var topStyle = len > 0 ?
styleStack[len - 1] : null;
return (topStyle ? topStyle : NO_COLOR);
}else{
return m;
}
}
// others
return m;
}) + NO_COLOR; // 最末尾的两个重置用来防止用户标签不闭合进而污染整个控制台输出
};
return handleTag(tmpl);
}
/**
* 向控制台输出彩色文字.
* @param {String}cont
* @example
* showColorText(
* ‘<red>%s <green>%s</green>! </red>‘,
* ‘hello‘,
* ‘wold‘
* );
* @public
*/
function output(cont){
// 若用户输入的是一个object则调用系统的console输出object结构
if(typeof cont == ‘object‘){
console.log(cont);
return;
}
var moreArgs = [].slice.call(arguments, 1);
moreArgs.unshift(getRichTmpl(cont));
console.log.apply(console, moreArgs);
}
/**
* 以红色文字向控制台输出错误信息.
* @param {String|Object}cont
* @param {Object...}
* @public
*/
function outputError(cont){
if(typeof cont == ‘object‘){
console.log(cont);
}else{
var moreArgs = [].slice.call(arguments, 1);
moreArgs.unshift(‘<red>‘ + cont + ‘</red>‘);
output.apply(null, moreArgs);
}
}
module.exports = {
getRichTmpl: getRichTmpl,
error: outputError,
log: output
}