Skip to content

Instantly share code, notes, and snippets.

@zgover
Last active June 10, 2021 14:44
Show Gist options
  • Save zgover/4b83a8e95360486c4cf1e8a75c5e543b to your computer and use it in GitHub Desktop.
Save zgover/4b83a8e95360486c4cf1e8a75c5e543b to your computer and use it in GitHub Desktop.
Quick TypeScript Guard and Helper Utility Functions
/**
* Is literal type 'boolean'
*
* @export
* @param {*} val
* @returns {val is boolean}
*/
export function _isBool(val: unknown): val is boolean {
return typeof val === 'boolean'
}
/**
* Is literal type 'bigint'
*
* @export
* @param {*} val
* @returns {val is bigint}
*/
export function _isBig(val: unknown): val is bigint {
return typeof val === 'bigint'
}
/**
* Is literal type 'number'
*
* @export
* @param {*} val
* @returns {val is number}
*/
export function _isNumT(val: unknown): val is number {
return typeof val === 'number'
}
/**
* Is value a number, allows even if string (e.g. "6" vs 6)
*
* @export
* @param {*} val
* @param {boolean} [noStr]
* @returns {val is number}
*/
export function _isNum(val: unknown, noStr?: boolean): val is number {
return noStr && _isStr(val) ? false : !isNaN(Number(val))
}
/**
* Is literal type symbol
*
* @export
* @param {*} val
* @returns {val is symbol}
*/
export function _isSym(val: unknown): val is symbol {
return typeof val === 'symbol'
}
/**
* Is literal type function
*
* @export
* @param {*} val
* @returns {val is Function}
*/
export function _isFn(val: unknown): val is Func {
return typeof val === 'function'
}
export interface Func {
(...args: unknown[]): unknown
}
/**
* Shortcut for Array.isArray
*
* @export
* @param {*} val
* @returns {val is any[]}
*/
export function _isArr(val: unknown): val is unknown[] {
return Array.isArray(val)
}
/**
* Is an empty array
*
* @export
* @param {*} val
* @returns {val is []}
*/
export function _isArrEmpty(val: unknown): val is [] {
return _isArr(val) && !val.length
}
/**
* Is literal type object, this could be any Object type
* such as a function, class, null, {}, array etc
*
* @export
* @param {*} val
* @returns {val is object}
*/
export function _isObjT(val: unknown): val is Record<string | number, unknown> {
return typeof val === 'object'
}
/**
* Is actually a dictionary object (e.g. key:value), but not
* an array or null
*
* @export
* @param {*} val
* @returns {val is Object}
*/
export function _isObj(val: unknown): val is Record<string, unknown> {
return !_isNull(val) && _isObjT(val) && !_isArr(val)
}
/**
* Is literal type null... null is actually 'object' type literal
*
* @export
* @param {*} val
* @returns {val is null}
*/
export function _isNull(val: unknown): val is null {
return _isObjT(val) && typeof val === null
}
/**
* Is literal type undefined
*
* @export
* @param {*} val
* @returns {val is undefined}
*/
export function _isUndef(val: unknown): val is undefined {
return typeof val === 'undefined'
}
/**
* Is literal type null or undefined
*
* @export
* @param {*} val
* @returns {(val is null | undefined)}
*/
export function _isUndOrNull(val: unknown): val is null | undefined {
return _isNull(val) || _isUndef(val)
}
/**
* Is literal type string
*
* @export
* @param {*} val
* @returns {val is string}
*/
export function _isStr(val: unknown): val is string {
return typeof val === 'string'
}
/**
* Is type empty string (e.g. "" vs "foo")
*
* @export
* @param {*} val
* @returns {val is ''}
*/
export function _isStrEmpty(val: unknown): val is '' {
return _isStr(val) && !val.length
}
/**
* Is type empty array or empty string
*
* @export
* @param {*} val
* @returns {(val is '' | [])}
*/
export function _isEmptyStrOrArr(val: unknown): val is '' | [] {
return _isStrEmpty(val) || _isArrEmpty(val)
}
/**
* Is type Buffer
*
* @export
* @param {*} val
* @returns {val is Buffer}
*/
export function _isBuff(val: unknown): val is Buffer {
return (
_isObjT(val) &&
_isObjT(val.constructor) &&
'isBuffer' in val.constructor &&
_isFn((val.constructor as any).isBuffer) &&
(val.constructor as any).isBuffer(val)
)
}
/**
* Is literal type of primitive type
*
* @export
* @param {*} val
* @returns {val is Primitive}
*/
export function _isPrim(val: unknown): val is Primitive {
return Boolean(
_isSym(val) ||
_isBig(val) ||
_isNumT(val) ||
_isObjT(val) ||
_isBool(val) ||
_isStr(val) ||
_isFn(val) ||
_isUndef(val)
)
}
export type Primitive = symbol | bigint | boolean | number | string | undefined
export type PrimitiveBasic = number | string | boolean
/**
* Checks if the parameter has length greater than 0 or second parameter
* @export
* @template T
* @param {(Iterable<T> | ArrayLike<T>)} val
* @param {number} [of] evaluation number if you want to check it against
* @param {('>'|'<'|'=')} [operator]
* @return {*} {boolean}
*/
export function _ln<T>(val: Iterable<T> | ArrayLike<T> | number, of?: number, operator?: '>' | '<' | '='): boolean {
if (val) {
const ln = _isNum(val) ? val : val['length']
const base = _isNum(of) ? of : 0
switch (operator) {
case '<':
return ln < base
case '=':
return ln === base
default:
return ln > base
}
}
return false
}
/**
* Verify the value has a length or value greater than 0 by default
*
* TODO: Document better... And refactor/cleanup.. Maybe just do away with
* the combination and separate out all, probably best
*
* Options:
* (equalTo) - val must equal to it otherwise false
* (lessThan) - val must be less than then it
* (moreThan) - val must be greater than it
* (lessThan & moreThan) - val must be between the two
* (also) - provide an additional validator
* (and) - provide any boolean value that may require true
*
* @export
* @template T
* @template U
* @param {T} val
* @param {HasLenOpt<U>} opts
* @returns {boolean}
*/
export function hasLn<T extends Iterable<U> | ArrayLike<U> | number, U>(val: T, opts?: HasLenOpt<U>): boolean
export function hasLn<T extends Iterable<U> | ArrayLike<U> | number, U>(val: T, equalTo: number): boolean
export function hasLn<T extends Iterable<U> | ArrayLike<U> | number, U>(val: T, opts: any): boolean {
const _opts = !opts || _isObj(opts) ? { ...opts } : { equalTo: opts }
const { equalTo, lessThan, moreThan, and, also } = _opts ?? {}
const v = _isNum(val) ? Number(val) : ((val as unknown) as Iterable<T> | ArrayLike<T>)
const len: number = _isNum(v) ? v : (v as any).length
const e = equalTo
const l = lessThan
const m = moreThan
const et = _isNum(e)
const lt = _isNum(l)
const mt = _isNum(m)
const noOpt = !et && !lt && !mt
const chkFunc = (firstCheck: boolean): boolean => {
if (_isFn(also)) {
return Boolean(and ? firstCheck && also(len, v) : firstCheck || also(len, v))
}
// !opts && console.log('has len', val, len, _isNumPos(len))
return firstCheck
}
const chkOpt = (equal: boolean, less: boolean, more: boolean, chk: boolean): boolean => {
return et === equal && lt === less && mt === more && chkFunc(chk)
}
return Boolean(
noOpt
? chkFunc(_isNumPos(len))
: chkOpt(true, false, false, len === e) ||
chkOpt(false, true, false, len < l) ||
chkOpt(false, false, true, len > m) ||
chkOpt(false, true, true, and ? len < l && len > m : len < l || len > m) ||
chkOpt(true, true, false, and ? len === e && len < l : len === e || len < l) ||
chkOpt(true, false, true, and ? len === e && len > m : len === e || len > m) ||
chkOpt(true, true, true, and ? len === e && len < l && len > m : len === e || len < l || len > m)
)
}
export type HasLenOpt<U> = {
equalTo?: number | undefined
lessThan?: number | undefined
moreThan?: number | undefined
and?: true | undefined
also?: ((length: number, value?: U) => boolean) | undefined
}
/**
* Checks if the value is a negative number
*
* @export
* @param {*} val
* @returns {(val is number & boolean)}
*/
export function _isNumNeg(val: unknown): val is number & boolean {
return _isNum(val) && Number(val) < 0
}
/**
* Checks if the value is a positive number
*
* @export
* @param {*} val
* @returns {(val is number & boolean)}
*/
export function _isNumPos(val: unknown): val is number & boolean {
return _isNum(val) && Number(val) > 0
}
/**
* Checks if the value is a number equal to zero(0)
*
* @export
* @param {*} val
* @returns {val is 0}
*/
export function _isNumZero(val: unknown): val is 0 {
return _isNum(val) && Number(val) === 0
}
/**
* Check if the value is same as one of the values within the array
*
* @export
* @template U
* @param {*} val
* @param {Array<U>} possible
* @returns {val is U}
*/
export function isOneOf<U>(val: unknown, possible: Array<U>): val is U {
return possible.some((i) => val === i)
}
import { _isArr, _isFn, _isNum, _isObj, _isStr, _isUndef, hasLn } from './guards'
export function tools(): string {
return 'tools'
}
/**
* Shortcut for retrieving the length property,
* defaults to zero (0) if the property does not exist
*
* @export
* @template T
* @param {(Iterable<T> | ArrayLike<T>)} val
* @returns {number}
*/
export function ln<T>(val: Iterable<T> | ArrayLike<T>): number {
if (val) {
return val['length'] ?? 0
}
return 0
}
/**
* Shortcut for String(...)
*
* @export
* @param {*} val
* @returns {string}
*/
export function s(...val: Parameters<typeof String>): string {
return String(val)
}
/**
* Shortcut for Boolean(...)
*
* @export
* @param {*} val
* @returns {boolean}
*/
export function b(...val: Parameters<typeof Boolean>): boolean {
return Boolean(...val)
}
/**
* Shortcut for JSON.stringify(...)
*
* @export
* @param args
* @return {string}
*/
export function JSONs(...args: Parameters<typeof JSON.stringify>): ReturnType<typeof JSON.stringify> {
return JSON.stringify(...args)
}
/**
* Shortcut for JSON.parse(...)
*
* @export
* @param args
* @return {any}
*/
export function JSONp(...args: Parameters<typeof JSON.parse>): ReturnType<typeof JSON.parse> {
return JSON.parse(...args)
}
/**
* Sort iterable by property or index returned from callbackFn
* @export`
* @param {T[]} target
* @param {(item: T) => (string | number)} callbackFn
* @param thisArg
* @returns {T[]}
*/
export function sortBy<T>(target: T[], callbackFn: ((item: T, target: T[]) => string | number), thisArg?: any) {
return target.slice().sort((a, b) => {
const propA = callbackFn.call(thisArg, a, target) // callbackFn(a)
const propB = callbackFn.call(thisArg, b, target) // prop(b)
if (propA < propB) return -1
if (propA > propB) return 1
return 0
})
}
/**
* Safe array, will always return an array
*
* @export
* @template T
* @param {T} val
* @param {any[]} [or]
* @returns {any[]}
*/
export function safeArray<T>(val: T, or?: any[]): any[] {
return _isArr(val) ? val : _isArr(or) ? or : []
}
/**
* Safe object/{} will always return an object
*
* @export
* @template T
* @param {T} val
* @param {Record<string, unknown>} [or]
* @returns {Record<string, unknown>}
*/
export function safeObj<T>(val: T, or?: Record<string, unknown>): Record<string, unknown> {
return _isObj(val) ? val : _isObj(or) ? or : {}
}
/**
* Get safe property
*
* @see getDeepProperty
* @see setDeepProperty
*
* @export
* @template T
* @template K
* @param {T} obj
* @param {K} key
* @returns {T[K]}
*/
export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] | undefined {
if (!_isObj(obj)) {
return undefined
}
return obj[key]
}
/**
* Get a nested property within an object literal
*
* EXAMPLE:
* const obj = {'a': {'prop': {'that': 'exists'}}};
* getNestedProperty(obj, 'a.very.deep.prop', 'value')
*
* @see getProperty
* @see setDeepProperty
*
* @export
* @template T
* @param {T} obj
* @param {string} path
* @param {string} [separator='.']
* @returns {*}
*/
export function getDeepProperty<T>(obj: T, path: string, separator = '.'): any {
const keys = path.split(separator)
const lastKey = keys.pop()
const lastObj = keys.reduce((v, k) => (v[k] = v[k] ?? {}), obj)
return lastObj[lastKey]
}
/**
* Set a nested property within an object literal
*
* EXAMPLE:
* const obj = {'a': {'prop': {'that': 'exists'}}};
* setNestedProperty(obj, 'a.very.deep.prop', 'value')
*
* @see getProperty
* @see getDeepProperty
*
* @export
* @template T
* @param {T} obj
* @param {string} path
* @param {*} val
* @param {string} [separator='.']
*/
export function setDeepProperty<T>(obj: T, path: string, val: any, separator = '.'): T {
const keys = path.split(separator)
const lastKey = keys.pop()
const lastObj = keys.reduce((v, k) => (v[k] = v[k] ?? {}), obj)
lastObj[lastKey] = val
return obj
}
/**
* Sort an array of object by a specified property path, optional second sorter
*
* @export
* @template T
* @param {T[]} items
* @param {string} firstPath
* @param {string} [secondPath]
* @returns {T[]}
*
* @deprecated {@link sortBy}
* @see {@link sortBy}
* TODO: REMOVE AMBIGUOUS METHOD TO REPLACE WITH `sortBy`
*/
export function sortByProperty<T>(items: T[], firstPath: string, secondPath?: string): T[] {
const lgr = (a, b, p) => getDeepProperty(a, p) > getDeepProperty(b, p)
const eq = (a, b, p) => getDeepProperty(a, p) === getDeepProperty(b, p)
return items.sort((a, b) =>
lgr(a, b, firstPath) ? 1 : secondPath && eq(a, b, firstPath) ? (lgr(a, b, secondPath) ? 1 : -1) : -1,
)
}
/**
* Copy an object literal
*
* @export
* @param {Record<string, unknown>} obj
* @returns
*/
export function copyObj(obj: Record<string, unknown>) {
return copy(obj)
}
/**
* Copy a json object by stringify and then parsing
*
* @export
* @template T
* @param {T} json
* @returns {T}
*/
export function copyJson<T>(json: T): T {
return JSON.parse(JSON.stringify(json))
}
/**
*
*
* @export
* @param {[any, any][]} arr
* @returns
*/
export function arrayToObjectLiteral(arr: [any, any][]) {
return Object.assign({}, ...arr.map(([key, val]) => ({ [key]: val })))
}
/**
*
*
* @export
* @template T
* @template K
* @param {T} obj
* @returns {K[]}
*/
export function getAllObjectKeys<T extends Record<string, unknown>, K extends keyof T>(obj: T): K[] {
return (_isFn(Object.getOwnPropertySymbols)
? Object.keys(obj).concat(Object.getOwnPropertySymbols(obj) as any)
: Object.keys(obj)) as K[]
}
/**
*
*
* @export
* @template T
* @template K
* @param {T} target
* @param {(val: T[K], key?: K, original?: T) => T[K]} reducerCallback
* @returns {T}
*/
export function reduceObject<T extends Record<string, unknown>, K extends keyof T>(
target: T,
reducerCallback: (val: T[K], key?: K, original?: T) => T[K],
): T {
if (_isUndef(reducerCallback)) {
return target
}
const _target: T = copyObj(target)
const original: T = copyObj(target)
return getAllObjectKeys(_target).reduce((result, key) => {
result[key as string] = reducerCallback(_target[key] as T[K], key as K, original)
return result
}, {}) as T
}
/**
* Map an object keys and values
* @export
* @template K
* @template V
* @template U
* @param target
* @param callbackFn
* @param thisArg
* @returns {{[key in K]: U}}
*/
export function map<K extends string, V, U>(
target: { [key in K]: V },
callbackFn: (value: V, key: K, obj: { [key in K]: V }) => U,
thisArg?: unknown,
): { [key in K]: U } {
const res: Partial<{ [key in K]: U }> = {}
for (const key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
res[key] = callbackFn.call(thisArg, target[key], key, target)
}
}
return res as { [key in K]: U }
}
/**
*
* Map object literal for quick editing or build a keyed object literal from
* an array of objects
*
* @example `
* let foobar = mapObject(
* [{id:'foo',foobar:'bar'}],
* ((value, key, index, array) => [value.id, value]),
* { advanced: true, copy: true, filter: true }
* ) // => ({'foo':{id:'foo',foobar:'bar'}})
* `
* @example `
* let foobar = mapObject(
* {foo:1,bar:2},
* ((value, key, index, array) => value * 2),
* { advanced: false, copy: true, filter: true }
* ) // => ({foo:2,bar:4})
* `
*
* @export
* @template T
* @param {T|Array<object>|object} target
* @param {<((val,key,index,arr) => (val|[key,val]))>} callbackfn
* @param {<{copy: boolean, filter: boolean, advanced: boolean}>} [opt] (
* if !!filter - removes anything undefined,
* if !!advanced - the clbk should return a tuple of the new key and value [k,v]
* )
* @returns {object}
* @deprecated Move to {@link map}
* @see {@link map}
*/
export function mapObject(target, callbackfn: MapObjectClbkFn, opt?: MapObjectOptions) {
const { copy: cp = false, filter = false, advanced = false, forEach = false } = opt ?? {}
const data = cp ? copy(target) : target
const entries = Object.entries(data) ?? []
const handler = ([key, value], index, array) =>
// If advanced is true allow the user to set the
// full tuple (i.e. including the object key)
!advanced ? [key, callbackfn(value, key, index, array)] : callbackfn(value, key, index, array)
const result: any = forEach ? entries.forEach(handler) : entries.map(handler)
if (!forEach) {
const filtered = !filter ? result : result.filter(([, v]) => !_isUndef(v))
return Object.fromEntries(filtered)
}
return undefined
}
export type MapObjectClbkFn = (
value: any,
key?: string | number,
index?: number,
array?: Array<any>,
) => [key: string | number, value: any] | any | void
export type MapObjectOptions = {
copy?: boolean
filter?: boolean
advanced?: boolean
forEach?: boolean
}
/**
* Filter indexes inside an object literal. Similar to
* Array.filter(...)
* @param target
* @param {FilterObjPredicate<T>} predicate
* @returns {{[p: string]: any}}
*/
export function filterObject<T>(target, predicate: FilterObjPredicate<T>) {
return mapObject(
target,
(v, k, i, a): [any, any] | null => {
if (predicate(v, k, i, a)) {
return [k, v]
}
return null
},
{ advanced: true, filter: true },
)
}
type FilterObjPredicate<T> = {
(value: T, key: keyof any, index: number, array: T[]): unknown
}
/**
* Shortcut for Object.assign({}, target, ...otherTargets)
*
* @export
* @template T
* @template U
* @param {T} target
* @param {...U[]} source
* @returns {(T & U)}
*/
export function updateObj<T, U>(target: T, ...source: U[]): T & U {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, target, ...source)
}
/**
*
*
* @export
* @template T
* @template K
* @param {Readonly<T>} obj
* @param {K} key
* @param {{ copy: boolean }} [options]
* @returns {T}
*/
export function deleteProperty<T, K extends keyof T>(obj: Readonly<T>, key: K, options?: { copy: boolean }): T {
const opts = { copy: false, ...options }
const newObj = opts.copy ? copyObj(obj) : obj
delete newObj[key]
return newObj
}
/**
*
*
* @export
* @template T
* @template U
* @template F
* @param {(Iterable<T> | ArrayLike<T>)} iterable
* @param {(v: T, k: number) => U} [mapfn]
* @param {*} [thisArg]
* @returns {F extends undefined ? Array<T> : Array<U>}
*/
export function toArray<T, U, F extends (v: T, k: number) => U = undefined>(
iterable: Iterable<T> | ArrayLike<T>,
mapfn?: (v: T, k: number) => U,
thisArg?: any,
): F extends undefined ? Array<T> : Array<U> {
return Array.from(iterable, mapfn, thisArg) as F extends undefined ? Array<T> : Array<U>
}
/**
*
*
* @export
* @template T
* @template U
* @template F
* @param {(Iterable<T> | ArrayLike<T>)} iterable
* @param {F} [mapfn]
* @param {*} [thisArg]
* @returns {F extends undefined ? Array<T> : Array<U>}
*/
export function copyArray<T, U, F extends (v: T, k: number) => U = undefined>(
iterable: Iterable<T> | ArrayLike<T>,
mapfn?: F,
thisArg?: any,
): F extends undefined ? Array<T> : Array<U> {
return toArray(iterable, mapfn, thisArg)
}
/**
*
*
* @export
* @template T
* @param {Array<T>} oldArray
* @param {(Array<T> | object)} newArray
* @returns {Array<T>}
*/
export function updateArray<T>(oldArray: Array<T>, newArray: Array<T> | Record<string, unknown>): Array<T> {
return Object.assign([], oldArray, newArray)
}
/**
*
*
* @export
* @template T
* @param {(number | any)} index
* @param {Array<T>} array
* @param {(T | Array<T>)} [items]
* @param {{ replace?: boolean, copy?: boolean }} [options]
* @returns {MutatedArrayResponse<T>}
*/
export function mutateArray<T>(
index: number | any,
array: Array<T>,
items?: T | Array<T>,
options?: { replace?: boolean; copy?: boolean },
): MutatedArrayResponse<T> {
const { replace, copy } = { replace: true, copy: false, ...options }
const _array = copy ? copyArray(array) : array
const _items = safeArray(items, items ? [items] : [])
const deleteCount = replace ? ln(_items) : 0
const deleted = _array.splice(index, deleteCount, ..._items)
return { items: _array, deleted, added: _items }
}
type MutatedArrayResponse<T> = Record<'items' | 'deleted' | 'added', Array<T>>
/**
*
*
* @export
* @template T
* @param {number} index
* @param {Array<T>} array
* @param {(T | Array<T>)} items
* @returns {MutatedArrayResponse<T>}
*/
export function addAtIndex<T>(index: number, array: Array<T>, items: T | Array<T>): MutatedArrayResponse<T> {
return mutateArray(index, array, items, { replace: false })
}
/**
*
*
* @export
* @template T
* @param {(number | any)} index
* @param {Array<T>} array
* @param {T} item
* @returns {MutatedArrayResponse<T>}
*/
export function updateAtIndex<T>(index: number | any, array: Array<T>, item: T): MutatedArrayResponse<T> {
return mutateArray(index, array, item)
}
/**
*
*
* @export
* @template T
* @param {number} index
* @param {Array<T>} array
* @returns {MutatedArrayResponse<T>}
*/
export function removeAtIndex<T>(index: number, array: Array<T>): MutatedArrayResponse<T> {
return mutateArray(index, array)
}
/**
*
*
* @export
* @template T
* @param {T} item
* @param {Array<T>} array
* @returns {Array<T>}
*/
export function removeFromArray<T>(item: T, array: Array<T>): Array<T> {
return safeArray(array).filter((i) => i !== item)
}
/**
*
*
* @export
* @template K
* @template T
* @template U
* @param {T} array
* @param {(K | any)} currentIndex
* @param {(K | any)} newIndex
* @returns {T}
*/
export function reorderArray<K extends number & keyof T, T extends Array<U>, U>(
array: T,
currentIndex: K | any,
newIndex: K | any,
): T {
const arr = mutateArray(currentIndex, array)
return addAtIndex(newIndex, arr.items, arr.deleted).items as T
}
/**
* Shortcut for String(val).trim()
*
* @export
* @template T
* @param {T} val
* @returns {string}
*/
export function trim<T>(val: T): string {
return s(val).trim()
}
/**
* Capitalize the first word in the string
*
* @export
* @template T
* @param {T} val
* @returns {T}
*/
export function capitalize<T extends string>(val: T): T {
if (!_isStr(val) || !hasLn(val)) {
return val
}
return (s(val).charAt(0).toUpperCase() + s(val).slice(1)) as T
}
/**
* Capitalize every word separated by a space
*
* @export
* @template T
* @param {T} val
* @returns {T}
*/
export function capitalizeTitle<T extends string>(val: T, separator = ' '): T {
return s(val)
.split(separator)
.map((i) => capitalize(i))
.join(separator) as T
}
/**
* Create a numeronym string from the provided string of characters
*
* @export
* @param {string} str
* @param {NumeronymOpts} [opt]
* @returns
*/
export function numeronym(str: string, opt?: NumeronymOpts) {
const { kind, short } = opt ?? {}
const builder = {
[NumeronymKind.n19s]: () => {
const v = String(str)
const ln = v.length
if (ln < 2) {
return v
}
if (ln < 3 || short) {
return `${v[0]}${ln - 1}`
}
return `${v[0]}${ln - 2}${v[ln - 1]}`
},
}
return builder[NumeronymKind[kind] ?? NumeronymKind.n19s]()
}
export enum NumeronymKind {
n19s = 'NumericalContractions' /* first letter + len between(+ last letter) */,
A9bs = 'AlphanumericAbbreviationS' /* AlphaN. = A9, Abbr. = bs */,
A2S = 'AlphanumericAcronymS' /* (2) A's and (1) S (e.g. W3 or W3C) */,
}
export type NumeronymOpts = {
kind?: NumeronymKind
short?: boolean
}
/**
* Ensure and parse any value to a number
*
* @export
* @param {*} val the value to parse into a number
* @param {*} [elseT] returns this if parsing fails
* @param {number} [radix] only necessary if value is of type string
* @returns {(number | typeof elseT)}
*/
export function toNum(val: any, elseT?: any, radix?: number): number | typeof elseT {
const num = _isNum(radix) && _isStr(val) ? parseInt(val, radix) : Number(val)
return isNaN(num) ? elseT ?? 0 : num
}
/**
* Convert decimal/number into its Base16/Hexadecimal equivalent
*
* @export
* @param {number} val
* @returns {string}
*/
export function numberToHexadecimal(val: number): string {
return Number(val).toString(16)
}
/**
* Convert Base16/Hexadecimal string to its decimal/number equivalent
*
* @export
* @param {string} val
* @returns {number}
*/
export function hexadecimalToNumber(val: string): number {
return toNum(val, 0, 16)
}
/**
* Get the display name of a function, react component
* @export
* @param {*} fn
* @param {string} fallback
* @returns {string}
*/
export function getDisplayName(fn, fallback = 'Component'): string {
return fn?.displayName ?? fn?.name ?? fallback
}
/**
* No operation function with no return
* @param args
* @returns {any}
*/
export function noop(...args: any[]): any {
// Do nothing.
}
/**
* Convince closure compiler that the wrapped function has no side-effects.
*
* Closure compiler always assumes that `toString` has no side-effects. We use this quirk to
* allow us to execute a function but have closure compiler mark the call as no-side-effects.
* It is important that the return value for the `noSideEffects` function be assigned
* to something which is retained otherwise the call to `noSideEffects` will be removed by closure
* compiler.
*
* @see https://github.com/angular/angular/blob/master/packages/core/src/util/closure.ts
*/
export function noSideEffects<T>(fn: () => T): T {
return { toString: fn }.toString() as unknown as T
}
namespace Copier {
const getType = <T>(obj: T) => (toString.call(obj) as string).slice(8, -1)
const defaultAssign = <T, S>(target: T, source: S) => {
getAllKeys(source).forEach((key) => {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key]
}
})
return target as T & S
}
const objectAssign = Object.assign ?? defaultAssign
const getAllKeys = typeof Object.getOwnPropertySymbols === 'function'
? (obj) => Object.keys(obj).concat(Object.getOwnPropertySymbols(obj) as any)
: (obj) => Object.keys(obj)
type CopyParams<T, U, K, V, X> = T extends ReadonlyArray<U>
? ReadonlyArray<U>
: T extends Map<K, V>
? Map<K, V>
: T extends Set<X>
? Set<X>
: T extends Record<string, unknown>
? T
: any
/**
* Immutability Copy Cherry Pick
* @see {@link:https://github.com/kolodny/immutability-helper/blob/master/index.ts}
*/
export function copy<T, U, K, V, X>(value: CopyParams<T, U, K, V, X>) {
return Array.isArray(value)
? objectAssign(value.constructor(value.length), value)
: getType(value) === 'Map'
? new Map(value as Map<K, V>)
: getType(value) === 'Set'
? new Set(value as Set<X>)
: value && typeof value === 'object'
? objectAssign(Object.create(Object.getPrototypeOf(value)), value) as T
: value as T
}
}
export const copy = Copier.copy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment