utils.mjs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import * as tty from 'node:tty';
  2. const {
  3. env = {},
  4. argv = [],
  5. platform = ""
  6. } = typeof process === "undefined" ? {} : process;
  7. const isDisabled = "NO_COLOR" in env || argv.includes("--no-color");
  8. const isForced = "FORCE_COLOR" in env || argv.includes("--color");
  9. const isWindows = platform === "win32";
  10. const isDumbTerminal = env.TERM === "dumb";
  11. const isCompatibleTerminal = tty && tty.isatty && tty.isatty(1) && env.TERM && !isDumbTerminal;
  12. const isCI = "CI" in env && ("GITHUB_ACTIONS" in env || "GITLAB_CI" in env || "CIRCLECI" in env);
  13. const isColorSupported = !isDisabled && (isForced || isWindows && !isDumbTerminal || isCompatibleTerminal || isCI);
  14. function replaceClose(index, string, close, replace, head = string.slice(0, Math.max(0, index)) + replace, tail = string.slice(Math.max(0, index + close.length)), next = tail.indexOf(close)) {
  15. return head + (next < 0 ? tail : replaceClose(next, tail, close, replace));
  16. }
  17. function clearBleed(index, string, open, close, replace) {
  18. return index < 0 ? open + string + close : open + replaceClose(index, string, close, replace) + close;
  19. }
  20. function filterEmpty(open, close, replace = open, at = open.length + 1) {
  21. return (string) => string || !(string === "" || string === void 0) ? clearBleed(
  22. ("" + string).indexOf(close, at),
  23. string,
  24. open,
  25. close,
  26. replace
  27. ) : "";
  28. }
  29. function init(open, close, replace) {
  30. return filterEmpty(`\x1B[${open}m`, `\x1B[${close}m`, replace);
  31. }
  32. const colorDefs = {
  33. reset: init(0, 0),
  34. bold: init(1, 22, "\x1B[22m\x1B[1m"),
  35. dim: init(2, 22, "\x1B[22m\x1B[2m"),
  36. italic: init(3, 23),
  37. underline: init(4, 24),
  38. inverse: init(7, 27),
  39. hidden: init(8, 28),
  40. strikethrough: init(9, 29),
  41. black: init(30, 39),
  42. red: init(31, 39),
  43. green: init(32, 39),
  44. yellow: init(33, 39),
  45. blue: init(34, 39),
  46. magenta: init(35, 39),
  47. cyan: init(36, 39),
  48. white: init(37, 39),
  49. gray: init(90, 39),
  50. bgBlack: init(40, 49),
  51. bgRed: init(41, 49),
  52. bgGreen: init(42, 49),
  53. bgYellow: init(43, 49),
  54. bgBlue: init(44, 49),
  55. bgMagenta: init(45, 49),
  56. bgCyan: init(46, 49),
  57. bgWhite: init(47, 49),
  58. blackBright: init(90, 39),
  59. redBright: init(91, 39),
  60. greenBright: init(92, 39),
  61. yellowBright: init(93, 39),
  62. blueBright: init(94, 39),
  63. magentaBright: init(95, 39),
  64. cyanBright: init(96, 39),
  65. whiteBright: init(97, 39),
  66. bgBlackBright: init(100, 49),
  67. bgRedBright: init(101, 49),
  68. bgGreenBright: init(102, 49),
  69. bgYellowBright: init(103, 49),
  70. bgBlueBright: init(104, 49),
  71. bgMagentaBright: init(105, 49),
  72. bgCyanBright: init(106, 49),
  73. bgWhiteBright: init(107, 49)
  74. };
  75. function createColors(useColor = isColorSupported) {
  76. return useColor ? colorDefs : Object.fromEntries(Object.keys(colorDefs).map((key) => [key, String]));
  77. }
  78. const colors = createColors();
  79. function getColor(color, fallback = "reset") {
  80. return colors[color] || colors[fallback];
  81. }
  82. function colorize(color, text) {
  83. return getColor(color)(text);
  84. }
  85. const ansiRegex = [
  86. "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
  87. "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
  88. ].join("|");
  89. function stripAnsi(text) {
  90. return text.replace(new RegExp(ansiRegex, "g"), "");
  91. }
  92. function centerAlign(str, len, space = " ") {
  93. const free = len - str.length;
  94. if (free <= 0) {
  95. return str;
  96. }
  97. const freeLeft = Math.floor(free / 2);
  98. let _str = "";
  99. for (let i = 0; i < len; i++) {
  100. _str += i < freeLeft || i >= freeLeft + str.length ? space : str[i - freeLeft];
  101. }
  102. return _str;
  103. }
  104. function rightAlign(str, len, space = " ") {
  105. const free = len - str.length;
  106. if (free <= 0) {
  107. return str;
  108. }
  109. let _str = "";
  110. for (let i = 0; i < len; i++) {
  111. _str += i < free ? space : str[i - free];
  112. }
  113. return _str;
  114. }
  115. function leftAlign(str, len, space = " ") {
  116. let _str = "";
  117. for (let i = 0; i < len; i++) {
  118. _str += i < str.length ? str[i] : space;
  119. }
  120. return _str;
  121. }
  122. function align(alignment, str, len, space = " ") {
  123. switch (alignment) {
  124. case "left": {
  125. return leftAlign(str, len, space);
  126. }
  127. case "right": {
  128. return rightAlign(str, len, space);
  129. }
  130. case "center": {
  131. return centerAlign(str, len, space);
  132. }
  133. default: {
  134. return str;
  135. }
  136. }
  137. }
  138. const boxStylePresets = {
  139. solid: {
  140. tl: "\u250C",
  141. tr: "\u2510",
  142. bl: "\u2514",
  143. br: "\u2518",
  144. h: "\u2500",
  145. v: "\u2502"
  146. },
  147. double: {
  148. tl: "\u2554",
  149. tr: "\u2557",
  150. bl: "\u255A",
  151. br: "\u255D",
  152. h: "\u2550",
  153. v: "\u2551"
  154. },
  155. doubleSingle: {
  156. tl: "\u2553",
  157. tr: "\u2556",
  158. bl: "\u2559",
  159. br: "\u255C",
  160. h: "\u2500",
  161. v: "\u2551"
  162. },
  163. doubleSingleRounded: {
  164. tl: "\u256D",
  165. tr: "\u256E",
  166. bl: "\u2570",
  167. br: "\u256F",
  168. h: "\u2500",
  169. v: "\u2551"
  170. },
  171. singleThick: {
  172. tl: "\u250F",
  173. tr: "\u2513",
  174. bl: "\u2517",
  175. br: "\u251B",
  176. h: "\u2501",
  177. v: "\u2503"
  178. },
  179. singleDouble: {
  180. tl: "\u2552",
  181. tr: "\u2555",
  182. bl: "\u2558",
  183. br: "\u255B",
  184. h: "\u2550",
  185. v: "\u2502"
  186. },
  187. singleDoubleRounded: {
  188. tl: "\u256D",
  189. tr: "\u256E",
  190. bl: "\u2570",
  191. br: "\u256F",
  192. h: "\u2550",
  193. v: "\u2502"
  194. },
  195. rounded: {
  196. tl: "\u256D",
  197. tr: "\u256E",
  198. bl: "\u2570",
  199. br: "\u256F",
  200. h: "\u2500",
  201. v: "\u2502"
  202. }
  203. };
  204. const defaultStyle = {
  205. borderColor: "white",
  206. borderStyle: "rounded",
  207. valign: "center",
  208. padding: 2,
  209. marginLeft: 1,
  210. marginTop: 1,
  211. marginBottom: 1
  212. };
  213. function box(text, _opts = {}) {
  214. const opts = {
  215. ..._opts,
  216. style: {
  217. ...defaultStyle,
  218. ..._opts.style
  219. }
  220. };
  221. const textLines = text.split("\n");
  222. const boxLines = [];
  223. const _color = getColor(opts.style.borderColor);
  224. const borderStyle = {
  225. ...typeof opts.style.borderStyle === "string" ? boxStylePresets[opts.style.borderStyle] || boxStylePresets.solid : opts.style.borderStyle
  226. };
  227. if (_color) {
  228. for (const key in borderStyle) {
  229. borderStyle[key] = _color(
  230. borderStyle[key]
  231. );
  232. }
  233. }
  234. const paddingOffset = opts.style.padding % 2 === 0 ? opts.style.padding : opts.style.padding + 1;
  235. const height = textLines.length + paddingOffset;
  236. const width = Math.max(...textLines.map((line) => line.length)) + paddingOffset;
  237. const widthOffset = width + paddingOffset;
  238. const leftSpace = opts.style.marginLeft > 0 ? " ".repeat(opts.style.marginLeft) : "";
  239. if (opts.style.marginTop > 0) {
  240. boxLines.push("".repeat(opts.style.marginTop));
  241. }
  242. if (opts.title) {
  243. const left = borderStyle.h.repeat(
  244. Math.floor((width - stripAnsi(opts.title).length) / 2)
  245. );
  246. const right = borderStyle.h.repeat(
  247. width - stripAnsi(opts.title).length - stripAnsi(left).length + paddingOffset
  248. );
  249. boxLines.push(
  250. `${leftSpace}${borderStyle.tl}${left}${opts.title}${right}${borderStyle.tr}`
  251. );
  252. } else {
  253. boxLines.push(
  254. `${leftSpace}${borderStyle.tl}${borderStyle.h.repeat(widthOffset)}${borderStyle.tr}`
  255. );
  256. }
  257. const valignOffset = opts.style.valign === "center" ? Math.floor((height - textLines.length) / 2) : opts.style.valign === "top" ? height - textLines.length - paddingOffset : height - textLines.length;
  258. for (let i = 0; i < height; i++) {
  259. if (i < valignOffset || i >= valignOffset + textLines.length) {
  260. boxLines.push(
  261. `${leftSpace}${borderStyle.v}${" ".repeat(widthOffset)}${borderStyle.v}`
  262. );
  263. } else {
  264. const line = textLines[i - valignOffset];
  265. const left = " ".repeat(paddingOffset);
  266. const right = " ".repeat(width - stripAnsi(line).length);
  267. boxLines.push(
  268. `${leftSpace}${borderStyle.v}${left}${line}${right}${borderStyle.v}`
  269. );
  270. }
  271. }
  272. boxLines.push(
  273. `${leftSpace}${borderStyle.bl}${borderStyle.h.repeat(widthOffset)}${borderStyle.br}`
  274. );
  275. if (opts.style.marginBottom > 0) {
  276. boxLines.push("".repeat(opts.style.marginBottom));
  277. }
  278. return boxLines.join("\n");
  279. }
  280. export { align, box, centerAlign, colorize, colors, getColor, leftAlign, rightAlign, stripAnsi };