@serpent/common-cli
Version:
通用的 cli 相关的函数
685 lines (611 loc) • 22.3 kB
JavaScript
import { g as getDefaultExportFromCjs } from './_commonjsHelpers-7d1333e8.mjs';
import { s as supportColor_1 } from './supportColor-2e661db8.mjs';
import require$$0 from 'util';
var stripAnsi = {exports: {}};
/**
* @module libs/tty/stripAnsi
* @createdAt 2016-07-17
*
* @copyright Copyright (c) 2016 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
function ansiRegex() {
var pattern = [
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
].join('|');
return new RegExp(pattern, 'g')
}
var gre = ansiRegex();
/**
* 去掉字符串中的 ansi escape 字符
*
* @param {*} str 需要去除 ansi escape code 的字符串,如果 str 不是字符串,则返回它本身
* @return {String} 去除 ansi escape code 之后的字符串
*
* @see [ansi escape code from wiki]{@link https://en.wikipedia.org/wiki/ANSI_escape_code}
* @see [strip-ansi@3.0.1]{@link https://github.com/chalk/strip-ansi/tree/v3.0.1}
*
* @example
* stripAnsi('\u001b[31ma\u001b[0m') // 'a'
*
* @author Zhonglei Qiu
* @since 2.0.0
*/
stripAnsi.exports = function(str) {
return typeof str === 'string' ? str.replace(gre, '') : str
};
/**
* 匹配 ansi escape code 的正则表达式
*
* 注意,此正则带有 global modifier
*
* **说明:**
*
* - `re` 或以 `re` 开头(如 reAnsi)表示__不__带 global modifier 的正则表达式
* - `gre` 或以 `gre` 开头(如 greAnsi) 表示带 global modifier 的正则表达式
*
* @type {RegExp}
* @see [ansi-regexp@2.0.0]{@link https://github.com/chalk/ansi-regex/tree/2.0.0}
* @author Zhonglei Qiu
* @since 2.0.0
*/
stripAnsi.exports.gre = gre;
var stripAnsiExports = stripAnsi.exports;
var _stripAnsi = /*@__PURE__*/getDefaultExportFromCjs(stripAnsiExports);
var clog$1 = {exports: {}};
/**
* @module libs/lang/toString
* @createdAt 2016-06-30
*
* @copyright Copyright (c) 2016 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
/**
* 获取 any 对象的原生的 toString 的结果
*
* @param {*} any 任何的 JS 类型
* @return {String}
*
* @author Zhonglei Qiu
* @since 2.0.0
*/
var toString$1 = function(any) {
return Object.prototype.toString.call(any)
};
/**
* @module libs/lang/isPlainObject
* @createdAt 2016-06-30
*
* @copyright Copyright (c) 2016 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
var toString = toString$1;
/**
* 判断 any 是不是一个原生的 Object,一般也叫做 PlainObject
*
* @param {*} any 任何的 JS 类型
* @return Boolean
*
* @author Zhonglei Qiu
* @since 2.0.0
*/
var isPlainObject$1 = function(any) {
return toString(any) === '[object Object]'
};
/**
* @module libs/sys/extendFormat
* @createdAt 2016-07-14
*
* @copyright Copyright (c) 2016 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
var util = require$$0;
var isPlainObject = isPlainObject$1;
// 之前还不想考虑 %% 的情况,因为它不需要处理
// 后来发现,如果不处理 %%,那么处理 %%c 情况
// 时就会出错,所以还必须在正则里处理 %% 转义
var baseMatchers = [
{match: /%%/, order: 0, expectArgNum: 0, handle: function() { return '%%' }},
{match: /%s/},
{match: /%d/},
{match: /%j/}
];
var reIllegalMatch = /(^|[^\\])\((?!\?[:=!])/;
/**
* 扩展系统的 util.format 函数,使其支持其它格式
*
* 默认的 util.format 只支持 %s %d %j %% 四个参数
*
* @param {RegExp|Object|Array<Object>} regexp 可以是下面三种形式:
* - RegExp: 一个不包含捕获数组(正则中没有括号 或者是 "(?: ... )" 这种形式)的正则表达式(正则如果有 modifiers,会忽略掉)
* - Object: Object 需要包含 match: RegExp 和 handle: Function,另外还可以配置 order: 优先级,expectArgNum: 处理的参数的个数
* - Array: 表示有多个上面的 Object
*
* @param {Function} [fn] 当第一个参数是 RegExp 时,此参数才有意义,表示 handle 函数
* @return {Function} 新的 format 函数
*
* @example
* // 1. 匹配 "%" + 数字 + "c" 形式的结构
* var format = extendFormat(/%\dc/, function (values, format) {
* return values[0].repeat(parseInt(values[0], 10)) + ' c'
* })
*
* format('foo %2c', 'bar') // => 'foo barbar c'
*
* @example
* // 2. 如果要继承多个,不要一个一个去调用,使用数组来批量注入
* var format = extendFormat([
* {
* match: /%\dd/,
* expectArgNum: 2, // 期望的参数个数(默认为1),如果参数不足,则 handle 不会被调用
* order: 100, // 用来决定调用 format 的顺序,默认为100,越小优先级越高
*
* handle(val1, val2, format) {},
*
* // 下面六个 hook 需要返回字符串,或 undefined
* onStart(parsedTemplateArray) {}, // 返回的字段会出现在输出的最前面
* onEnd(parsedTemplateArray) {}, // 返回的字段会出现在输出的最后面
*
* onGroupStart(parsedTemplate, template) {}, // 在单个模板替换前调用
* onGroupEnd(parsedTemplate, replacedTemplate) {}, // 在单个模板替换后调用
*
* onFormatStart(templateArg, parsedTemplate) {}, // 在模板的每个字段替换前调用
* onFormatEnd(templateArg, parsedTemplate) {} // 在模板的每个字段替换后调用
* },
*
* {
* match: /%\ds/,
* handle: ...
* }
* ])
*
* @author Zhonglei Qiu
* @since 2.0.0
*/
var extendFormat = function(regexp, fn) {
var matchers = regexp;
if (isPlainObject(regexp)) {
matchers = [regexp];
} else if (!Array.isArray(regexp)) {
matchers = [{ match: regexp, handle: fn }];
}
matchers = baseMatchers
.concat(matchers)
.map(matcherMap)
.sort(matcherSort);
regexp = buildRegExp(matchers.map(function(it) { return it.match }));
return function format() {
var i, group, arg, argIndex;
var groups = [];
for (i = 0; i < arguments.length; i++) {
if (!group || group.argNum === group.expectArgNum) {
if (group) {
groups.push(group);
}
argIndex = 0;
group = parseToGroup(matchers, regexp, arguments[i]);
} else {
group.argNum++;
arg = group.args[argIndex];
while (arg.matcher.expectArgNum === arg.values.length) {
arg = group.args[++argIndex];
}
arg.values.push(arguments[i]);
}
}
if (group && groups.indexOf(group) < 0) groups.push(group);
return hook(matchers, 'onStart', -1, [groups])
+ util.format.apply(util, groups.reduce(function(newArgs, group, i) {
newArgs.push.apply(newArgs, compileToNewArgs(matchers, regexp, group));
return newArgs
}, []))
+ hook(matchers, 'onEnd', 1, [groups]) + '' // make it to string
}
};
function parseToGroup(matchers, regexp, template) {
var group = { template: template, args: [], expectArgNum: 0, argNum: 0 };
if (typeof template === 'string') {
template.replace(regexp, function(format) {
var i, matcher;
for (i = 0; i < matchers.length; i++) {
if (format === arguments[i + 1]) {
matcher = matchers[i];
break
}
}
group.args.push({
matcher: matchers[i],
format: format,
values: []
});
group.expectArgNum += matcher.expectArgNum;
});
}
return group
}
function compileToNewArgs(matchers, regexp, group) {
var result = [];
var template = group.template;
var args = group.args;
var prefix, suffix;
if (typeof template === 'string') {
prefix = hook(matchers, 'onGroupStart', -1, [group, template]);
template = template.replace(regexp, function(raw) {
// 既然进来了,就一定有 args.length > 0
if (args[0].matcher.expectArgNum > group.argNum) { // 参数不足,给原生处理
// 参数不足时一定是 args.length === 1
result.push.apply(result, args[0].values);
return raw
} else {
var arg = args.shift();
var matcher = arg.matcher;
var format = arg.format;
var values = arg.values;
group.argNum -= values.length;
if (matcher.handle) {
// 注意: 用户 handle 返回的数据不一定都是字符串,但这里的 template 一定是字符串
// 所以强制转化成字符串
raw = String(matcher.handle.apply(matcher, values.concat(format)));
} else {
// 没有 handle 则表示是原生支持的,给原生处理
result.push.apply(result, values);
}
return hook(matchers, 'onFormatStart', -1, [arg, group])
+ raw
+ hook(matchers, 'onFormatEnd', 1, [arg, group])
}
});
suffix = hook(matchers, 'onGroupEnd', 1, [group, template]);
template = prefix + template + suffix;
}
result.unshift(template);
return result
}
function hook(matchers, fn, order, args) {
var l = matchers.length;
var i = l;
var result = [];
if (order > 0) {
while (i--) {
_add(result, _callFn(matchers[i][fn], matchers[i], args));
}
} else {
for (i = 0; i < l; i++) {
_add(result, _callFn(matchers[i][fn], matchers[i], args));
}
}
return result.join('')
}
function _add(arr, item) {
if (item !== undefined) arr.push(String(item));
}
function _callFn(fn, binder, args) {
if (typeof fn === 'function') return fn.apply(binder, args)
}
// 根据需要 extend 的参数重新生成 正则
function buildRegExp(regexps) {
return new RegExp(regexps.map(stringifyRegExp).join('|'), 'g')
}
// 将用户提供的正则转化成字符串,并去掉首尾以提供给新的正则使用
function stringifyRegExp(regexp) {
return '(' + regexp.toString().replace(/^\/|\/\w*$/g, '') + ')'
}
function matcherSort(a, b) {
return a.order - b.order
}
function matcherMap(matcher) {
if (reIllegalMatch.test(matcher.match)) throw new Error('正则不能使用捕获性数组')
if (!('order' in matcher)) matcher.order = 100;
if (!('expectArgNum' in matcher)) matcher.expectArgNum = 1;
return matcher
}
/**
* @module libs/sys/clog
* @createdAt 2016-07-15
*
* @copyright Copyright (c) 2016 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
(function (module, exports) {
var hasAnsiColorRegExp = stripAnsiExports.gre;
var supportColor = supportColor_1();
var bgRE = /^(?:bg|background)(\w+)$/; // match: bgRed, bgYellow ...
var resetModifRE = /^(?:reset|r)(\w+)$/; // match: resetDim, resetItalic ...
var hexRE = /^[0-9a-f]{3}(?:[0-9a-f]{3})?$/; // match: ff00ff, f0f ...
var PREFIX = '\x1b[';
var SUFFIX = 'm';
var RESET = PREFIX + '0' + SUFFIX;
var MODIFIERS = { // reset
bold: 1, // 21 // 21 isn't widely supported and 22 does the same thing
faint: 2, // 22
gray: 2, // 22
dim: 2, // 22
italic: 3, // 23
underline: 4, // 24
reverse: 7 // 27
};
// 30-37 color; 40-47 background
// 90-97 high intensity color; 100-107 high intensity background
//
// 注意: windows 下 high intensity black + dim 会导致文字不显示 ( high intensity black 和 dim 都是灰色 )
// SEE: https://github.com/chalk/chalk/issues/58
// 其它
// 39 => default color;
// 49 => default background;
// 0 => reset
var NAMES = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
var ansiStack = [];
var reset = function() { ansiStack.length = 0; return RESET };
var colorMatcher = {
match: /%c/,
handle: function(color) {
/* istanbul ignore if */
if (!supportColor) return ''
var ansi = parseColor(color);
var i = ansi.indexOf(RESET);
if (i >= 0) {
reset();
ansi = ansi.substr(i);
}
ansiStack.push(ansi);
return ansi
},
onEnd: function() {
/* istanbul ignore if */
if (!supportColor) return ''
return clog.autoResetAtEnd ? reset() : ''
},
onGroupEnd: function() {
/* istanbul ignore if */
if (!supportColor) return ''
return clog.autoResetAtGroupEnd ? reset() : ''
},
onFormatEnd: function(arg) {
/* istanbul ignore if */
if (!supportColor) return ''
if (this !== arg.matcher) {
// format 的 value 自带颜色,需要保留它自带的所有样式
if (arg.values.some(hasAnsi)) {
return RESET + ansiStack.join('')
}
}
}
};
// 1. 为了生成 jsdoc 才这样写的
// 2. 下面的那些变量需要先定义,否则 require('clog') 之后 autoResetAtEnd 这些值没没有值
exports = clog;
/**
* {@link module:libs/sys/clog} 使用的 format 函数,类似于 console.log 使用了 util.format 函数
* @type {Function}
*/
exports.format = extendFormat(colorMatcher);
/**
* 是否在 format 的最后加上 ANSI 的 RESET 控制字符
*
* @default true
* @type {Boolean}
*/
exports.autoResetAtEnd = true;
/**
* 是否在每一个模板结尾加上 ANSI 的 RESET 控制字符(默认为 true)
*
* 这里解释一下,拿语句 `console.log('Are %s ok', 'you', 'I %s %s', 'am', 'ok')` 来说,
* 句子中共有两个 group(把每一个模板和它的参数叫做一个 group):
* - group 1 => 模板:'Are %s ok', 参数:'you'
* - group 2 => 模板:'I %s %s', 参数:'am', 'ok'
*
* 所以,如果 autoResetAtGroupEnd 是 true,则在上面每个 group 之后都会加上 reset 控制串
*
* @default true
* @type {Boolean}
*/
exports.autoResetAtGroupEnd = true;
/**
* 所有支持的具名颜色值,暴露给调用方,调用方可以对其进行修改或替换
*
* 默认支持:
*
* ```
* brown: 'A52A2A',
* chocolate: 'D2691E',
* ghostwhite: 'F8F8FF',
* gold: 'FFD700',
* navy: '000080',
* olive: '808000',
* orange: 'FFA500',
* orangered: 'FF4500',
* pink: 'FFC0CB',
* purple: '800080',
* seagreen: '2E8B57',
* silver: 'C0C0C0',
* skyblue: '87CEEB',
* yellowgreen: '9ACD32'
* ```
*
* @type {Object}
*/
exports.NAMED_COLORS = {
brown: 'A52A2A',
chocolate: 'D2691E',
ghostwhite: 'F8F8FF',
gold: 'FFD700',
navy: '000080',
olive: '808000',
orange: 'FFA500',
orangered: 'FF4500',
pink: 'FFC0CB',
purple: '800080',
seagreen: '2E8B57',
silver: 'C0C0C0',
skyblue: '87CEEB',
yellowgreen: '9ACD32'
};
exports.colorMatcher = colorMatcher; // 提供给 xlog.js 使用
/**
* 输出带颜色的格式化字符串
*
* 函数参数和 util.format 类似,只是 util.format 支持 %s %d %j %% 四个格式,
* 此函数多支持一个 %c 的格式,用来指定后面文字的颜色
*
*
* 支持的颜色字符有(所有 CODE 大小写都不敏感):
*
* CODE | EXPLAIN
* -------------------------|---------------
* reset, end | 重置所有色值
* h, high, l, low | 标记颜色的明亮度,只针对 8 种基本颜色有效 {@link https://en.wikipedia.org/wiki/ANSI_escape_code#Colors}
* fg, foreground, c, color | 标记为前景色模式
* bg, background | 标记为背景色模式
* default | 恢复默认的前景色或背景色
* black, red, green, yellow, blue, magenta, cyan, white | 8 种基本颜色(不带标记默认为前景色)
* bgBlack, backgroundBlack, bgRed, backgroundReg, ... | 8 种基本的背景色
* bold, faint, gray, dim, italic, underline, reverse | 一些 modifiers
* rBold, resetBold, rFaint, resetFaint, ... | 重置 modifiers
* brown, chocolate, ghostwhite, gold, navy, ... | 默认的一些颜色,参见 {@link module:libs/sys/clog.NAMED_COLORS}
* F00, FF0000, #F00, #FF0000 ... | 十六进制颜色,支持三位,或六位,支持不带 "#"
*
* @example
* // 下面两个输出的 "you" 是红字黄底,其它文字都是默认的颜色
* clog('Are %cyou%c ok', 'fg.red.bg.yellow', 'reset')
* clog('Are %s ok', clog.format('%cyou', 'red.bgYellow'))
*
* @param {String} template 模板字符串,里面可以包含 %s, %d, %j, %c, %% 这些特殊格式
* @param {...*} [arg] 给模板用的参数
* @param {String} [template2] 第二个模板,后面可以继续接 ...arg, template, ...arg, ...
* @return {String} 格式化后的字符串
*
* @method
* @author Zhonglei Qiu
* @since 2.0.0
*/
module.exports = clog;
function clog() {
console.log(exports.format.apply(null, arguments));
}
function hasAnsi(str) {
return typeof str === 'string' && hasAnsiColorRegExp.test(str)
}
// c. 或者 color. 表示设置前景色
// b. bg. 或者 background. 表示设置背景色
// h. l. 或者 high. low. 可以设置成使用 high intensity 相关的颜色
// 每次切换 color 或者 background 都会自动将 high 设置成 false (high intensity color 兼容性不好)
// 而 MODIFIERS 可以随便加,不加前缀时默认使用 color.
function parseColor(color) {
color = String(color);
var bg = false;
var high = false;
var getNamedColorValue = function(key, forceBG) {
return (high ? 60 : 0) + (bg || forceBG ? 40 : 30) + NAMES.indexOf(key)
};
/* eslint-disable no-multi-spaces, brace-style */
return color
.split(/[{}#.,:;"'\s]+/)
.map(function(raw) {
var k = raw.toLowerCase();
// 空字符串
if (!k) return
// 重置
else if (k === 'reset' || k === 'end') return 0
// 修改状态
else if (k === 'h' || k === 'high') { high = true; }
else if (k === 'l' || k === 'low') { high = false; }
else if (k === 'fg' || k === 'foreground' || k === 'c' || k === 'color') { high = false; bg = false; }
else if (k === 'bg' || k === 'background') { high = false; bg = true; }
// 修改颜色
else if (k in MODIFIERS) return MODIFIERS[k]
else if (k === 'default') return bg ? 49 : 39
else if (NAMES.indexOf(k) >= 0) return getNamedColorValue(k)
else if (resetModifRE.test(k) && RegExp.$1 in MODIFIERS) return 20 + MODIFIERS[RegExp.$1] // reset modifier 兼容性不好,少用
else if (bgRE.test(k) && NAMES.indexOf(RegExp.$1) >= 0) return getNamedColorValue(RegExp.$1, true)
// hex 颜色
else if (k in clog.NAMED_COLORS) return getHexColor(clog.NAMED_COLORS[k], bg)
else if (hexRE.test(k)) return getHexColor(k, bg)
// 其它
else return raw // 用户可以自己直接写 ASCII 编码
})
.map(function(n) {
return n == null || n === '' ? '' : PREFIX + n + SUFFIX
})
.join('')
/* eslint-enable no-multi-spaces, brace-style */
}
function getHexColor(hex, bg) {
return (bg ? '48;5;' : '38;5;') + hexToRGB5(hex)
}
function hexToRGB5(hex) {
var rgb, gap;
gap = hex.length === 3 ? 1 : 2;
rgb = [
hex.substring(0, gap),
hex.substring(gap, gap * 2),
hex.substring(gap * 2, gap * 3)
].map(mapHexToInt5);
return 16 + rgb[0] * 36 + rgb[1] * 6 + rgb[2]
}
function mapHexToInt5(hex) {
return hexToInt5(hex.length === 1 ? hex + hex : hex)
}
function hexToInt5(hex) {
return Math.round((parseInt(hex, 16) || 0) * 5 / 255)
}
} (clog$1, clog$1.exports));
var clogExports = clog$1.exports;
var clog = /*@__PURE__*/getDefaultExportFromCjs(clogExports);
/**
* 输出带颜色的格式化字符串
*
* 函数参数和 util.format 类似,只是 util.format 支持 %s %d %j %% 四个格式,
* 此函数多支持一个 %c 的格式,用来指定后面文字的颜色
*
*
* 支持的颜色字符有(所有 CODE 大小写都不敏感):
*
* CODE | EXPLAIN
* -------------------------|---------------
* reset, end | 重置所有色值
* h, high, l, low | 标记颜色的明亮度,只针对 8 种基本颜色有效 {@link https://en.wikipedia.org/wiki/ANSI_escape_code#Colors ANSI Colors}
* fg, foreground, c, color | 标记为前景色模式
* bg, background | 标记为背景色模式
* default | 恢复默认的前景色或背景色
* black, red, green, yellow, blue, magenta, cyan, white | 8 种基本颜色(不带标记默认为前景色)
* bgBlack, backgroundBlack, bgRed, backgroundReg, ... | 8 种基本的背景色
* bold, faint, gray, dim, italic, underline, reverse | 一些 modifiers
* rBold, resetBold, rFaint, resetFaint, ... | 重置 modifiers
* brown, chocolate, ghostwhite, gold, navy, ... | 默认的一些颜色,参见 NAMED_COLORS
* F00, FF0000, #F00, #FF0000 ... | 十六进制颜色,支持三位,或六位,支持不带 "#"
*
* **NAMED_COLORS:**
* - brown: 'A52A2A',
* - chocolate: 'D2691E',
* - ghostwhite: 'F8F8FF',
* - gold: 'FFD700',
* - navy: '000080',
* - olive: '808000',
* - orange: 'FFA500',
* - orangered: 'FF4500',
* - pink: 'FFC0CB',
* - purple: '800080',
* - seagreen: '2E8B57',
* - silver: 'C0C0C0',
* - skyblue: '87CEEB',
* - yellowgreen: '9ACD32'
*
* @example
* // 下面两个输出的 "you" 是红字黄底,其它文字都是默认的颜色
* format('Are %cyou%c ok', 'fg.red.bg.yellow', 'reset')
* format('Are %s ok', format('%cyou', 'red.bgYellow'))
*
* @param args
* - 第一个参数:模板字符串,里面可以包含 %s, %d, %j, %c, %% 这些特殊格式
* - 第二个参数:给模板用的参数(可以有多个)
* - 后续参数:可以继续是 “一个模板” + “N个参数” 的组合
*/
function format() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return clog.format.apply(clog, args);
}
export { _stripAnsi as _, clog as a, clogExports as c, format as f, isPlainObject$1 as i, stripAnsiExports as s };