UNPKG

@serpent/common-cli

Version:

通用的 cli 相关的函数

692 lines (617 loc) 22.5 kB
'use strict'; var _commonjsHelpers = require('./_commonjsHelpers-ed042b00.cjs'); var supportColor = require('./supportColor-07ee281e.cjs'); var require$$0 = require('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__*/_commonjsHelpers.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$1 = 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$1) 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$1) return '' return clog.autoResetAtEnd ? reset() : '' }, onGroupEnd: function() { /* istanbul ignore if */ if (!supportColor$1) return '' return clog.autoResetAtGroupEnd ? reset() : '' }, onFormatEnd: function(arg) { /* istanbul ignore if */ if (!supportColor$1) 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__*/_commonjsHelpers.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); } exports._stripAnsi = _stripAnsi; exports.clog = clog; exports.clogExports = clogExports; exports.format = format; exports.isPlainObject = isPlainObject$1; exports.stripAnsiExports = stripAnsiExports;