Last active
December 11, 2022 17:15
-
-
Save taozhiyu/cdf0cdbb927b334b28fb96e6ae2b5f3a to your computer and use it in GitHub Desktop.
generate `"words".colorful` ansi formatted text
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String.prototype.colorful = function(...colors) { | |
const text = this, | |
// ======================================= Core codes below from: ansi-style ======================================= | |
colorTypes = ['color', 'bgColor'], | |
{ | |
styles, | |
colorNames, | |
modifierNames | |
} = function() { | |
const ANSI_BACKGROUND_OFFSET = 10; | |
const wrapAnsi16 = (offset = 0) => code => `\u001B[${code + offset}m`; | |
const wrapAnsi256 = (offset = 0) => code => `\u001B[${38 + offset};5;${code}m`; | |
const wrapAnsi16m = (offset = 0) => (red, green, blue) => `\u001B[${38 + offset};2;${red};${green};${blue}m`; | |
const styles = { | |
modifier: { | |
reset: [0, 0], | |
// 21 isn't widely supported and 22 does the same thing | |
bold: [1, 22], | |
dim: [2, 22], | |
italic: [3, 23], | |
underline: [4, 24], | |
overline: [53, 55], | |
inverse: [7, 27], | |
hidden: [8, 28], | |
strikethrough: [9, 29], | |
}, | |
color: { | |
black: [30, 39], | |
red: [31, 39], | |
green: [32, 39], | |
yellow: [33, 39], | |
blue: [34, 39], | |
magenta: [35, 39], | |
cyan: [36, 39], | |
white: [37, 39], | |
// Bright color | |
blackBright: [90, 39], | |
gray: [90, 39], // Alias of `blackBright` | |
grey: [90, 39], // Alias of `blackBright` | |
redBright: [91, 39], | |
greenBright: [92, 39], | |
yellowBright: [93, 39], | |
blueBright: [94, 39], | |
magentaBright: [95, 39], | |
cyanBright: [96, 39], | |
whiteBright: [97, 39], | |
}, | |
bgColor: { | |
bgBlack: [40, 49], | |
bgRed: [41, 49], | |
bgGreen: [42, 49], | |
bgYellow: [43, 49], | |
bgBlue: [44, 49], | |
bgMagenta: [45, 49], | |
bgCyan: [46, 49], | |
bgWhite: [47, 49], | |
// Bright color | |
bgBlackBright: [100, 49], | |
bgGray: [100, 49], // Alias of `bgBlackBright` | |
bgGrey: [100, 49], // Alias of `bgBlackBright` | |
bgRedBright: [101, 49], | |
bgGreenBright: [102, 49], | |
bgYellowBright: [103, 49], | |
bgBlueBright: [104, 49], | |
bgMagentaBright: [105, 49], | |
bgCyanBright: [106, 49], | |
bgWhiteBright: [107, 49], | |
}, | |
}; | |
const modifierNames = Object.keys(styles.modifier); | |
const foregroundColorNames = Object.keys(styles.color); | |
const backgroundColorNames = Object.keys(styles.bgColor); | |
const colorNames = [...foregroundColorNames, ...backgroundColorNames]; | |
function assembleStyles() { | |
const codes = new Map(); | |
for (const [groupName, group] of Object.entries(styles)) { | |
for (const [styleName, style] of Object.entries(group)) { | |
styles[styleName] = { | |
open: `\u001B[${style[0]}m`, | |
close: `\u001B[${style[1]}m`, | |
}; | |
group[styleName] = styles[styleName]; | |
codes.set(style[0], style[1]); | |
} | |
Object.defineProperty(styles, groupName, { | |
value: group, | |
enumerable: false, | |
}); | |
} | |
Object.defineProperty(styles, 'codes', { | |
value: codes, | |
enumerable: false, | |
}); | |
styles.color.close = '\u001B[39m'; | |
styles.bgColor.close = '\u001B[49m'; | |
styles.color.ansi = wrapAnsi16(); | |
styles.color.ansi256 = wrapAnsi256(); | |
styles.color.ansi16m = wrapAnsi16m(); | |
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET); | |
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET); | |
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET); | |
// From https://github.com/Qix-/color-convert/blob/3f0e0d4e92e235796ccb17f6e85c72094a651f49/conversions.js | |
Object.defineProperties(styles, { | |
rgbToAnsi256: { | |
value: (red, green, blue) => { | |
// We use the extended greyscale palette here, with the exception of | |
// black and white. normal palette only has 4 greyscale shades. | |
if (red === green && green === blue) { | |
if (red < 8) { | |
return 16; | |
} | |
if (red > 248) { | |
return 231; | |
} | |
return Math.round(((red - 8) / 247) * 24) + 232; | |
} | |
return 16 + | |
(36 * Math.round(red / 255 * 5)) + | |
(6 * Math.round(green / 255 * 5)) + | |
Math.round(blue / 255 * 5); | |
}, | |
enumerable: false, | |
}, | |
hexToRgb: { | |
value: hex => { | |
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16)); | |
if (!matches) { | |
return [0, 0, 0]; | |
} | |
let [colorString] = matches; | |
if (colorString.length === 3) { | |
colorString = [...colorString].map(character => character + character).join(''); | |
} | |
const integer = Number.parseInt(colorString, 16); | |
return [ | |
/* eslint-disable no-bitwise */ | |
(integer >> 16) & 0xFF, | |
(integer >> 8) & 0xFF, | |
integer & 0xFF, | |
/* eslint-enable no-bitwise */ | |
]; | |
}, | |
enumerable: false, | |
}, | |
hexToAnsi256: { | |
value: hex => styles.rgbToAnsi256(...styles.hexToRgb(hex)), | |
enumerable: false, | |
}, | |
ansi256ToAnsi: { | |
value: code => { | |
if (code < 8) { | |
return 30 + code; | |
} | |
if (code < 16) { | |
return 90 + (code - 8); | |
} | |
let red; | |
let green; | |
let blue; | |
if (code >= 232) { | |
red = (((code - 232) * 10) + 8) / 255; | |
green = red; | |
blue = red; | |
} else { | |
code -= 16; | |
const remainder = code % 36; | |
red = Math.floor(code / 36) / 5; | |
green = Math.floor(remainder / 6) / 5; | |
blue = (remainder % 6) / 5; | |
} | |
const value = Math.max(red, green, blue) * 2; | |
if (value === 0) { | |
return 30; | |
} | |
// eslint-disable-next-line no-bitwise | |
let result = 30 + ((Math.round(blue) << 2) | (Math.round(green) << 1) | Math.round(red)); | |
if (value === 2) { | |
result += 60; | |
} | |
return result; | |
}, | |
enumerable: false, | |
}, | |
rgbToAnsi: { | |
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)), | |
enumerable: false, | |
}, | |
hexToAnsi: { | |
value: hex => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)), | |
enumerable: false, | |
}, | |
}); | |
return styles; | |
} | |
const ansiStyles = assembleStyles(); | |
return { | |
styles: ansiStyles, | |
colorNames, | |
modifierNames | |
} | |
}() | |
// ======================================= Core codes below from: ansi-style ======================================= | |
if ( | |
colors.find( | |
(color) => Object.prototype.toString.call(color) !== '[object String]', | |
) | |
) | |
throw new Error('Invalid color') | |
let ret = text | |
colors.forEach((color, i) => { | |
if ([...colorNames, ...modifierNames].includes(color)) { | |
ret = styles[color].open + ret + styles[color].close | |
} else if (color.startsWith('#')) { | |
ret = | |
styles[colorTypes[+!!i]].ansi(styles.hexToAnsi(color)) + | |
ret + | |
styles[colorTypes[+!!i]].close | |
/* 注: 这里+!!i是处理RGB数据的骚操作 | |
* !!i将索引(0, 1, 2)都转成boolean | |
* 也就是(true, false, false, false ...) | |
* 在最前面加一个+就可以把布尔型再转回数字 | |
* 也就是(0, 1, 1, 1 ...) | |
* 这样就实现了输入RGB时只有第一个参数为字符颜色,其余均为背景色 | |
* 因此("bold","#aabbcc","#ddeeff")后面两个都会被识别成背景色, | |
* 最终 ↑这个↑ 效果与("bold","#aabbcc")相同 | |
*/ | |
} | |
}) | |
return ret | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String.prototype.colorful=function(...e){const r=["color","bgColor"],{styles:o,colorNames:t,modifierNames:n}=function(){const e=10,r=(e=0)=>r=>`\x1b[${r+e}m`,o=(e=0)=>r=>`\x1b[${38+e};5;${r}m`,t=(e=0)=>(r,o,t)=>`\x1b[${38+e};2;${r};${o};${t}m`,n={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],gray:[90,39],grey:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgGray:[100,49],bgGrey:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}},i=Object.keys(n.modifier),l=[...Object.keys(n.color),...Object.keys(n.bgColor)];return{styles:function(){const i=new Map;for(const[e,r]of Object.entries(n)){for(const[e,o]of Object.entries(r))n[e]={open:`\x1b[${o[0]}m`,close:`\x1b[${o[1]}m`},r[e]=n[e],i.set(o[0],o[1]);Object.defineProperty(n,e,{value:r,enumerable:!1})}return Object.defineProperty(n,"codes",{value:i,enumerable:!1}),n.color.close="\x1b[39m",n.bgColor.close="\x1b[49m",n.color.ansi=r(),n.color.ansi256=o(),n.color.ansi16m=t(),n.bgColor.ansi=r(e),n.bgColor.ansi256=o(e),n.bgColor.ansi16m=t(e),Object.defineProperties(n,{rgbToAnsi256:{value:(e,r,o)=>e===r&&r===o?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(o/255*5),enumerable:!1},hexToRgb:{value:e=>{const r=/[a-f\d]{6}|[a-f\d]{3}/i.exec(e.toString(16));if(!r)return[0,0,0];let[o]=r;3===o.length&&(o=[...o].map(e=>e+e).join(""));const t=Number.parseInt(o,16);return[t>>16&255,t>>8&255,255&t]},enumerable:!1},hexToAnsi256:{value:e=>n.rgbToAnsi256(...n.hexToRgb(e)),enumerable:!1},ansi256ToAnsi:{value:e=>{if(e<8)return 30+e;if(e<16)return e-8+90;let r,o,t;if(e>=232)o=r=(10*(e-232)+8)/255,t=r;else{const n=(e-=16)%36;r=Math.floor(e/36)/5,o=Math.floor(n/6)/5,t=n%6/5}const n=2*Math.max(r,o,t);if(0===n)return 30;let i=30+(Math.round(t)<<2|Math.round(o)<<1|Math.round(r));return 2===n&&(i+=60),i},enumerable:!1},rgbToAnsi:{value:(e,r,o)=>n.ansi256ToAnsi(n.rgbToAnsi256(e,r,o)),enumerable:!1},hexToAnsi:{value:e=>n.ansi256ToAnsi(n.hexToAnsi256(e)),enumerable:!1}}),n}(),colorNames:l,modifierNames:i}}();if(e.find(e=>"[object String]"!==Object.prototype.toString.call(e)))throw new Error("Invalid color");let i=this;return e.forEach((e,l)=>{[...t,...n].includes(e)?i=o[e].open+i+o[e].close:e.startsWith("#")&&(i=o[r[+!!l]].ansi(o.hexToAnsi(e))+i+o[r[+!!l]].close)}),i}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment