🥂 Initial commit
This commit is contained in:
162
tools/utils/funcs.js
Normal file
162
tools/utils/funcs.js
Normal file
@ -0,0 +1,162 @@
|
||||
import {
|
||||
existsSync,
|
||||
lstatSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
writeFileSync,
|
||||
} from 'fs';
|
||||
import { basename, join } from 'path';
|
||||
|
||||
export function readFiles(path, extensions) {
|
||||
return readdirSync(path).filter((file) =>
|
||||
extensions.some((ext) => file.endsWith(ext))
|
||||
);
|
||||
}
|
||||
|
||||
function copyFileSync(source, target) {
|
||||
var targetFile = target;
|
||||
|
||||
// If target is a directory, a new file with the same name will be created
|
||||
if (existsSync(target)) {
|
||||
if (lstatSync(target).isDirectory()) {
|
||||
targetFile = join(target, basename(source));
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(targetFile, readFileSync(source));
|
||||
}
|
||||
|
||||
export function copyFolderRecursiveSync(source, target) {
|
||||
var files = [];
|
||||
|
||||
var targetFolder = join(target, basename(source));
|
||||
if (!existsSync(targetFolder)) {
|
||||
mkdirSync(targetFolder);
|
||||
}
|
||||
|
||||
// Copy
|
||||
if (lstatSync(source).isDirectory()) {
|
||||
files = readdirSync(source);
|
||||
files.forEach(function (file) {
|
||||
var curSource = join(source, file);
|
||||
if (lstatSync(curSource).isDirectory()) {
|
||||
copyFolderRecursiveSync(curSource, targetFolder);
|
||||
} else {
|
||||
copyFileSync(curSource, targetFolder);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/bjoerge/debounce-promise/blob/master/index.js
|
||||
export function debounce(fn, wait = 0, options = {}) {
|
||||
let lastCallAt;
|
||||
let deferred;
|
||||
let timer;
|
||||
let pendingArgs = [];
|
||||
return function debounced(...args) {
|
||||
const currentWait = getWait(wait);
|
||||
const currentTime = new Date().getTime();
|
||||
|
||||
const isCold = !lastCallAt || currentTime - lastCallAt > currentWait;
|
||||
|
||||
lastCallAt = currentTime;
|
||||
|
||||
if (isCold && options.leading) {
|
||||
return options.accumulate
|
||||
? Promise.resolve(fn.call(this, [args])).then((result) => result[0])
|
||||
: Promise.resolve(fn.call(this, ...args));
|
||||
}
|
||||
|
||||
if (deferred) {
|
||||
clearTimeout(timer);
|
||||
} else {
|
||||
deferred = defer();
|
||||
}
|
||||
|
||||
pendingArgs.push(args);
|
||||
timer = setTimeout(flush.bind(this), currentWait);
|
||||
|
||||
if (options.accumulate) {
|
||||
const argsIndex = pendingArgs.length - 1;
|
||||
return deferred.promise.then((results) => results[argsIndex]);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function flush() {
|
||||
const thisDeferred = deferred;
|
||||
clearTimeout(timer);
|
||||
|
||||
Promise.resolve(
|
||||
options.accumulate
|
||||
? fn.call(this, pendingArgs)
|
||||
: fn.apply(this, pendingArgs[pendingArgs.length - 1])
|
||||
).then(thisDeferred.resolve, thisDeferred.reject);
|
||||
|
||||
pendingArgs = [];
|
||||
deferred = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getWait(wait) {
|
||||
return typeof wait === 'function' ? wait() : wait;
|
||||
}
|
||||
|
||||
function defer() {
|
||||
const deferred = {};
|
||||
deferred.promise = new Promise((resolve, reject) => {
|
||||
deferred.resolve = resolve;
|
||||
deferred.reject = reject;
|
||||
});
|
||||
return deferred;
|
||||
}
|
||||
|
||||
export async function sequence(tasks) {
|
||||
const results = [];
|
||||
for (const task of tasks) {
|
||||
results.push(await task());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
async function tasksRunner(tasks, abort) {
|
||||
let result = null;
|
||||
for (const task of tasks) {
|
||||
if (abort.signal.aborted) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = await task(result);
|
||||
}
|
||||
}
|
||||
|
||||
// each task should return a promise
|
||||
// each task takes the result of the previous task as an argument
|
||||
// the output of the last task is the output of the sequence
|
||||
export async function sequenceStream(tasks) {
|
||||
const abort = new AbortController();
|
||||
|
||||
abort.signal.addEventListener('abort', () => {
|
||||
console.log('sequenceStream aborted');
|
||||
});
|
||||
|
||||
return [
|
||||
tasksRunner(tasks, abort),
|
||||
abort,
|
||||
]
|
||||
}
|
||||
|
||||
export const getArg = (flag, def) => {
|
||||
const args = process.argv.slice(2);
|
||||
const flagIndex = args.findIndex(arg => arg === flag);
|
||||
|
||||
if (flagIndex !== -1 && flagIndex + 1 < args.length) {
|
||||
return args[flagIndex + 1];
|
||||
}
|
||||
|
||||
return def || null;
|
||||
};
|
105
tools/utils/logger.js
Normal file
105
tools/utils/logger.js
Normal file
@ -0,0 +1,105 @@
|
||||
const LOG_LEVEL_MAP = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
};
|
||||
|
||||
const ANSI_COLORS = {
|
||||
reset: '\x1b[0m',
|
||||
black: '\x1b[30m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m',
|
||||
gray: '\x1b[90m',
|
||||
brightRed: '\x1b[91m',
|
||||
brightGreen: '\x1b[92m',
|
||||
brightYellow: '\x1b[93m',
|
||||
brightBlue: '\x1b[94m',
|
||||
brightMagenta: '\x1b[95m',
|
||||
brightCyan: '\x1b[96m',
|
||||
pink: '\x1b[38;2;255;182;193m'
|
||||
};
|
||||
|
||||
export class Logger {
|
||||
/**
|
||||
* @param {string} ctx
|
||||
* @param {'debug'|'info'|'warn'|'error'} log_level - default: 'debug'
|
||||
* @param {'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'gray'|'brightRed'|
|
||||
* 'brightGreen'|'brightYellow'|'brightBlue'|'brightMagenta'|'brightCyan'|'pink'} color - default: 'magenta'
|
||||
*/
|
||||
constructor(ctx, log_level, color = 'magenta') {
|
||||
this.ctx = ctx;
|
||||
this.log_level = LOG_LEVEL_MAP[log_level || 'debug'];
|
||||
this.color = ANSI_COLORS[color] || ANSI_COLORS.reset;
|
||||
|
||||
if (this.log_level === undefined) {
|
||||
throw new Error(`Invalid log level: ${log_level}`);
|
||||
}
|
||||
}
|
||||
|
||||
debug(...args) {
|
||||
if (!this.#canLog('debug')) return;
|
||||
this.log('DEBUG', false, ...args);
|
||||
}
|
||||
|
||||
info(...args) {
|
||||
if (!this.#canLog('info')) return;
|
||||
this.log('INFO', false, ...args);
|
||||
}
|
||||
|
||||
warn(...args) {
|
||||
if (!this.#canLog('warn')) return;
|
||||
this.log('WARN', false, ...args);
|
||||
|
||||
}
|
||||
|
||||
error(...args) {
|
||||
if (!this.#canLog('error')) return;
|
||||
this.log('ERROR', false, ...args);
|
||||
}
|
||||
|
||||
log(level, simple, ...args) {
|
||||
if (simple) {
|
||||
args = [args[0]];
|
||||
}
|
||||
|
||||
if (level === 'ERROR') {
|
||||
console.error(
|
||||
`${this.color}[${level}] [${this.ctx}]${ANSI_COLORS.reset}`,
|
||||
...args
|
||||
);
|
||||
} else {
|
||||
console.log('🍵', `${this.color}[${this.ctx}]${ANSI_COLORS.reset}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
simpleDebug(...args) {
|
||||
if (!this.#canLog('debug')) return;
|
||||
this.log('DEBUG', true, ...args);
|
||||
}
|
||||
|
||||
simpleInfo(...args) {
|
||||
if (!this.#canLog('info')) return;
|
||||
this.log('INFO', true, ...args);
|
||||
}
|
||||
|
||||
simpleWarn(...args) {
|
||||
if (!this.#canLog('warn')) return;
|
||||
this.log('WARN', true, ...args);
|
||||
|
||||
}
|
||||
|
||||
simpleError(...args) {
|
||||
if (!this.#canLog('error')) return;
|
||||
this.log('ERROR', true, ...args);
|
||||
}
|
||||
|
||||
#canLog(level) {
|
||||
return this.log_level <= LOG_LEVEL_MAP[level];
|
||||
}
|
||||
}
|
67
tools/utils/task-debouncer.js
Normal file
67
tools/utils/task-debouncer.js
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* #### TaskDebouncer
|
||||
*
|
||||
* Executes a task after a certain delay, but cancels the execution if
|
||||
* a new task is sent before the delay expires. Also, if a task is
|
||||
* already being executed, the new task is queued and executed after
|
||||
* the current one finishes. It will only execute the task that was
|
||||
* sent last.
|
||||
*/
|
||||
export class TaskDebouncer {
|
||||
constructor(debounceDelay) {
|
||||
this.debounceDelay = debounceDelay;
|
||||
this.queued = undefined;
|
||||
this.isProcessing = false;
|
||||
this.timerId = null;
|
||||
}
|
||||
|
||||
#clearQueue() {
|
||||
this.queued = undefined;
|
||||
}
|
||||
|
||||
#enqueue(executor, args) {
|
||||
this.queued = { executor, args };
|
||||
this.#processQueue();
|
||||
}
|
||||
|
||||
#setProcessing(value) {
|
||||
this.isProcessing = value;
|
||||
}
|
||||
|
||||
async #processQueue() {
|
||||
if (this.isProcessing || !this.queued) {
|
||||
return;
|
||||
}
|
||||
const { executor, args } = this.queued;
|
||||
this.#clearQueue();
|
||||
|
||||
// execute the task
|
||||
this.#setProcessing(true);
|
||||
await executor(...args);
|
||||
this.#setProcessing(false);
|
||||
|
||||
// continue with the next task
|
||||
this.#continue();
|
||||
}
|
||||
|
||||
#continue() {
|
||||
if (this.queued) {
|
||||
this.#processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a task to the queue. If a task is already being executed,
|
||||
* the new task is queued and executed after the current one finishes.
|
||||
* It will only execute the task if no other task is sent before the
|
||||
* delay expires or before the current task finishes.
|
||||
*
|
||||
* IOW, it will only execute the task that was sent last.
|
||||
*/
|
||||
add(executor, ...args) {
|
||||
clearTimeout(this.timerId);
|
||||
this.timerId = setTimeout(() => {
|
||||
this.#enqueue(executor, args);
|
||||
}, this.debounceDelay);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user