Last active
October 10, 2024 06:56
-
-
Save nyteshade/42ab5ad99ef2f78882447eab22ea4c82 to your computer and use it in GitHub Desktop.
Select Graphic Rendition (SGR)
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
/** | |
* Applies Select Graphic Rendition (SGR) parameters to a given message for | |
* styling in terminal environments. This function allows for the dynamic | |
* styling of text output using ANSI escape codes. It supports a variety of | |
* modes such as color, brightness, and text decorations like bold or underline. | |
* | |
* @param {string} message The message to be styled. | |
* @param {...string} useModes A series of strings representing the desired | |
* styling modes. Modes can include colors (e.g., 'red', 'blue'), brightness | |
* ('bright'), foreground/background ('fg', 'bg'), and text decorations | |
* ('bold', 'underline'). Modes can be combined in a single string using | |
* commas or passed as separate arguments. | |
* | |
* Colors: | |
* ``` | |
* 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' | |
* ``` | |
* Color Specifiers: | |
* ``` | |
* 'fg' -> foreground | 'bg' -> background | 'bright' -> bright colors | |
* ``` | |
* | |
* Modes: | |
* ``` | |
* 'blink' or 'k' | 'conceal' or 'c' | 'italics' or 'i' | 'strike' or 's' | |
* 'bold' or 'b' | 'dim' or 'd' | 'negative' or 'n' | 'underline' or 'u' | |
* ``` | |
* | |
* Examples: | |
* - `sgr('Hello', 'red')` applies red color to 'Hello'. | |
* - `sgr('World', 'green,bold')` applies green color and bold styling | |
* to 'World'. | |
* - `sgr('Example', 'bluebgbright')` applies bright blue | |
* background color. | |
* | |
* Short hand syntax is also allowed: | |
* - `sgr('hello', 'biu')` applies bold, italics and underline | |
* - `sgr('hello', 'bi,redfg')` applies bold, italics and red foreground | |
* | |
* As a bonus, there is a secret getter applied to the return string that | |
* allows you to invoke `sgr(...).show` to automatically log the output to | |
* `console.log`. This is done by wrapping the output string in `Object()` | |
* to make it a `String` instance and then adding the property descriptor. | |
* A custom `Symbol` is applied to make it evaluate in nodejs as though it | |
* were a normal string. To strip the extras, wrap the output in `String()` | |
* | |
* @returns {string} The message wrapped in ANSI escape codes corresponding | |
* to the specified modes. The returned string, when printed to a terminal, | |
* displays the styled message. Additional properties are attached to the | |
* result for utility purposes, such as 'show' for immediate console output. | |
*/ | |
function sgr(message, ...useModes) { | |
const colors = Object.assign( | |
['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'], | |
{ | |
isBG: a => !!/bg/i.exec(a), | |
isBright: a => !!/bright/i.exec(a), | |
isColor: a => { | |
let color = colors.find(c => new RegExp(c, 'i').exec(a)); | |
return [!!color, colors.indexOf(color)]; | |
}, | |
} | |
); | |
const arrayifyString = s => { | |
if (Array.isArray(s)) { | |
let results = []; | |
for (const i of s) { | |
results = [ ...results, ...arrayifyString(i) ]; | |
} | |
return results.flat().filter(i => i.length); | |
} | |
if (!s || typeof s !== 'string') { | |
return ['']; | |
} | |
else if (s.includes(',')) { | |
return arrayifyString(s.split(',')); | |
} | |
else { | |
if (!colors.isColor(s)[0] && s.length > 1) { | |
return [...s]; | |
} | |
else return [s]; | |
} | |
} | |
let modes = arrayifyString(useModes) | |
const sgrModes = { | |
blink: ['\x1b[5m', '\x1b[25m', 'k'], | |
bold: ['\x1b[1m', '\x1b[22m', 'b'], | |
conceal: ['\x1b[8m', '\x1b[28m', 'c'], | |
dim: ['\x1b[2m', '\x1b[22m', 'd'], | |
italics: ['\x1b[3m', '\x1b[23m', 'i'], | |
negative: ['\x1b[7m', '\x1b[27m', 'n'], | |
strike: ['\x1b[9m', '\x1b[29m', 's'], | |
underline: ['\x1b[4m', '\x1b[24m', 'u'], | |
}; | |
Object.values(sgrModes).forEach(mode => sgrModes[mode[2]] = mode); | |
const codes = a => { | |
let open = '', close = '', mode = String(a).toLowerCase(); | |
let [_isColor, colorIndex] = colors.isColor(mode); | |
if (_isColor) { | |
open = colors.isBG(mode) | |
? `\x1b[${colors.isBright(mode) ? 10 : 4}${colorIndex}m` | |
: `\x1b[${colors.isBright(mode) ? 9 : 3}${colorIndex}m`; | |
close = colors.isBG(mode) ? '\x1b[49m' : `\x1b[39m`; | |
} | |
else if (sgrModes[mode]) { | |
open = sgrModes[mode][0]; | |
close = sgrModes[mode][1]; | |
} | |
return [open, close]; | |
}; | |
const onOrder = modes.map(key => codes(key)[0]).join(''); | |
const offOrder = modes.map(key => codes(key)[1]).reverse().join(''); | |
let result = Object(`${onOrder}${message}${offOrder}`) | |
Object.defineProperties(result, { | |
show: { | |
get() { console.log(String(this)); return this }, | |
enumerable: false, | |
}, | |
[Symbol.for('nodejs.util.inspect.custom')]: { | |
value(depth, options, inspect) { | |
return inspect(String(this), options) | |
}, | |
enumerable: false, | |
}, | |
}) | |
return result | |
} | |
/** | |
* A tag string variant of the function {@link sgr}. Examples of used might | |
* be something like the following | |
* | |
* @example | |
* `sgrtag('biu')`this string is bold, italicized and underlined` | |
* | |
* @param {string[]} useModes an infinite variety of string modes defined | |
* in the {@link sgr} function above | |
* @returns {string} | |
* | |
* @see {@link sgr} | |
*/ | |
function sgrtag(...useModes) { | |
return function(strings, ...values) { | |
const finalString = strings.reduce((result, string, i) => { | |
return result + string + (values[i] || ''); | |
}, ''); | |
return sgr(finalString, ...useModes); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment