declare global { interface String { format: (...args: Array) => string; formatFromArray: (args: any[]) => string; rawformat: (object: any) => string; } interface Math { sum: (...args: Array) => number; } } const defaultDict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; function randomInt(min: number, max: number) { const range = max - min + 1; const maxValid = Math.floor(4294967296 / range) * range; const array = new Uint32Array(1); let value: number; do { crypto.getRandomValues(array); value = array[0]; } while (value >= maxValid); return min + (value % range); } export function randomstring(digit = 32, dict = defaultDict) { let str = ''; for (let i = 1; i <= digit; i++) str += dict[randomInt(0, dict.length - 1)]; return str; } String.prototype.format ||= function formatStr(...args) { let result = this; if (args.length) { if (args.length === 1 && typeof args[0] === 'object') { const t = args[0]; for (const key in t) { if (!key.startsWith('_') && t[key] !== undefined) { const reg = new RegExp(`(\\{${key}\\})`, 'g'); result = result.replace(reg, t[key]); } } } else return this.formatFromArray(args); } return result as string; }; String.prototype.formatFromArray = function formatStr(args) { let result = this; for (let i = 0; i < args.length; i++) { if (args[i] !== undefined) { const reg = new RegExp(`(\\{)${i}(\\})`, 'g'); result = result.replace(reg, args[i]); } } return result as string; }; String.prototype.rawformat = function rawFormat(object) { const res = this.split('{@}'); return [res[0], object, res[1]].join(); }; export function diffArray(a, b) { if (a.length !== b.length) return true; a.sort(); b.sort(); for (const i in a) { if (a[i] !== b[i]) return true; } return false; } export function formatDate(date: Date, fmt = '%Y-%m-%d %H:%M:%S') { let m = (date.getMonth() + 1).toString(); if (m.length < 2) m = `0${m}`; let d = date.getDate().toString(); if (d.length < 2) d = `0${d}`; let H = date.getHours().toString(); if (H.length < 2) H = `0${H}`; let M = date.getMinutes().toString(); if (M.length < 2) M = `0${M}`; let S = date.getSeconds().toString(); if (S.length < 2) S = `0${S}`; return fmt .replace('%Y', date.getFullYear().toString()) .replace('%m', m) .replace('%d', d) .replace('%H', H) .replace('%M', M) .replace('%S', S); } Math.sum = function sum(...args) { let s = 0; for (const i of args) { if (i instanceof Array) { for (const j of i) { s += j; } } else s += i; } return s; }; // TODO: remove these // @ts-ignore Set.isSuperset = function isSuperset(set, subset) { for (const elem of subset) { if (!set.has(elem)) return false; } return true; }; // @ts-ignore Set.union = function Union(setA: Set | Array, setB: Set | Array) { const union = new Set(setA); for (const elem of setB) union.add(elem); return union; }; // @ts-ignore Set.intersection = function Intersection(A: Set | Array = [], B: Set | Array = []) { const intersection = new Set(); if (A instanceof Array) A = new Set(A); if (B instanceof Array) B = new Set(B); for (const elem of B) if (A.has(elem)) intersection.add(elem); return intersection; }; export function sleep(timeout: number) { return new Promise((resolve) => { setTimeout(resolve, timeout, true); }); } function deepen(modifyString: (source: string) => string) { function modifyObject(source: T): T { if (typeof source !== 'object' || !source) return source; if (Array.isArray(source)) return source.map(modifyObject) as any; const result = {} as any; for (const key in source) { result[modifyString(key)] = modifyObject(source[key]); } return result as T; } return function t(source: T): T { if (typeof source === 'string') return modifyString(source) as any; return modifyObject(source); }; } export function noop() { } export const camelCase = deepen((source) => source.replace(/[_-][a-z]/g, (str) => str.slice(1).toUpperCase())); export const paramCase = deepen((source) => source.replace(/_/g, '-').replace(/(? `-${str.toLowerCase()}`)); export const snakeCase = deepen((source) => source.replace(/-/g, '_').replace(/(? `_${str.toLowerCase()}`)); const TIME_RE = /^([0-9]+(?:\.[0-9]*)?)([mu]?)s?$/i; const TIME_UNITS = { '': 1000, m: 1, u: 0.001 } as Record; const MEMORY_RE = /^([0-9]+(?:\.[0-9]*)?)([kmg])b?$/i; const MEMORY_UNITS = { k: 1 / 1024, m: 1, g: 1024 } as Record; export function parseTimeMS(str: string | number, throwOnError = true) { if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str; const match = TIME_RE.exec(str); if (!match && throwOnError) throw new Error(`${str} error parsing time`); if (!match) return 1000; return Math.floor(Number.parseFloat(match[1]) * TIME_UNITS[match[2].toLowerCase()]); } export function parseMemoryMB(str: string | number, throwOnError = true) { if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str; const match = MEMORY_RE.exec(str); if (!match && throwOnError) throw new Error(`${str} error parsing memory`); if (!match) return 256; return Math.ceil(Number.parseFloat(match[1]) * MEMORY_UNITS[match[2].toLowerCase()]); } function _digit2(number: number) { return number < 10 ? `0${number}` : number.toString(); } export function formatSeconds(_seconds: string | number = '0', showSeconds = true) { const seconds = +_seconds; let res = '{0}:{1}'.format( showSeconds ? _digit2(Math.floor(seconds / 3600)) : Math.floor(seconds / 3600), _digit2(Math.floor((seconds % 3600) / 60)), ); if (showSeconds) res += `:${_digit2(seconds % 60)}`; return res; } export function size(s: number, base = 1) { s *= base; const unit = 1024; const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; for (const unitName of unitNames) { if (s < unit) return '{0} {1}'.format(Math.round(s * 10) / 10, unitName); s /= unit; } return `${Math.round(s * unit)} ${unitNames.at(-1)}`; } export function randomPick(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } export type StringKeys = { [K in keyof O]: string extends O[K] ? K : never }[keyof O]; const fSortR = /\D+|\d+/g; export function sortFiles(files: string[]): string[]; export function sortFiles(files: T[], key?: '_id'): T[]; export function sortFiles>(files: T[], key: StringKeys): T[]; export function sortFiles(files: Record[] | string[], key = '_id') { if (!files?.length) return []; const isString = typeof files[0] === 'string'; const result = files .map((i: any) => (isString ? { name: i, _weights: i.match(fSortR) } : { ...i, _weights: (i[key] || i.name).match(fSortR) })) .sort((a, b) => { let pos = 0; const weightsA = a._weights; const weightsB = b._weights; let weightA = weightsA[pos]; let weightB = weightsB[pos]; while (weightA && weightB) { const v = weightA - weightB; if (!Number.isNaN(v) && v !== 0) return v; if (weightA !== weightB) return weightA > weightB ? 1 : -1; pos += 1; weightA = weightsA[pos]; weightB = weightsB[pos]; } return weightA ? 1 : -1; }); return result.map((x) => (isString ? x.name : (delete x._weights && x))); } export const getAlphabeticId = (() => { // A...Z, AA...AZ, BA...BZ, ... const f: (a: number) => string = (a: number) => (a < 0 ? '' : f(a / 26 - 1) + String.fromCharCode((a % 26) + 65)); const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const cache = alphabet.split(''); for (const ch of alphabet) cache.push(...alphabet.split('').map((c) => ch + c)); return (i: number) => cache[i] || (i < 0 ? '?' : f(i)); })();