import {dotNotation, flattenObj} from './objects.ts'; /** * String of all letters */ const LETTER_LIST = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; /** * String of all numbers */ const NUMBER_LIST = '0123456789'; /** * String of all symbols */ const SYMBOL_LIST = '~`!@#$%^&*()_-+={[}]|\\:;"\'<,>.?/'; /** * String of all letters, numbers & symbols */ const CHAR_LIST = LETTER_LIST + NUMBER_LIST + SYMBOL_LIST; /** * Convert number of bytes into a human-readable size * * @param {number} bytes Number of bytes * @param {number} decimals Decimal places to preserve * @return {string} Formated size */ export function formatBytes(bytes: number, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i]; } /** * Extract numbers from a string & create a formated phone number: +1 (123) 456-7890 * * @param {string} number String that will be parsed for numbers * @return {string} Formated phone number */ export function formatPhoneNumber(number: string) { const parts = /(\+?1)?.*?(\d{3}).*?(\d{3}).*?(\d{4})/g.exec(number); if(!parts) throw new Error(`Number cannot be parsed: ${number}`); return `${parts[1] ?? ''} (${parts[2]}) ${parts[3]}-${parts[4]}`.trim(); } /** * Insert a string into another string at a given position * * @example * ```js * console.log(insertAt('Hello world!', ' glorious', 5); * // Output: Hello glorious world! * ``` * * @param {string} target - Parent string you want to modify * @param {string} str - Value that will be injected to parent * @param {number} index - Position to inject string at * @returns {string} - New string */ export function insertAt(target: string, str: string, index: number): String { return `${target.slice(0, index)}${str}${target.slice(index + 1)}`; } /** * Add padding to string * * @example * ```js * const now = new Date(); * const padded = now.getHours() + ':' + pad(now.getMinutes(), 2, '0'); * console.log(padded); // Output: "2:05" * ``` * * @param text Text that will be padded * @param {number} length Target length * @param {string} char Character to use as padding, defaults to space * @param {boolean} start Will pad start of text if true, or the end if false * @return {string} Padded string * @deprecated Please use `String.padStart` & `String.padEnd` */ export function pad(text: any, length: number, char: string = ' ', start = true) { if(start) return text.toString().padStart(length, char); return text.toString().padEnd(length, char); } /** * Generate a random hexadecimal value * * @param {number} length Number of hexadecimal place values * @return {string} Hexadecimal number as a string */ export function randomHex(length: number) { return Array(length).fill(null).map(() => Math.round(Math.random() * 0xF).toString(16)).join(''); } /** * Generate a string of random characters. * * @example * ```ts * const random = randomString(); * const randomByte = randomString(8, "01") * ``` * * @param {number} length - length of generated string * @param {string} pool - character pool to generate string from * @return {string} generated string */ export function randomString(length: number, pool: string = CHAR_LIST): string { return Array(length).fill(null).map(() => { const n = ~~(Math.random() * pool.length); return pool[n]; }).join(''); } /** * Generate a random string with fine control over letters, numbers & symbols * * @example * ```ts * const randomLetter = randomString(1, true); * const randomChar = randomString(1, true, true, true); * ``` * * @param {number} length - length of generated string * @param {boolean} letters - Add letters to pool * @param {boolean} numbers - Add numbers to pool * @param {boolean} symbols - Add symbols to pool * @return {string} generated string */ export function randomStringBuilder(length: number, letters = false, numbers = false, symbols = false): string { if(!letters && !numbers && !symbols) throw new Error('Must enable at least one: letters, numbers, symbols'); return Array(length).fill(null).map(() => { let c; do { const type = ~~(Math.random() * 3); if(letters && type == 0) { c = LETTER_LIST[~~(Math.random() * LETTER_LIST.length)]; } else if(numbers && type == 1) { c = NUMBER_LIST[~~(Math.random() * NUMBER_LIST.length)]; } else if(symbols && type == 2) { c = SYMBOL_LIST[~~(Math.random() * SYMBOL_LIST.length)]; } } while(!c); return c; }).join(''); } /** * Find all substrings that match a given pattern. * * Roughly based on `String.prototype.matchAll`. * * @param {string} value - String to search. * @param {RegExp | string} regex - Regular expression to match. * @return {RegExpExecArray[]} Found matches. */ export function matchAll(value: string, regex: RegExp | string): RegExpExecArray[] { if(typeof regex === 'string') { regex = new RegExp(regex, 'g'); } // https://stackoverflow.com/a/60290199 if(!regex.global) { throw new TypeError('Regular expression must be global.'); } let ret: RegExpExecArray[] = []; let match: RegExpExecArray | null; while((match = regex.exec(value)) !== null) { ret.push(match); } return ret; } /** Parts of a URL */ export type ParsedUrl = { protocol?: string, subdomain?: string, domain: string, host: string, port?: number, path?: string, query?: {[name: string]: string} fragment?: string } /** * Break a URL string into its parts for easy parsing * * @param {string} url URL string that will be parsed * @returns {RegExpExecArray} Parts of URL */ export function parseUrl(url: string): ParsedUrl { const processed = new RegExp( '(?:(?[\\w\\d]+)\\:\\/\\/)?(?:(?.+)\\@)?(?(?[^:\\/\\?#@\\n]+)(?:\\:(?\\d*))?)(?\\/.*?)?(?:\\?(?.*?))?(?:#(?.*?))?$', 'gm').exec(url); const groups: ParsedUrl = processed?.groups ?? {}; const domains = groups.domain.split('.'); if(groups['port'] != null) groups.port = Number(groups.port); if(domains.length > 2) { groups.domain = domains.splice(-2, 2).join('.'); groups.subdomain = domains.join('.'); } if(groups.query) { const split = (groups.query).split('&'), query: any = {}; split.forEach((q: any) => { const [key, val] = q.split('='); query[key] = val; }); groups.query = query; } return groups; } /** * Create MD5 hash using native javascript * * @param d String to hash * @returns {string} Hashed string */ export function md5(d: string) { var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()}; function M(d:any){for(var _,m="0123456789ABCDEF",f="",r=0;r>>4&15)+m.charAt(15&_);return f} function X(d:any){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<>5]>>>m%32&255);return _} function Y(d:any,_:any){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n>16)+(_>>16)+(m>>16)<<16|65535&m} function bit_rol(d:any,_:any){return d<<_|d>>>32-_ } /** * Check if email is valid * * @param {string} email - Target * @returns {boolean} - Follows format */ export function validateEmail(email: string) { return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(email); }