Skip to content

Instantly share code, notes, and snippets.

@nyteshade
Created February 10, 2024 05:08
Show Gist options
  • Save nyteshade/42ab5ad99ef2f78882447eab22ea4c82 to your computer and use it in GitHub Desktop.
Save nyteshade/42ab5ad99ef2f78882447eab22ea4c82 to your computer and use it in GitHub Desktop.
Select Graphic Rendition (SGR)
/**
* 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment