UNPKG

topkat-utils

Version:

A comprehensive collection of TypeScript/JavaScript utility functions for common programming tasks. Includes validation, object manipulation, date handling, string formatting, and more. Zero dependencies, fully typed, and optimized for performance.

363 lines 16.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.dim = exports.cliLoadingSpinner = exports.cliProgressBar = exports.C = exports.logger = void 0; /* eslint-disable no-console */ /* eslint-disable no-control-regex */ //---------------------------------------- // LOGGER //---------------------------------------- const config_1 = require("./config"); const isset_1 = require("./isset"); const remove_circular_json_stringify_1 = require("./remove-circular-json-stringify"); const clean_stack_trace_1 = require("./clean-stack-trace"); exports.logger = { /** log is an OUTDATED alisas of info, appError is for warning */ log(str, level = 'info') { const { preprocessLog } = (0, config_1.configFn)(); if (typeof preprocessLog === 'function') str = preprocessLog(str) || str; if ((0, isset_1.isset)(console[level])) console[level](str); else console.log(str); exports.logger.raw.push(str + `\n`); exports.logger.raw = exports.logger.raw.slice(exports.logger.raw.length - (0, config_1.configFn)()?.nbOfLogsToKeep, exports.logger?.raw?.length); }, /** * @param {String[]|String} inputLogs */ toHtml(inputLogs = [...exports.logger.raw]) { if (!Array.isArray(inputLogs)) inputLogs = [inputLogs]; const code2css = { 2: `opacity:.5`, 32: `color:#679933`, 31: `color:#A8383B`, 33: `color:#D7941C`, 35: `color:#C21949`, 36: `color:#128C6D`, 34: `color:#1B568B`, // blue }; let htmlLogs = ''; inputLogs.join('<br/>').split('\x1b[').forEach((bit, i) => { // the first doesn't have a preceding (may be undef) if (bit) { if (i !== 0) { const [, code, R, G, B, content] = bit.match(/(^\d\d?)(?:;|m)(?:\d;(\d+);(\d+);(\d+)m)?(.*$)/) || []; if (content) { const style = !(0, isset_1.isset)(R) ? (0, isset_1.isset)(code) ? code2css[code] : undefined : `color:rgb(${R},${G},${B})`; htmlLogs += (0, isset_1.isset)(style) ? `<span style='${style}'>${content.replace(/\n/g, '<br>')}</span>` : content.replace(/\n/g, '<br>'); } } else htmlLogs += bit.replace(/\n/g, '<br>'); } }); return `<div style='color:#ccc'>${htmlLogs}<br></div>`; }, toText(inputLogs = [...exports.logger.raw]) { const str = Array.isArray(inputLogs) ? inputLogs.join('\n') : inputLogs; return str.replace(/\x1b\[.*?m/g, ''); }, raw: [], json: [], }; /** // console colored output // * console.log(C.green(C.dim('Hey bro !'))) // * or C.log() // will use color defined by themes // * or C.line('MY TITLE', 53) // * or C.gradientize(myLongString) */ exports.C = { dim: str => exports.C.output(2, str), green: str => exports.C.output(32, str), red: str => exports.C.output(31, str), yellow: str => exports.C.output(33, str), grey: str => exports.C.output(2, str), magenta: str => exports.C.output(35, str), cyan: str => exports.C.output(36, str), blue: str => exports.C.output(34, str), primary: str => { const primary = (0, config_1.configFn)()?.terminal?.theme?.primary; return exports.C.rgb(...primary) + str + exports.C.reset; }, reset: '\x1b[0m', output: (code, str = '') => (0, config_1.configFn)()?.terminal?.noColor ? str : `\x1b[${code}m${str}\x1b[0m`, // true RGB colors B-* rgb: (r, g = 0, b = 0) => (0, config_1.configFn)()?.terminal?.noColor || !(0, isset_1.isset)(r, g, b) ? '' : `\x1b[38;2;${r};${g};${b}m`, bg: (r, g, b) => (0, config_1.configFn)()?.terminal?.noColor || !(0, isset_1.isset)(r, g, b) ? '' : `${'\x1b['}48;2;${r};${g};${b}m`, /** Output a line of title */ line(title = '', length = (0, config_1.configFn)()?.terminal?.theme?.pageWidth, clr = (0, config_1.configFn)()?.terminal?.theme?.primary, char = '=') { this.log('\u00A0\n' + this.rgb(...clr) + (title + ' ').padEnd(length || 0, char) + this.reset + '\u00A0\n'); }, /** Eg: ['cell1', 'cell2', 'cell3'], [25, 15] will start cell2 at 25 and cell 3 at 25 + 15 * @param {Array} limits default divide the viewport */ cols(strings, limits = [], clr = (0, config_1.configFn)()?.terminal?.theme?.fontColor) { if (!limits.length) { const colWidth = Math.round((0, config_1.configFn)()?.terminal?.theme.pageWidth / strings.length); limits = Array(strings.length - 1).fill(2).map((_, i) => colWidth * i + 1); } const str = strings.reduce((glob, str = '', i) => { const realCharLength = str.toString().replace(/\x1b\[.*?m/, '').length; const charLength = str.toString().length; const realLimit = limits[i] + charLength - realCharLength; return glob + str.toString().substring(0, realLimit || 999).padEnd(realLimit || 0, ' '); }, ''); this.logClr(str, clr); }, /** Console log alias */ log(...stringsCtxMayBeFirstParam) { stringsCtxMayBeFirstParam.forEach(str => this.logClr(str)); }, logClr(str, clr = (0, config_1.configFn)()?.terminal?.theme?.fontColor) { if (!(0, isset_1.isset)(str)) return; str = (typeof clr !== 'undefined' ? this.rgb(...clr) : '') + str.toString().replace(/\n/g, '\n' + (typeof clr !== 'undefined' ? this.rgb(...clr) : '')); exports.logger.log(str + this.reset, 'info'); }, info(...str) { str.forEach((s, i) => { if (i === 0) this.logClr('ⓘ ' + s, (0, config_1.configFn)()?.terminal?.theme?.primary); else this.log(this.dimStrSplit(s)); }); this.log(' '); }, success(...str) { str.forEach((s, i) => { if (i === 0) this.log(this.green('✓ ' + s)); else this.log(this.dimStrSplit(s)); }); }, /** First param **false** to avoid logging stack trace */ error: (...errors) => logErrPrivate('error', [255, 0, 0], ...errors), /** First param **false** to avoid logging stack trace */ warning: (...str) => logErrPrivate('warn', [255, 122, 0], ...str), customError: (color, ...str) => logErrPrivate('error', color, ...str), customWarning: (color, ...str) => logErrPrivate('warn', color, ...str), applicationError: (color, ...str) => logErrPrivate('warn', color, ...str), warningLight: (_, ...str) => logErrPrivate('warn', [196, 120, 52], ...str), dimStrSplit(...logs) { const logsStr = []; logs.filter(isset_1.isset).forEach(log => log.toString().split('\n').forEach(line => line && logsStr.push(this.dim(` ${line}`)))); return logsStr.join('\n'); }, notifShow() { console.log('\n\u00A0'); this.notifications.forEach(fn => fn()); this.notifications = []; console.log('\n\u00A0'); }, /** Keep in memory the logs to show when needed with C.notifShow() * Ex: C.notification('info', str); */ notification(type, ...messages) { this.notifications.push(() => { if ((0, isset_1.isset)(this[type])) { this[type](...messages); } else { this.warning('Wrong param for C.notification'); } }); }, notifications: [], /** Gratientize lines of text (separated by \n) */ gradientize(str = '', rgb1 = (0, config_1.configFn)()?.terminal?.theme?.shade1, rgb2 = (0, config_1.configFn)()?.terminal?.theme?.shade2, bgRgb = (0, config_1.configFn)()?.terminal?.theme?.bgColor, paddingY = 2) { const lines = str.split('\n'); const largestLine = lines.reduce((sum, line) => sum < line.length ? line.length : sum, 0); const rgbParts = rgb1.map((val, i) => (val - rgb2[i]) / (lines.length)); const bg = bgRgb ? this.bg(bgRgb[0], bgRgb[1], bgRgb[2]) : ''; const padLine = bg + ' '.padEnd(largestLine, ' ') + '\x1b[0m\n'; console.log(padLine.repeat(paddingY) + lines.reduce((s, line, i) => { return s + bg + this.rgb(...(rgb1.map((val, i2) => Math.round(val - i * rgbParts[i2])))) + line.padEnd(largestLine, ' ') + '\x1b[0m\n'; }, '') + padLine.repeat(paddingY)); }, debugModeLog(title, ...string) { this.logClr('🐞 ' + title, (0, config_1.configFn)()?.terminal?.theme?.debugModeColor); this.log(this.dimStrSplit(...string)); }, /** allow to clear the last lines written to console */ clearLastLines(n = 2) { for (let i = 0; i < n; i++) { process?.stdout?.moveCursor?.(0, -1); // Move up one line process?.stdout?.clearLine?.(0); } process?.stdout?.cursorTo?.(0); // Move cursor to beginning of the current line } }; function logErrPrivate(level, color, ...errors) { const { isProd } = (0, config_1.configFn)(); if (errors.length === 1 && typeof errors[0]?.log === 'function') return errors[0].log(); let stackTrace = (new Error('')).stack || ''; const displayStack = errors[0] === false ? errors.shift() : true; const symbol = level === 'error' ? '✘ ' : '⚠ '; if (errors.length > 1 && !(0, isset_1.isset)(errors[0])) errors.shift(); // remove first empty object const getStringFromErr = (err, i) => { if (!(0, isset_1.isset)(err)) return ''; else if (typeof err === 'string') { if (i === 0) return exports.C.rgb(...color) + symbol + err + exports.C.reset; else return err.split('\n').map(val => exports.C.dim(val)).join('\n'); } else if (err instanceof Error) { const { str, stackTrace: stkTrc } = stringifyInstanceOfError(err, level, color); if (stkTrc) stackTrace = stkTrc; return str; } else if (typeof err === 'object') { let msg = ''; msg += (0, remove_circular_json_stringify_1.removeCircularJSONstringify)(err, 2).split('\n').map(val => exports.C.dim(val)).join('\n') + '\n'; const { str, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo || err, level, color); if (stkTrc) stackTrace = stkTrc; msg += str; return msg; } else return ''; }; if (errors.length && errors[0]) { const messages = errors.map((e, i) => { if (typeof e?.log === 'function') { e.log(); return ''; } else return getStringFromErr(e, i); }); // Stack if (displayStack) { messages.push(isProd ? stackTrace : (0, clean_stack_trace_1.cleanStackTrace)(stackTrace) + '\n'); } exports.logger.log(messages.join(''), level); } } function stringifyInstanceOfError(err, type = 'error', color = [255, 0, 0], level = 0) { if (level > 5) return { str: '' }; let str = ''; let stackTrace; const symbol = type === 'error' ? '✘ ' : '⚠ '; const title = err.msg || err.message || err.id || (err.stack ? err.stack.split('\n')[0] : 'Error'); // Err mess str += exports.C.rgb(...color) + symbol + title + exports.C.reset + '\n'; if (err.stack) stackTrace = err.stack; // more relevant // ExtraInfos if ((0, isset_1.isset)(err.extraInfo)) { const { str: str2, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo, type, color, level++); if (stkTrc) stackTrace = stkTrc; str += str2; } return { str, stackTrace }; } function stringifyExtraInfos(extraInfoOriginal, type, color, level = 0) { let stackTrace; const originalError = [exports.C.dim(`ORIGINAL ERROR ${'-'.repeat(39)}\n`)]; if (extraInfoOriginal instanceof Error) { // case where error is passed directly to extraInfos return stringifyInstanceOfError(extraInfoOriginal, type, color); } else { const extraInfo = { ...extraInfoOriginal }; const extraInfos = [exports.C.dim(`EXTRA INFOS ${'-'.repeat(41)}\n`)]; if (typeof extraInfo === 'object' && Object.keys(extraInfo).length) { for (const itemName in extraInfo) { if (extraInfo[itemName] instanceof Error) { const { str, stackTrace: stkTrc } = stringifyInstanceOfError(extraInfo[itemName], type, color, level++); originalError.push(str); stackTrace = stkTrc; delete extraInfo[itemName]; } } } if (Object.keys(extraInfo).length) { extraInfos.push((0, remove_circular_json_stringify_1.removeCircularJSONstringify)(extraInfo, 2) .replace(/(?:^\s*{(?:\n {2})?|}\s*$)/g, '') .replace(/\n {2}/g, '\n') .split('\n') .map(val => exports.C.dim(val)).join('\n') + '\n'); } return { str: (extraInfos.length > 1 ? extraInfos.join('') : '') + (originalError.length > 1 ? originalError.join('') + '\n' : ''), stackTrace }; } } /** * Call this at each steps of your progress and change the step value * @param {Number} step Number of "char" to output * @param {String} char Default: '.' * @param {String} msg String before char. Final output will be `${str}${char.repeat(step)}` */ function cliProgressBar(step, char = '.', msg = `\x1b[2mⓘ Waiting response`) { if (typeof process?.stdout?.clearLine === 'function') { process.stdout.cursorTo(0); process.stdout.write(`${msg}${char.repeat(step)}\x1b[0m`); // \x1b[0m == reset color } } exports.cliProgressBar = cliProgressBar; /** This allow an intuitive inline loading spinner with a check mark when loading as finished or a red cross for errors */ class cliLoadingSpinner { /** Please use it like spinner.start('myStuff') then spinner.end() * @param {String} type in: ['arrow', 'dots'] */ frameRate; animFrames; activeProcess; frameNb = 0; progressMessage = ''; interval = -1; constructor(type = 'dots', activeProcess = process) { const anims = { arrow: { interval: 120, frames: ['▹▹▹▹▹', '▸▹▹▹▹', '▹▸▹▹▹', '▹▹▸▹▹', '▹▹▹▸▹', '▹▹▹▹▸'] }, dots: { interval: 80, frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] } }; this.frameRate = anims[type].interval; this.animFrames = anims[type].frames; this.activeProcess = activeProcess; } start(msg) { this.frameNb = 0; this.progressMessage = msg; this.interval = setInterval(() => { this.activeProcess?.stdout?.cursorTo?.(0); // Move cursor to beginning of the current line const symbol = this.animFrames[this.frameNb++ % this.animFrames.length]; this.activeProcess.stdout.write(exports.C.primary(symbol) + ' ' + this.progressMessage); }, this.frameRate); } end(error = false) { clearInterval(this.interval); this.activeProcess?.stdout?.cursorTo?.(0); exports.C.log(error ? exports.C.red('✘ ' + this.progressMessage + '\n\n') : '\x1b[32m✓ ' + this.progressMessage + '\n\n'); this.progressMessage = ''; } error() { return this.end(true); } } exports.cliLoadingSpinner = cliLoadingSpinner; function dim(str = '') { return (0, config_1.configFn)()?.terminal?.noColor ? str : `\x1b[2m${str.toString().split('\n').join('\x1b[0m\n\x1b[2m')}\x1b[0m`; } exports.dim = dim; //# sourceMappingURL=logger-utils.js.map