UNPKG

@serpent/common-cli

Version:

通用的 cli 相关的函数

405 lines (351 loc) 16.8 kB
'use strict'; var _commonjsHelpers = require('./_commonjsHelpers-ed042b00.cjs'); var require$$1 = require('os'); var format = require('./format-4c37063e.cjs'); var supportColor = require('./supportColor-07ee281e.cjs'); var _helper = {}; /** * Creates an array containing the numeric code points of each Unicode * character in the string. While JavaScript uses UCS-2 internally, * this function will convert a pair of surrogate halves (each of which * UCS-2 exposes as separate characters) into a single code point, * matching UTF-16. * @see `punycode.ucs2.encode` * @see <https://mathiasbynens.be/notes/javascript-encoding> * @memberOf punycode.ucs2 * @name decode * @param {String} string The Unicode input string (UCS-2). * @returns {Array} The new array of code points. */ function ucs2decode$1(string) { var output = []; var counter = 0; var length = string.length; /* istanbul ignore next */ while (counter < length) { var value = string.charCodeAt(counter++); if (value >= 0xD800 && value <= 0xDBFF && counter < length) { // It's a high surrogate, and there is a next character. var extra = string.charCodeAt(counter++); if ((extra & 0xFC00) === 0xDC00) { // Low surrogate. output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); } else { // It's an unmatched surrogate; only append this code unit, in case the // next code unit is the high surrogate of a surrogate pair. output.push(value); counter--; } } else { output.push(value); } } return output } /** * Creates a string based on an array of numeric code points. * @see `punycode.ucs2.decode` * @memberOf punycode.ucs2 * @name encode * @param {Array} codePoints The array of numeric code points. * @returns {String} The new Unicode string (UCS-2). */ function ucs2encode(codePoints) { return String.fromCodePoint.apply(String, codePoints) } _helper.ucs2decode = ucs2decode$1; _helper.ucs2encode = ucs2encode; /** * @module libs/tty/cliTextSize * @createdAt 2016-07-17 * * @copyright Copyright (c) 2016 Zhonglei Qiu * @license Licensed under the MIT license. * * 可以参考: eastasianwidth string-width */ var stripAnsi = format.stripAnsiExports; var isWin = supportColor.isWin; var ucs2decode = _helper.ucs2decode; // Window 对不支持的字符都使用了默认的占一个位置的字符替代 // 中国的 window 用户大多是 cp936 编码 (但是最近听说 window 也将支持 Linux shell) /* eslint-disable */ var sizeRangesMap = {"0":[[0,7],[11,31],[768,879],[1155,1161],[1425,1469],[1471],[1473,1474],[1476,1477],[1479],[1552,1562],[1611,1631],[1648],[1750,1756],[1759,1764],[1767,1768],[1770,1773],[1809],[1840,1866],[1958,1968],[2027,2035],[2070,2073],[2075,2083],[2085,2087],[2089,2093],[2137,2139],[2304,2307],[2362,2364],[2366,2383],[2385,2391],[2402,2403],[2433,2435],[2492],[2494,2500],[2503,2504],[2507,2509],[2519],[2530,2531],[2561,2563],[2620],[2622,2626],[2631,2632],[2635,2637],[2641],[2672,2673],[2677],[2689,2691],[2748],[2750,2757],[2759,2761],[2763,2765],[2786,2787],[2817,2819],[2876],[2878,2884],[2887,2888],[2891,2893],[2902,2903],[2914,2915],[2946],[3006,3010],[3014,3016],[3018,3021],[3031],[3073,3075],[3134,3140],[3142,3144],[3146,3149],[3157,3158],[3170,3171],[3202,3203],[3260],[3262,3268],[3270,3272],[3274,3277],[3285,3286],[3298,3299],[3330,3331],[3390,3396],[3398,3400],[3402,3405],[3415],[3426,3427],[3458,3459],[3530],[3535,3540],[3542],[3544,3551],[3570,3571],[3633],[3636,3642],[3655,3662],[3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893],[3895],[3897],[3902,3903],[3953,3972],[3974,3975],[3981,3991],[3993,4028],[4038],[4139,4158],[4182,4185],[4190,4192],[4194,4196],[4199,4205],[4209,4212],[4226,4237],[4239],[4250,4253],[4957,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6070,6099],[6109],[6155,6157],[6313],[6432,6443],[6448,6459],[6576,6592],[6600,6601],[6679,6683],[6741,6750],[6752,6780],[6783],[6912,6916],[6964,6980],[7019,7027],[7040,7042],[7073,7082],[7142,7155],[7204,7223],[7376,7378],[7380,7400],[7405],[7410],[7616,7654],[7676,7679],[8203,8205],[8400,8432],[11503,11505],[11647],[11744,11775],[12330,12335],[12441,12442],[42607,42610],[42620,42621],[42736,42737],[43010],[43014],[43019],[43043,43047],[43136,43137],[43188,43204],[43232,43249],[43302,43309],[43335,43347],[43392,43395],[43443,43456],[43561,43574],[43587],[43596,43597],[43643],[43696],[43698,43700],[43703,43704],[43710,43711],[43713],[44003,44010],[44012,44013],[64286],[65024,65039],[65056,65062],[65279]],"2":[[4352,4447],[4515,4519],[4602,4607],[9001,9002],[11904,11929],[11931,12019],[12032,12245],[12272,12283],[12288,12329],[12336,12350],[12353,12438],[12443,12543],[12549,12589],[12593,12686],[12688,12730],[12736,12771],[12784,12830],[12832,12871],[12880,13054],[13056,19903],[19968,42124],[42128,42182],[43360,43388],[44032,55203],[55216,55238],[55243,55291],[63744,64255],[65040,65049],[65072,65106],[65108,65126],[65128,65131],[65281,65376],[65504,65510],[110592,110593],[119134,119135],[119227,119228],[127488,127490],[127504,127546],[127552,127560],[127568,127569],[131072,196605],[196608,262141]],"3":[[119136,119140],[119229,119232]]}; var ambRanges = [[161],[164],[167,168],[170],[173,174],[176,180],[182,186],[188,191],[198],[208],[215,216],[222,225],[230],[232,234],[236,237],[240],[242,243],[247,250],[252],[254],[257],[273],[275],[283],[294,295],[299],[305,307],[312],[319,322],[324],[328,331],[333],[338,339],[358,359],[363],[462],[464],[466],[468],[470],[472],[474],[476],[593],[609],[708],[711],[713,715],[717],[720],[728,731],[733],[735],[903],[913,929],[931,937],[945,961],[963,969],[1025],[1040,1103],[1105],[8126],[8189],[8208],[8211,8214],[8216,8217],[8220,8221],[8224,8226],[8228,8231],[8240],[8242,8243],[8245],[8251],[8254],[8308],[8319],[8321,8324],[8364],[8451],[8453],[8457],[8467],[8470],[8481,8482],[8486],[8531,8532],[8539,8542],[8544,8555],[8560,8569],[8585],[8592,8601],[8632,8633],[8658],[8660],[8679],[8704],[8706,8707],[8711,8712],[8715],[8719],[8721],[8725],[8730],[8733,8736],[8739],[8741],[8743,8748],[8750],[8756,8759],[8764,8765],[8776],[8780],[8786],[8800,8801],[8804,8807],[8810,8811],[8814,8815],[8834,8835],[8838,8839],[8853],[8857],[8869],[8895],[8978],[9312,9449],[9451,9547],[9552,9587],[9600,9615],[9618,9621],[9632,9633],[9635,9641],[9650,9651],[9654,9655],[9660,9661],[9664,9665],[9670,9672],[9675],[9678,9681],[9698,9701],[9711],[9733,9734],[9737],[9742,9743],[9748,9749],[9756],[9758],[9792],[9794],[9824,9825],[9827,9829],[9831,9834],[9836,9837],[9839],[9886,9887],[9918,9919],[9924,9933],[9935,9953],[9955],[9960,9983],[10045],[10071],[10102,10111],[11093,11097],[12872,12879],[55296,61439],[61444,63743],[65533],[127232,127242],[127248,127277],[127280,127337],[127344,127386],[917760,917999],[983040,1048573],[1048576,1114109]]; var cp936Ranges = [[0,127],[164],[167,168],[176,177],[183],[215],[224,225],[232,234],[236,237],[242,243],[247],[249,250],[252],[257],[275],[283],[299],[324],[328],[333],[363],[462],[464],[466],[468],[470],[472],[474],[476],[593],[609],[711],[713,715],[729],[913,929],[931,937],[945,961],[963,969],[1025],[1040,1103],[1105],[8208],[8211,8214],[8216,8217],[8220,8221],[8229,8230],[8240],[8242,8243],[8245],[8251],[8364],[8451],[8453],[8457],[8470],[8481],[8544,8555],[8560,8569],[8592,8595],[8598,8601],[8712],[8719],[8721],[8725],[8730],[8733,8736],[8739],[8741],[8743,8747],[8750],[8756,8759],[8765],[8776],[8780],[8786],[8800,8801],[8804,8807],[8814,8815],[8853],[8857],[8869],[8895],[8978],[9312,9321],[9332,9371],[9472,9547],[9552,9587],[9601,9615],[9619,9621],[9632,9633],[9650,9651],[9660,9661],[9670,9671],[9675],[9678,9679],[9698,9701],[9733,9734],[9737],[9792],[9794],[12288,12291],[12293,12311],[12317,12318],[12321,12329],[12353,12435],[12443,12446],[12449,12534],[12540,12542],[12549,12585],[12832,12841],[12849],[12963],[13198,13199],[13212,13214],[13217],[13252],[13262],[13265,13266],[13269],[19968,40869],[63788],[63865],[63893],[63975],[63985],[64012,64015],[64017],[64019,64020],[64024],[64031,64033],[64035,64036],[64039,64041],[65072,65073],[65075,65092],[65097,65106],[65108,65111],[65113,65126],[65128,65131],[65281,65374],[65504,65509]]; /* eslint-enable */ var sizes = Object.keys(sizeRangesMap).map(function(s) { return parseInt(s, 10) }); /** * 计算 Unicode 字符串在终端上的长度 * * @param {String} str 将会显示在终端上的字符串 * @return {Number} * * @example * cliTextSize('中国') // => 4 * cliTextSize('ab') // => 2 * * @see [tty-text-size@1.0.0]{@link https://github.com/qiu8310/tty-text-size/tree/1.0.0} * @throws {Error} 当包含 \t, \r, \v, \f, \n 这些无法计算长度的字符时 * * @since 2.0.0 * @author Zhonglei Qiu */ var cliTextSize$1 = function(str) { str = stripAnsi(str); return ucs2decode(str).reduce(function(size, cp) { return size + getCodePointCliSize(cp) }, 0) }; function getCodePointCliSize(cp) { // 特殊字符无法知道它的 size if ([9, 10, 11, 12, 13].indexOf(cp) >= 0) { // throw new Error('Code point ' + cp + ' not allowed, its size is not predictable.') return cp === 9 ? 6 : 0 // tab Mac iTerm 上占了 6 个字符 } // window 对不支持的字符都使用了默认的占一个位置的字符替代 if (isWin && !inRanges(cp, cp936Ranges)) return 1 // Linux 很多终端此值都是 1,但用户可以修改,但程序不好获取用户修改的值 // 所以取默认值 if (inRanges(cp, ambRanges)) return isWin ? 2 : 1 for (var i = 0; i < sizes.length; i++) { // console.log(cp, sizeRangesMap[sizes[i]], inRanges(cp, sizeRangesMap[sizes[i]])) if (inRanges(cp, sizeRangesMap[sizes[i]])) { // window 不会超过 2 个字符 return isWin ? Math.min(2, sizes[i]) : sizes[i] } } return 1 } function inRanges(cp, ranges) { return ranges.some(function(range) { if (range.length === 1) return cp === range[0] return cp >= range[0] && cp <= range[1] }) } var _cliTextSize = /*@__PURE__*/_commonjsHelpers.getDefaultExportFromCjs(cliTextSize$1); /** * @module libs/tty/table * @createdAt 2016-07-19 * * @copyright Copyright (c) 2016 Zhonglei Qiu * @license Licensed under the MIT license. */ var punycode = _helper; var os = require$$1; var cliTextSize = cliTextSize$1; var greAnsi = format.stripAnsiExports.gre; var encode = punycode.ucs2encode; var decode = punycode.ucs2decode; // 获取当前屏幕的宽度 var stream = process.stdout; var MIN_OVERFLOW_CELL_WIDTH = 10; var SCREEN_WIDTH = 120; /* istanbul ignore else */ if (stream.getWindowSize && !process.env.CI) SCREEN_WIDTH = stream.getWindowSize()[0]; // CI 中以 120 为准,travis 中只有 32,导致很多输出断行了,很多测试失败 /** * 将一个二维的字符串数组,转化成一个 table 格式的字符串,方便在终端上显示,主要特点有: * * 1. 精确计算字符的宽度,自动换行:很多老外写的类似的库都采用 str.length 来得到字符串在终端上的显示长度, * 但是,中文在终端上一般占用两个字符,而且不同系统上字符长度也会不一样 * 2. 自动重置颜色:当一个 cell 中有颜色时,如果不重置色值,它是会影响后面 cell * 3. 判断是否超出屏幕大小:如果超出,自动截断最宽的一行 * * @param {Array<Array<String>>} rows table 中每个 cell 中的字符串,是个二维的字符串数组 * @return {String} table 化的字符串 * * @example * * table([ * ['a', 'b', 'c'], * ['d', 'ee', 'f'] * ]) * * // ab c * // deef * * @see [tty-wrap@0.1.0]{@link https://github.com/qiu8310/tty-wrap/tree/0.1.0} * @since 2.0.0 * @author Zhonglei Qiu */ var table = function(rows) { if (!Array.isArray(rows) || !Array.isArray(rows[0]) || !rows[0].length) return '' var maxCellHeights = new Array(rows.length); // 同一行中, cell 占位最多的高度 // 初始化 each(rows, function(val, opt) { rows[opt.r][opt.c] = val = val.replace(/[\r\v\f]/g, '').replace(/\t/g, ' ').split(/\n/); maxCellHeights[opt.r] = Math.max(maxCellHeights[opt.r] || 0, val.length); }); var maxColumnWidths = getMaxWidthsOfEachColumn(rows); var sumwidth = sum(maxColumnWidths); // 处理宽度最宽的一列的宽度(不能超过屏幕) if (sumwidth > SCREEN_WIDTH) { var maxindex = maxNumberIndex(maxColumnWidths); var limit = Math.max(MIN_OVERFLOW_CELL_WIDTH, SCREEN_WIDTH - (sumwidth - maxColumnWidths[maxindex])); // 保证不要小于 0 maxColumnWidths[maxindex] = limit; each(rows, function(val, opt) { if (opt.c === maxindex) { rows[opt.r][opt.c] = val = wrapCell(val, limit); maxCellHeights[opt.r] = Math.max(maxCellHeights[opt.r], val.length); } }); } // 保证每个 cell 的宽度和高度一致 each(rows, function(val, opt) { var i; var l = val.length; // 统一高度 for (i = 0; i < maxCellHeights[opt.r] - l; i++) { val.push(''); } // 统一宽度 val.forEach(function(str, j) { val[j] = str + space(maxColumnWidths[opt.c] - cliTextSize(str)); }); // 保证 cli 上的颜色不要互相污染 rows[opt.r][opt.c] = wrapColor(val); }); // 组装 var r, c, j, row; var result = []; for (r = 0; r < rows.length; r++) { for (j = 0; j < maxCellHeights[r]; j++) { row = []; for (c = 0; c < rows[r].length; c++) { row.push(rows[r][c][j]); } result.push(row.join('')); } } return result.join(os.EOL) }; // 限制 cell 的宽度,过长就将它裁断 function wrapCell(cell, size) { var res = []; cell.forEach(function(str) { if (cliTextSize(str) > size) { res.push.apply(res, wrapStr(str, size)); } else { res.push(str); } }); return res } // 限制 string 的宽度,过长就将它裁断 function wrapStr(str, size) { var rows = []; var ansis = []; // str 中可能包含 ansi 控制字符 // 要先备份其位置 str = str.replace(greAnsi, function(ansi, i) { // 需要减去之前 ansi 的长度 var index = i; /* istanbul ignore next */ for (var j = 0; j < ansis.length; j++) index -= ansis[j][1].length; // 如果和上一个对象的 index 相同,需要合并 (因为存在添加多个 ansi 前缀的可能) var last = ansis[ansis.length - 1]; /* istanbul ignore next */ if (last && last[0] === index) { last[1] += ansi; } else { ansis.push([index, ansi]); } return '' }); var data = []; var cps = decode(str); cps.forEach(function(cp, i) { var char = encode([cp]); data.push({ char: char, size: cliTextSize(char) }); }); var i, ansi, ai; var len = 0; var fetchAnsi = function() { var t = ansis.shift(); ai = t && t[0]; ansi = t && t[1]; }; str = ''; fetchAnsi(); for (i = 0; i < data.length; i++) { if (len + data[i].size > size) { rows.push(str); str = ''; len = 0; } if (ansi && ai === i) { str += ansi; fetchAnsi(); } str += data[i].char; len += data[i].size; } /* istanbul ignore else */ if (str) rows.push(str); if (ansi) rows[rows.length - 1] += ansi; return rows } // 保证 cli 上的颜色不要互相污染 function wrapColor(cell) { var hasAnsi; var stack = ''; var res = []; cell.forEach(function(str) { var prevStack = stack; str.replace(greAnsi, function(ansi) { hasAnsi = true; stack += ansi; }); if (hasAnsi) str += '\x1b[0m'; res.push(prevStack + str); }); return res } function each(data, fn) { var r, c; var rowSize = data.length; var colSize = data[0].length; for (r = 0; r < rowSize; r++) { for (c = 0; c < colSize; c++) { fn(data[r][c], {r: r, c: c}); } } } // 获取每列中宽度最大的值 function getMaxWidthsOfEachColumn(rows) { var result = new Array(rows[0].length); each(rows, function(cell, opt) { result[opt.c] = Math.max(result[opt.c] || 0, getCellWidth(cell)); }); return result } function getCellWidth(cell) { return Math.max.apply(Math, cell.map(cliTextSize)) } function space(n) { if (n < 1) return '' return new Array(n + 1).join(' ') } function sum(arr) { return arr.reduce(function(s, i) { return s + i }, 0) } function maxNumberIndex(arr) { var i; var max = 0; var res = -1; for (i = 0; i < arr.length; i++) { if (arr[i] >= max) { // 优先取最后的最大值 max = arr[i]; res = i; } } return res } var table$1 = /*@__PURE__*/_commonjsHelpers.getDefaultExportFromCjs(table); exports._cliTextSize = _cliTextSize; exports.table = table; exports.table$1 = table$1;