Last active
February 1, 2024 17:42
-
-
Save piksel/19bd5ae774f8a2a78cb828cd539a14f1 to your computer and use it in GitHub Desktop.
conlib
This file contains hidden or 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
import readline from 'node:readline/promises'; | |
/** | |
* conlib v6 | |
* @author nils måsén | |
* @copyright 2018-2024, nils måsén | |
*/ | |
export default class ConLib { | |
constructor(stream) { | |
this.stream = stream; | |
this.usage = ''; | |
} | |
static stderr() { | |
return new ConLib(process.stderr); | |
} | |
static stdout() { | |
return new ConLib(process.stdout); | |
} | |
log(s) { | |
this.stream.write(s); | |
return this; | |
} | |
logf = (...a) => this.log(colorFormat(...a)) | |
sendEsc(action, n) { | |
this.stream.write(`\x1b[${n}${action}`); | |
return this; | |
} | |
up = n => this.sendEsc('A', n) | |
down = n => this.sendEsc('B', n) | |
right = n => this.sendEsc('C', n) | |
left = n => this.sendEsc('D', n) | |
startNext = n => this.sendEsc('E', n) | |
startPrev = n => this.sendEsc('F', n) | |
col = n => this.sendEsc('G', n); | |
color = n => this.sendEsc('m', n); | |
reset = () => this.color(0); | |
nl = () => this.log('\n'); | |
clearToStart = () => this.sendEsc('K', 0); | |
clearToEnd = () => this.sendEsc('K', 1); | |
clearLine = () => this.sendEsc('K', 2); | |
fatal = (message) => { | |
this.log(message).nl().log(this.usage); | |
process.exit(1); | |
} | |
fatalf = (...a) => this.fatal(colorFormat(...a)) | |
confirm = async (question) => { | |
const rl = readline.createInterface({ input: process.stdin, output: this.stream }); | |
const answers = {y: true, n: false}; | |
do { | |
const answer = answers[(await rl.question(question + ' [y/n] ')).trim().toLowerCase()]; | |
if (typeof answer === 'boolean') { | |
rl.close(); | |
return answer; | |
} | |
} while(true); | |
} | |
static colorEnabled = true; | |
static setColorEnabled = (enabled) => { | |
ConLib.colorEnabled = enabled; | |
} | |
static createColorVariant = val => { | |
const colorFormatted = () => ConLib.colorEnabled ? `\x1b[${val}m` : ''; | |
const wrap = (v) => `${colorFormatted()}${v}\x1b[m`; | |
const tpl = (strings, ...values) => wrap(Array.isArray(strings) | |
? strings.map((s, i) => `${s ?? ''}${i < values.length ? values[i] : ''}`) | |
: strings); | |
const variant = (...a) => tpl(...a); | |
variant.tpl = tpl; | |
variant.wrap = wrap; | |
variant.colorFormatted = colorFormatted; | |
variant.toString = colorFormatted; | |
return variant; | |
} | |
static createColor = val => { | |
const loColor = ConLib.createColorVariant(30 + val); | |
const hiColor = ConLib.createColorVariant(90 + val); | |
loColor.hi = hiColor; | |
loColor.Hi = hiColor; | |
return loColor; | |
} | |
static black = ConLib.createColor(0); | |
static red = ConLib.createColor(1); | |
static green = ConLib.createColor(2); | |
static yellow = ConLib.createColor(3); | |
static blue = ConLib.createColor(4); | |
static purple = ConLib.createColor(5); | |
static cyan = ConLib.createColor(6); | |
static normal = ConLib.createColor(7); | |
static white = ConLib.normal.hi; | |
static grey = ConLib.black.hi; | |
static reset = ConLib.createColorVariant(0, 0); | |
static Colors = { | |
black: ConLib.black, | |
red: ConLib.red, | |
green: ConLib.green, | |
yellow: ConLib.yellow, | |
blue: ConLib.blue, | |
purple: ConLib.purple, | |
cyan: ConLib.cyan, | |
normal: ConLib.normal, | |
grey: ConLib.grey, | |
white: ConLib.white, | |
reset: ConLib.reset, | |
} | |
static coloredValue = (value, maxDepth = 2) => { | |
const { yellow, green, red, grey, reset, cyan, purple, blue, white } = ConLib.Colors; | |
const { coloredValue } = ConLib; | |
if (typeof value?.colorFormatted === 'function') { | |
return value.colorFormatted(); | |
} | |
switch (typeof value) { | |
case 'string': | |
// Prevent double encoding: | |
if (value.startsWith('\x1b[')) return value; | |
return yellow.hi(value); | |
case 'boolean': | |
return value ? green.hi(value) : red.hi(value); | |
case 'object': | |
if (value instanceof Error) { | |
const msg = value.message.trim().split('\n') | |
.map(r => r.toLowerCase().startsWith('error: ') ? r.substring(7) : r) | |
.join('\n'); | |
const name = value.name.toLowerCase() != 'error' ? red.wrap(value.name) + ': ' : ''; | |
const stack = ((stack) => { | |
if (!stack) return ''; | |
stack = stack.split('\n').filter((r, i) => i > 0 && !r.includes('conlib.mjs')).join('\n'); | |
return '\n'+grey.wrap(stack); | |
})(value.stack); | |
return `${name}${white}${msg}${stack}${reset}`; | |
} | |
if (Array.isArray(value)) { | |
if (maxDepth > 0) { | |
return grey`[` + value.map(av => coloredValue(av, maxDepth - 1)).join(grey + `, `) + grey`]`; | |
} else { | |
return purple.hi.wrap(`Array[${value.length}]`) | |
} | |
} | |
if (maxDepth > 0) { | |
return `${grey}{${Object.entries(value).map(([ek, ev]) => | |
`${cyan.hi(ek)}${grey}: ${coloredValue(ev, maxDepth - 1)}` | |
).join(grey`, `)}${grey}}${reset}`; | |
} else { | |
return purple.hi.wrap(Object.getPrototypeOf(value)?.constructor?.name ?? value) | |
} | |
case 'number': | |
case 'bigint': | |
return blue.hi(value); | |
case 'undefined': | |
return grey(value); | |
case 'symbol': | |
return cyan.hi(value.description ?? 'Symbol') | |
case 'function': | |
const strVal = ('' + value); | |
if (strVal.startsWith('function')) { | |
return `${cyan}function ${cyan.Hi}${value.name}${cyan}${strVal.substring(9 + value.name.length)}${reset}` | |
} else { | |
return `${cyan.Hi}${value.name}${cyan}${strVal}${reset}`; | |
} | |
default: | |
return white(value); | |
} | |
} | |
/** | |
* @param {string[]} strings | |
* @param {...any} values | |
* @returns {string} | |
*/ | |
static colorFormat = (strings, ...values) => | |
strings.flatMap((s, i) => [s, ...(i < values.length ? [ConLib.coloredValue(values[i])] : [ConLib.reset])]).join(''); | |
} | |
export const black = ConLib.black; | |
export const red = ConLib.red; | |
export const green = ConLib.green; | |
export const yellow = ConLib.yellow; | |
export const blue = ConLib.blue; | |
export const purple = ConLib.purple; | |
export const cyan = ConLib.cyan; | |
export const normal = ConLib.normal; | |
export const grey = ConLib.grey; | |
export const white = ConLib.white; | |
export const reset = ConLib.reset; | |
export const Colors = ConLib.Colors; | |
export const colorFormat = ConLib.colorFormat; | |
export const coloredPath = (path, root, opts) => new ColoredPath(path, root, opts); | |
export class ColoredPath { | |
prefix = ''; | |
constructor(path, root, opts = { stripRoot: false }) { | |
path = (path ?? '').replaceAll('\\', '/'); | |
root = (root ?? '').replaceAll('\\', '/'); | |
if (root && !root.endsWith('/')) { | |
root += '/' | |
} | |
if (path.startsWith(root)) { | |
path = path.slice(root.length); | |
this.prefix = root; | |
} | |
this.paths = path.split(/\/|\\/); | |
this.opts = opts; | |
} | |
formatPrefix() { | |
if (!this.opts.stripRoot) { | |
return `${ConLib.grey}${this.prefix}`; | |
} | |
return '' | |
} | |
colorFormatted() { | |
const purple = ConLib.purple; | |
const sep = purple + '/' + purple.Hi; | |
return `${this.formatPrefix()}${purple.hi(this.paths.join(sep))}` | |
} | |
toString() { | |
return this.colorFormatted(); | |
} | |
} |
This file contains hidden or 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
import ConLib, { colorFormat, Colors, coloredPath } from "./conlib.mjs"; | |
const { grey, reset, white, blue } = Colors; | |
let level = 0; | |
const con = ConLib.stdout(); | |
const logPad = () => `${grey}${(level > 1 ? '' : '').padStart(level * 2, ' ')}${reset}`; | |
const log = m => con.log(logPad() + (m ?? '')).nl(); | |
const logf = (s, ...v) => log(colorFormat(s, ...v)); | |
const section = (title, fun) => { | |
const border = ''.padEnd((70 - title.length) - level, '-'); | |
if (level < 1) { | |
log(`${grey}--| ${white}${title} ${grey}|${border}${reset}`); | |
} else { | |
log(); | |
log(`${grey} -| ${white}${title}${reset}`); | |
} | |
level++; | |
log(); | |
fun(); | |
log(); | |
level--; | |
} | |
log(white`\n ConLib Example\n`); | |
section('Strings', () => { | |
logf`${'hello?'}`; | |
}); | |
section('Numbers', () => { | |
// eslint-disable-next-line no-undef | |
logf`${5} ${BigInt(5)}`; | |
}); | |
section('Booleans', () => { | |
logf`${true} ${false}`; | |
}); | |
section('Arrays', () => { | |
logf`${[1, 2, 3, 4, 8]}`; | |
logf`${['somtimesimalone', 'sometimesamnot']}`; | |
}); | |
section('Objects', () => { | |
logf`${{ foo: 'bar', baz: 5, kez: [2, 5] }}`; | |
}); | |
section('Symbols', () => { | |
logf`\${Symbol()} ${grey`=>`} ${Symbol()}` | |
logf`\${Symbol('NamedSymbol')} ${grey`=>`} ${Symbol('NamedSymbol')}` | |
}); | |
section('Functions', () => { | |
section('Fat arrow', () => { | |
const add = (left, right) => left + right; | |
logf` Named: ${add}` | |
logf`Anonymous: ${(left, right) => left * right}` | |
}) | |
section('Regular', () => { | |
function subtract(left, right) { return left - right } | |
logf` Named: ${subtract}` | |
const modulus = function (left, right) { return left - right } | |
logf` Variable: ${modulus}` | |
logf`Anonymous: ${function (left, right) { return left / right }}` | |
}); | |
}); | |
section('Paths', () => { | |
logf`${coloredPath('/usr/local/etc/service.config', '/usr/local')}`; | |
logf`${coloredPath('/usr/local/etc/service.config', '/usr/local', { stripRoot: true })}`; | |
logf`${coloredPath('/usr/local/etc/service.config', '/usr/local/', { stripRoot: true })}`; | |
}); | |
section('Proxies', () => { | |
section('Stringable', () => { | |
log(`\${blue}blue ${grey`=>`} ${blue}blue${reset}`); | |
log(`\${blue.hi}blue ${grey`=>`} ${blue.hi}blue.hi${reset}`); | |
log(); | |
log(`colorFormat\`\${blue}blue\` ${grey`=>`} ${colorFormat`${blue}blue`}`); | |
}); | |
section('Tagged template', () => { | |
log(`blue\`blue\` ${grey`=>`} ${blue`blue`}`); | |
log(`blue.hi\`blue\` ${grey`=>`} ${blue.hi`blue.hi`}`); | |
}); | |
}); | |
section('Config', () => { | |
section('Color Enabled', () => { | |
ConLib.setColorEnabled(true); | |
logf`${{ str: 'bar', yes: true, no: false, num: 5, arr: [2, 5], path: coloredPath('/etc/passwd', '/etc') }}`; | |
}) | |
section('Color Disabled', () => { | |
ConLib.setColorEnabled(false); | |
logf`${{ str: 'bar', yes: true, no: false, num: 5, arr: [2, 5], path: coloredPath('/etc/passwd', '/etc') }}`; | |
ConLib.setColorEnabled(true); | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment