0xweb
Version:
Contract package manager and other web3 tools
245 lines (209 loc) • 6.62 kB
text/typescript
import memd from 'memd';
import { $color } from './$color';
import { $date } from './$date';
import alot from 'alot';
import { $dependency } from './$dependency';
export enum ELogLevel {
INFO = 0,
WARN = 1,
ERROR = 2,
RESULT = 3,
}
interface ILoggerOptions {
time?: boolean
color?: boolean
level?: ELogLevel
}
class Logger {
private stdCalls = [];
private stdOnHold = false;
private stdQueue = [];
constructor(private options?: ILoggerOptions) {
this.options ??= {};
this.options.level ??= ELogLevel.INFO;
this.options.color ??= true;
this.options.time ??= true;
}
config (options: ILoggerOptions) {
for (let key in options) {
this.options[key] = options[key];
}
}
log(...args: any) {
if (this.options?.level > ELogLevel.INFO) {
return;
}
if (args.length === 1 && typeof args[0] !== 'string') {
console.dir(args[0], { depth: null });
return;
}
this.print(this.format(...args), { method: 'log' });
}
toast(str: string) {
if (this.options?.level > ELogLevel.INFO) {
return;
}
let row = this.colored([ str ]);
this.print(row, { method: 'log', isToast: true });
}
warn(...args: any) {
if (this.options?.level > ELogLevel.WARN) {
return;
}
this.print(this.format(...args), { method: 'warn' });
}
error(...args: any) {
this.print(this.format(...args), { method: 'error' });
}
result (...args: any) {
let row = this.colored(args);
this.print(row, { method: 'log' });
}
table(arr: (string | number | bigint)[][]) {
arr = arr.filter(x => x != null && x.length > 0);
if (arr.length === 0) {
// No rows
return;
}
let lengths = arr[0].map((_, i) => {
let size = alot(arr).max(x => {
if (x.length === 1) {
// If a row has only one column do not calculate column sizes and it will take the whole space
return 0;
}
let str = String(x[i]);
let lines = str.split('\n');
let max = alot(lines).max(x => x.length);
const LIMIT_COLUMNG_LENGTH = 100;
return Math.min(max, LIMIT_COLUMNG_LENGTH);
});
return size;
});
let lines = arr.map(row => {
let multiLines = row.map(x => String(x).split('\n'));
let multiLinesCount = alot(multiLines).max(x => x.length);
return alot
.fromRange(0, multiLinesCount)
.map(y => {
return row.map((_, i) => {
let x = multiLines[i][y];
let size = lengths[i];
let str = String(x ?? '').padEnd(size, ' ');
if (i % 2 === 1) {
str = `bold<${str}>`;
}
return str;
})
.join(' ');
})
.toArray()
.join('\n')
});
let row = this.colored([ lines.join('\n') ])
this.print(row, { method: 'log' });
}
/**
* Print log message not often than every 1 second
*/
.deco.throttle(1000)
throttled(...args: any) {
this.log(...args);
}
private print(row: (string | any)[], params: { method: 'log' | 'warn' | 'error', isToast?: boolean }) {
if (params?.isToast) {
if (StdToast.isLoaded !== true) {
this.stdOnHold = true;
StdToast.initialize().then(x => {
let arr = this.stdQueue;
this.stdOnHold = false;
this.stdQueue = [];
arr.map(([row, params]) => {
this.print(row, params);
});
});
}
}
if (this.stdOnHold) {
this.stdQueue.push([row, params]);
return;
}
if (this.stdCalls[0]?.isToast) {
// Last print is the toast, clear it
StdToast.clean();
}
console[params.method](...row);
this.stdCalls.unshift(params);
if (this.stdCalls.length > 100) {
this.stdCalls.splice(50);
}
}
private format(...args: any): (string | any)[] {
let row = this.colored(args);
if (this.options?.time) {
row.unshift($date.format(new Date(), 'HH:mm:ss'));
}
return row;
}
private colored(args: (string | any)[]) {
if (this.options?.color === false) {
return args;
}
for (let i = 0; i < args.length; i++) {
let x = args[i];
if (typeof x !== 'string') {
continue;
}
args[i] = $color(args[i]);
}
return args;
}
}
export let $logger = new Logger();
export function l(strings: TemplateStringsArray, ...values: any[]) {
let args = [];
for (let i = 0; i < strings.length; i++) {
args.push(strings[i]);
if (i < values.length) {
args.push(values[i]);
}
}
// join value types if should be colorized: l`Age: bold<${age}>`
for (let i = 1; i < args.length - 1; i++) {
let before = args[i - 1];
let value = args[i];
let after = args[i + 1];
if (typeof before !== 'string' || typeof after !== 'string') {
continue;
}
switch (typeof value) {
case 'number':
case 'string':
case 'boolean':
case 'undefined':
case 'bigint':
break;
default:
// skip colorizing all non-value types.
continue;
}
args[i - 1] = `${before}${value}${after}`;
args.splice(i, 2);
i--;
}
$logger.log(...args);
}
namespace StdToast {
let rl: typeof import('readline');
export let isLoaded = false;
export function clean() {
rl.clearLine(process.stdout, 0);
rl.cursorTo(process.stdout, 0, null);
rl.moveCursor(process.stdout, 0, -1);
rl.clearLine(process.stdout, 0);
}
export async function initialize() {
/** lazy */
rl = await $dependency.load('readline');
isLoaded = true;
}
}