UNPKG

console-toolkit

Version:

Toolkit to produce a fancy console output (boxes, tables, charts, colors).

621 lines (583 loc) 18.5 kB
import * as sgr from './ansi/sgr.js'; import { RESET_STATE, combineStates, addCommandsToState, stateTransition, stateReverseTransition, stringifyCommands, toState, optimize, extractState } from './ansi/sgr-state.js'; import {matchCsi} from './ansi/csi.js'; import {capitalize, toCamelCase, fromSnakeCase, addGetter, addAliases, addGetters} from './meta.js'; export {RESET_STATE}; const styleSymbol = Symbol('styleObject'), isBrightSymbol = Symbol('isBright'), initStateSymbol = Symbol('initState'), stateSymbol = Symbol('currentState'), colorDepthSymbol = Symbol('colorDepth'), optionsSymbol = Symbol('options'); class ExtendedColor { constructor(styleObject, options, isBright) { this[styleSymbol] = styleObject; this[optionsSymbol] = options; this[isBrightSymbol] = isBright; } make(newCommands) { if (Array.isArray(newCommands)) newCommands[0] = this[optionsSymbol].extended; return this[styleSymbol].make(newCommands); } // options: bright get bright() { return new ExtendedColor(this[styleSymbol], this[optionsSymbol], true); } get dark() { return new ExtendedColor(this[styleSymbol], this[optionsSymbol], false); } get default() { return this.make(this[optionsSymbol].default); } // standard colors: defined externally // get red() { // return this.make([this[optionsSymbol].base, sgr.ColorFormat.COLOR_256, (this[isBrightSymbol] ? 8 : 0) + sgr.Colors.RED]); // } // get brightRed() { // return this.make([this[optionsSymbol].base, sgr.ColorFormat.COLOR_256, 8 + sgr.Colors.RED]); // } // 256 colors color(c) { return this.make(sgr.getRawColor256(c)); } stdRgb256(r, g, b) { return this.make(sgr.getRawColor256((this[isBrightSymbol] ? 8 : 0) + sgr.colorStdRgb(r, g, b))); } brightStdRgb256(r, g, b) { return this.make(sgr.getRawColor256(8 + sgr.colorStdRgb(r, g, b))); } darkStdRgb256(r, g, b) { return this.make(sgr.getRawColor256(sgr.colorStdRgb(r, g, b))); } rgb256(r, g, b) { return this.make(sgr.getColor256(r, g, b)); } hex256(hex) { return this.make(sgr.getHexColor256(hex)); } rgb6(r, g, b) { return this.make(sgr.getColor6(r, g, b)); } grayscale256(i) { return this.make(sgr.getGrayColor256(i)); } grayscale24(i) { return this.make(sgr.getGrayColor24(i)); } // true colors trueColor(r, g, b) { return this.make(sgr.getTrueColor(r, g, b)); } trueGrayscale(i) { return this.make(sgr.getTrueColor(i, i, i)); } hexTrueColor(hex) { return this.make(sgr.getHexTrueColor(hex)); } // composite rgb(r, g, b) { return this[styleSymbol][colorDepthSymbol] > 8 ? this.trueColor(r, g, b) : this.rgb256(r, g, b); } grayscale(i) { return this[styleSymbol][colorDepthSymbol] > 8 ? this.trueGrayscale(i) : this.grayscale256(i); } hex(hex) { return this[styleSymbol][colorDepthSymbol] > 8 ? this.hexTrueColor(hex) : this.hex256(hex); } } addAliases(ExtendedColor, {stdRgb: 'stdRgb256', brightStdRgb: 'brightStdRgb256', darkStdRgb: 'darkStdRgb256'}); class Color extends ExtendedColor { // options: bright get bright() { return new Color(this[styleSymbol], this[optionsSymbol], true); } get dark() { return new Color(this[styleSymbol], this[optionsSymbol], false); } // standard colors: defined externally // get red() { // return this.make((this[isBrightSymbol] ? this[optionsSymbol].brightBase : this[optionsSymbol].base) + sgr.Colors.RED); // } // get brightRed() { // return this.make(this[optionsSymbol].brightBase + sgr.Colors.RED); // } stdRgb(r, g, b) { return this.make( (this[isBrightSymbol] ? this[optionsSymbol].brightBase : this[optionsSymbol].base) + sgr.colorStdRgb(r, g, b) ); } brightStdRgb(r, g, b) { return this.make(this[optionsSymbol].brightBase + sgr.colorStdRgb(r, g, b)); } darkStdRgb(r, g, b) { return this.make(this[optionsSymbol].base + sgr.colorStdRgb(r, g, b)); } } class Bright { constructor(styleObject, isBright) { this[styleSymbol] = styleObject; this[isBrightSymbol] = isBright; } make(newCommands) { return this[styleSymbol].make(newCommands); } // options: bright get bright() { return new Bright(this[styleSymbol], true); } get dark() { return new Bright(this[styleSymbol], false); } // standard colors: defined externally // get red() { // return this.make(this[isBrightSymbol] ? sgr.getBrightColor(sgr.Colors.RED) : sgr.getColor(sgr.Colors.RED)); // } stdRgb(r, g, b) { return this.make(this[isBrightSymbol] ? sgr.getBrightStdRgb(r, g, b) : sgr.getStdRgb(r, g, b)); } brightStdRgb(r, g, b) { return this.make(sgr.getBrightStdRgb(r, g, b)); } darkStdRgb(r, g, b) { return this.make(sgr.getStdRgb(r, g, b)); } } class Reset { constructor(styleObject) { this[styleSymbol] = styleObject; } make(newCommands) { return this[styleSymbol].make(newCommands); } // resettable properties: defined externally get all() { return this.make(''); } get bold() { return this[styleSymbol].addState({bold: null}); } get dim() { return this[styleSymbol].addState({dim: null}); } } export class Style { constructor(initState, currentState, colorDepth = 24) { this[initStateSymbol] = toState(initState); this[stateSymbol] = currentState ? toState(currentState) : this[initStateSymbol]; this[colorDepthSymbol] = colorDepth; } make(newCommands = []) { if (!Array.isArray(newCommands)) newCommands = [newCommands]; return new Style(this[initStateSymbol], addCommandsToState(this[stateSymbol], newCommands), this[colorDepthSymbol]); } add(commandSequence) { const state = extractState(String(commandSequence), this[stateSymbol]); return state === this[stateSymbol] ? this : new Style(this[initStateSymbol], state, this[colorDepthSymbol]); } addState(state) { return new Style(this[initStateSymbol], combineStates(this[stateSymbol], toState(state)), this[colorDepthSymbol]); } mark(fn) { const newStyle = new Style(this[stateSymbol], null, this[colorDepthSymbol]); if (typeof fn != 'function') return newStyle; fn(newStyle); return this; } getInitialState(fn) { if (typeof fn != 'function') return this[initStateSymbol]; fn(this[initStateSymbol]); return this; } getState(fn) { if (typeof fn != 'function') return this[stateSymbol]; fn(this[stateSymbol]); return this; } // color depth get colorDepth() { return this[colorDepthSymbol]; // 1, 4, 8, 24 } setColorDepth(colorDepth) { return new Style(this[initStateSymbol], this[stateSymbol], colorDepth); } // fg, bg, decoration, reset, bright get fg() { return new Color(this, sgr.FgColorOptions); } get bg() { return new Color(this, sgr.BgColorOptions); } get colorDecoration() { return new ExtendedColor(this, sgr.DecorationColorOptions); } get reset() { return new Reset(this); } get bright() { return new Bright(this, true); } get dark() { return new Bright(this, false); } // general commands: defined externally get resetAll() { return this.make(''); } get resetBold() { return this.addState({bold: null}); } get resetDim() { return this.addState({dim: null}); } // color commands: defined externally stdRgb(r, g, b) { return this.make(sgr.getStdRgb(r, g, b)); } brightStdRgb(r, g, b) { return this.make(sgr.getBrightStdRgb(r, g, b)); } color(c) { return this.make(sgr.getRawColor256(c)); } rgb256(r, g, b) { return this.make(sgr.getColor256(r, g, b)); } hex256(hex) { return this.make(sgr.getHexColor256(hex)); } rgb6(r, g, b) { return this.make(sgr.getColor6(r, g, b)); } grayscale256(i) { return this.make(sgr.getGrayColor256(i)); } grayscale24(i) { return this.make(sgr.getGrayColor24(i)); } trueColor(r, g, b) { return this.make(sgr.getTrueColor(r, g, b)); } trueGrayscale(i) { return this.make(sgr.getTrueColor(i, i, i)); } hexTrueColor(hex) { return this.make(sgr.getHexTrueColor(hex)); } rgb(r, g, b) { return this[colorDepthSymbol] > 8 ? this.trueColor(r, g, b) : this.rgb256(r, g, b); } grayscale(i) { return this[colorDepthSymbol] > 8 ? this.trueGrayscale(i) : this.grayscale256(i); } hex(hex) { return this[colorDepthSymbol] > 8 ? this.hexTrueColor(hex) : this.hex256(hex); } bgStdRgb(r, g, b) { return this.make(getBgStdRgb(r, g, b)); } bgBrightStdRgb(r, g, b) { return this.make(sgr.getBgBrightStdRgb(r, g, b)); } bgColor(c) { return this.make(sgr.getBgRawColor256(c)); } bgRgb256(r, g, b) { return this.make(sgr.getBgColor256(r, g, b)); } bgHex256(hex) { return this.make(sgr.getBgHexColor256(hex)); } bgRgb6(r, g, b) { return this.make(sgr.getBgColor6(r, g, b)); } bgGrayscale256(i) { return this.make(sgr.getBgGrayColor256(i)); } bgGrayscale24(i) { return this.make(sgr.getBgGrayColor24(i)); } bgTrueColor(r, g, b) { return this.make(sgr.getBgTrueColor(r, g, b)); } bgTrueGrayscale(i) { return this.make(sgr.getBgTrueColor(i, i, i)); } bgHexTrueColor(hex) { return this.make(sgr.getBgHexTrueColor(hex)); } bgRgb(r, g, b) { return this[colorDepthSymbol] > 8 ? this.bgTrueColor(r, g, b) : this.bgRgb256(r, g, b); } bgGrayscale(i) { return this[colorDepthSymbol] > 8 ? this.bgTrueGrayscale(i) : this.bgGrayscale256(i); } bgHex(hex) { return this[colorDepthSymbol] > 8 ? this.bgHexTrueColor(hex) : this.bgHex256(hex); } decorationStdRgb256(r, g, b) { return this.make(sgr.getDecorationStdColor256(r, g, b)); } decorationBrightStdRgb256(r, g, b) { return this.make(getDecorationBrightStdColor256(r, g, b)); } decorationColor(c) { return this.make(sgr.getDecorationRawColor256(c)); } decorationRgb256(r, g, b) { return this.make(sgr.getDecorationColor256(r, g, b)); } decorationHex256(hex) { return this.make(sgr.getDecorationHexColor256(hex)); } decorationRgb6(r, g, b) { return this.make(sgr.getDecorationColor6(r, g, b)); } decorationGrayscale256(i) { return this.make(sgr.getDecorationGrayColor256(i)); } decorationGrayscale24(i) { return this.make(sgr.getDecorationGrayColor24(i)); } decorationTrueColor(r, g, b) { return this.make(sgr.getDecorationTrueColor(r, g, b)); } decorationTrueGrayscale(i) { return this.make(sgr.getDecorationTrueColor(i, i, i)); } decorationHexTrueColor(hex) { return this.make(sgr.getDecorationHexTrueColor(hex)); } decorationRgb(r, g, b) { return this[colorDepthSymbol] > 8 ? this.decorationTrueColor(r, g, b) : this.decorationRgb256(r, g, b); } decorationGrayscale(i) { return this[colorDepthSymbol] > 8 ? this.decorationTrueGrayscale(i) : this.decorationGrayscale256(i); } decorationHex(hex) { return this[colorDepthSymbol] > 8 ? this.decorationHexTrueColor(hex) : this.decorationHex256(hex); } // wrap a string text(s) { if (Array.isArray(s)) return s.map(s => this.text(s)); s = String(s); let state = this[stateSymbol]; const initialCommands = stateTransition(this[initStateSymbol], state); matchCsi.lastIndex = 0; for (const match of s.matchAll(matchCsi)) { if (match[3] !== 'm') continue; state = addCommandsToState(state, match[1].split(';')); } const cleanupCommands = stateReverseTransition(this[initStateSymbol], state); return stringifyCommands(initialCommands) + s + stringifyCommands(cleanupCommands); } // convert to string toString() { const initialCommands = stateTransition(this[initStateSymbol], this[stateSymbol]); return stringifyCommands(initialCommands); } } // add color names to ExtendedColor, Bright and Style const make = value => function () { return this.make(value); }; for (const [name, value] of Object.entries(sgr.Colors)) { const nameLower = name.toLowerCase(), nameCap = capitalize(name); addGetters(ExtendedColor, { [nameLower]: function () { return this.make([ this[optionsSymbol].extended, sgr.ColorFormat.COLOR_256, (this[isBrightSymbol] ? 8 : 0) + value ]); }, ['bright' + nameCap]: function () { return this.make([this[optionsSymbol].extended, sgr.ColorFormat.COLOR_256, 8 + value]); }, ['dark' + nameCap]: function () { return this.make([this[optionsSymbol].extended, sgr.ColorFormat.COLOR_256, value]); } }); addGetters(Color, { [nameLower]: function () { return this.make( (this[isBrightSymbol] ? this[optionsSymbol].brightBase : this[optionsSymbol].base) + sgr.colorNumber(value) ); }, ['bright' + nameCap]: function () { return this.make(this[optionsSymbol].brightBase + sgr.colorNumber(value)); }, ['dark' + nameCap]: function () { return this.make(this[optionsSymbol].base + sgr.colorNumber(value)); } }); addGetters(Bright, { [nameLower]: function () { return this.make(this[isBrightSymbol] ? sgr.getBrightColor(value) : sgr.getColor(value)); }, ['bright' + nameCap]: make(sgr.getBrightColor(value)), ['dark' + nameCap]: make(sgr.getColor(value)) }); addGetters(Style, { [nameLower]: make(sgr.getColor(value)), ['bright' + nameCap]: make(sgr.getBrightColor(value)), ['bg' + nameCap]: make(sgr.getBgColor(value)), ['bgBright' + nameCap]: make(sgr.getBgBrightColor(value)) }); addAliases(Style, {['dark' + nameCap]: nameLower, ['bgDark' + nameCap]: 'bg' + nameCap}); } addAliases(Style, { // method aliases addCommands: 'make', decoration: 'colorDecoration', foreground: 'fg', background: 'bg', // color aliases gray: 'brightBlack', bgGray: 'bgBrightBlack', // alias "gray" to "grey" grey: 'brightBlack', bgGrey: 'bgBrightBlack', greyscale: 'grayscale', greyscale24: 'grayscale24', greyscale256: 'grayscale256', trueGreyscale: 'trueGrayscale', bgGreyscale: 'bgGrayscale', bgGreyscale24: 'bgGrayscale24', bgGreyscale256: 'bgGrayscale256', bgTrueGreyscale: 'bgTrueGrayscale', decorationGreyscale: 'decorationGrayscale', decorationGreyscale24: 'decorationGrayscale24', decorationGreyscale256: 'decorationGrayscale256', decorationTrueGreyscale: 'decorationTrueGrayscale' }); addAliases(ExtendedColor, { // color aliases gray: 'brightBlack', // alias "gray" to "grey" grey: 'brightBlack', greyscale: 'grayscale', greyscale24: 'grayscale24', greyscale256: 'grayscale256', trueGreyscale: 'trueGrayscale' }); addAliases(Color, { // color aliases gray: 'brightBlack', // alias "gray" to "grey" grey: 'brightBlack' }); // add commands to Reset, Style const skipCommands = {EXTENDED_COLOR: 1, BG_EXTENDED_COLOR: 1, DECORATION_COLOR: 1}; for (const [name, value] of Object.entries(sgr.Commands)) { if (name.startsWith('RESET_')) { addGetter(Reset, toCamelCase(fromSnakeCase(name).slice(1)), make(value)); } if (skipCommands[name] !== 1) { addGetter(Style, toCamelCase(fromSnakeCase(name)), make(value)); } } // the back quote function const matchOps = /\{\{([\.\w]+)\}\}/g; const processPart = s => { s = String(s); const result = []; let start = (matchOps.lastIndex = 0); for (const match of s.matchAll(matchOps)) { result.push(s.substring(start, match.index), match[1].split('.')); start = match.index + match[0].length; } if (start < s.length) result.push(s.substring(start)); return result; }; const processStringConstant = (strings, i, result, stack, style) => { const pos = () => (i < strings.length ? 'string before argument #' + i : 'string after the last argument'); for (const input of processPart(strings[i])) { if (typeof input == 'string') { // process a string style = style.add(input); result += style + input; style = style.mark(); continue; } // process commands for (const command of input) { switch (command) { case 'save': const setupCommands = stateTransition(style[initStateSymbol], style[stateSymbol]); result += stringifyCommands(setupCommands); stack.push(style); style = style.mark(); continue; case 'restore': { const newStyle = style; style = stack.pop(); if (!style) throw new ReferenceError(`Unmatched restore (${pos()})`); const cleanupCommands = stateReverseTransition(style[stateSymbol], newStyle[stateSymbol]); result += stringifyCommands(cleanupCommands); style = style.mark(); } continue; } style = style[command]; if (!style) throw new TypeError(`Wrong style property: "${command}" (${pos()})`); } if (!(style instanceof Style)) throw new TypeError(`The final object is not Style (${pos()})`); result += style; style = style.mark(); } return {result, style}; }; const makeBq = clear => (strings, ...args) => { const callAsFunction = !Array.isArray(strings), states = callAsFunction && strings; const bq = (strings, ...args) => { const stack = []; let style = new Style(states?.initState, states?.setState), result = ''; for (let i = 0; i < args.length; ++i) { // process a string constant ({result, style} = processStringConstant(strings, i, result, stack, style)); // process an argument const arg = args[i]; if (typeof arg == 'function') { style = arg(style); if (!(style instanceof Style)) throw new TypeError(`The returned object is not Style (argument #${i})`); result += style; style = style.mark(); continue; } const input = String(arg); style = style.add(input); result += input; style = style.mark(); } ({result, style} = processStringConstant(strings, strings.length - 1, result, stack, style)); if (clear) { const cleanupCommands = stateReverseTransition(states?.initState, style[stateSymbol]); result += stringifyCommands(cleanupCommands); } return optimize(result, states?.initState); }; if (callAsFunction) return bq; return bq(strings, ...args); }; export const s = makeBq(false); export const c = makeBq(true); // singleton export const style = new Style({}); export default style;