UNPKG

xtutils

Version:

Thuku's assorted general purpose typescript/javascript library.

1,243 lines 48.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports._tree = exports._selectKeys = exports._chunks = exports._propsObj = exports.FailError = exports._mapValues = exports._rows2cols = exports._array2d = exports._arrayList = exports._trans = exports._sort = exports._dumpVal = exports._flatten = exports._values = exports._isArray = exports._isObject = exports._iterable = exports._empty = exports._valueOf = exports._dotGet = exports._bool = exports._validDotPath = exports._dotInflate = exports._dotFlat = exports._minMax = exports._isFunc = exports._isClass = exports._getProp = exports._hasAnyProps = exports._hasProps = exports._hasProp = exports._getAllProperties = exports._getAllPropertyDescriptors = void 0; const Buffer_1 = require("../Buffer"); const _json_1 = require("./_json"); const _number_1 = require("./_number"); const _string_1 = require("./_string"); /** * Get all property descriptors * - API ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty * * @param value - parse value object * @returns `{[key: string|number|symbol]: any}` ~ {property => descriptors} object */ const _getAllPropertyDescriptors = (value) => { if ([null, undefined].includes(value)) return {}; const proto = Object.getPrototypeOf(value); return { ...(0, exports._getAllPropertyDescriptors)(proto), ...Object.getOwnPropertyDescriptors(value) }; }; exports._getAllPropertyDescriptors = _getAllPropertyDescriptors; /** * Get all value properties * * @param value - parse value object * @param statics - include `static` class properties * @returns `(string|number|symbol)[]` - found own/prototype/symbol properties | `[]` when none found */ const _getAllProperties = (value, statics = false) => { if ([null, undefined].includes(value)) return []; //ignore null/undefined const props = new Set(); //properies //add own property names for (const v of Object.getOwnPropertyNames(value)) props.add(v); //own //fn => get keys helper const __get_keys = (obj) => { const keys = []; for (let key in obj) keys.push(key); return keys; }; //fn => get properties helper const __get_props = (val) => __get_keys((0, exports._getAllPropertyDescriptors)(val)).concat(Object.getOwnPropertySymbols(val)); //excluded default props const excluded_props = [...new Set([ //Function ...__get_props(Function.prototype), ...(!statics ? [] : __get_props(Function)), //Object ...__get_props(Object.prototype), ...(!statics ? [] : __get_props(Object)), ])]; //fn => add props helper const __add_props = (val) => { for (const v of __get_props(val)) { if (!excluded_props.includes(v)) props.add(v); } }; //add props __add_props(value); if (statics) __add_props(Object(value).constructor); //result return [...props]; }; exports._getAllProperties = _getAllProperties; /** * Check if value has property * * @param value - parse `object` value * @param prop - property name * @param own [default: `false`] As own property * @returns `boolean` */ const _hasProp = (value, prop, own = false) => { if (!('object' === typeof value && !!value)) return false; return Object.prototype.hasOwnProperty.call(value, prop) || (own ? false : prop in value); }; exports._hasProp = _hasProp; /** * Check if object has properties * * @param value - parse `object` value * @param props - property names * @returns `boolean` */ const _hasProps = (value, ...props) => { if (!('object' === typeof value && !!value)) return false; if (!props.length) return false; for (const key of props) { if (!(0, exports._hasProp)(value, key)) return false; } return true; }; exports._hasProps = _hasProps; /** * Check if object has any of the properties * * @param value - parse `object` value * @param props - property names * @returns `false|any[]` */ const _hasAnyProps = (value, ...props) => { if (!('object' === typeof value && !!value)) return false; if (!props.length) return false; const found = new Set(); for (const key of props) { if ((0, exports._hasProp)(value, key)) found.add(key); } return found.size ? [...found] : false; }; exports._hasAnyProps = _hasAnyProps; /** * Get value property * * @param value - parse value * @param match - match property * @param ignoreCase - whether to ignore property name case * @param own - whether property is value's own ~ `value.hasOwnProperty` * @returns `IProperty` ~ `{exists:boolean; name:string; value:any;}` */ const _getProp = (value, match, ignoreCase = false) => { const property = { match, key: undefined, value: undefined, exists: 0, }; const props = (0, exports._getAllProperties)(value, false); if (props.includes(match)) { property.key = match; property.value = value[match]; property.exists = value.hasOwnProperty(match) ? 1 : 2; return property; } const text_match = (0, _string_1._stringable)(match); if (text_match !== false) { if (props.includes(match = text_match)) { property.key = match; property.value = value[match]; property.exists = value.hasOwnProperty(match) ? 1 : 2; return property; } if (ignoreCase) { for (const prop of props) { const key = (0, _string_1._stringable)(prop); if (key === false) continue; if (key.toLowerCase() === match.toLowerCase()) { property.key = key; property.value = value[key]; property.exists = value.hasOwnProperty(match) ? 1 : 2; return property; } } } } return property; }; exports._getProp = _getProp; /** * Check if value is a class function * * @param value - parse value */ const _isClass = (value) => { if (!(value && value.constructor === Function) || value.prototype === undefined) return false; if (Function.prototype !== Object.getPrototypeOf(value)) return true; return Object.getOwnPropertyNames(value.prototype).length > 1; }; exports._isClass = _isClass; /** * Check if value is a `function` * * @param value - parse value * @param orClass - (default: `false`) include `class` objects * @returns `boolean` */ const _isFunc = (value, orClass = false) => { return value && 'function' === typeof value && (orClass ? true : !(0, exports._isClass)(value)); }; exports._isFunc = _isFunc; /** * Get `[min, max]` compared and arranged in order * - Example: `_minMax(20, 10)` => `[10, 20]` * - Example: `_minMax(0.23, null)` => `[null, 0.23]` * * @param a - first value * @param b - second value * @returns `[min, max]` */ const _minMax = (a, b) => { let min = a, max = b; if (a > b) { min = b; max = a; } return [min, max]; }; exports._minMax = _minMax; /** * Flatten `object` values recursively to dot paths * * @example * _dotFlat({a:{x:1},b:{y:2,z:[5,6]}}) //{'a.x':1,'b.y':2,'b.z.0':5,'b.z.1':6} * * @param value - parse `object` value * @param omit - omit entry keys/dot paths * @returns `{[key: string]: any}` */ const _dotFlat = (value, omit = []) => { if (!(value && 'object' === typeof value)) return {}; const _entries = []; const _addEntries = (obj, _p_key) => { for (const entry of Object.entries(obj)) { const [k, v] = entry; const _key = `${(_p_key ? `${_p_key}.` : '')}${k}`; if (omit && Array.isArray(omit) && omit.length && (omit.includes(`${k}`) || omit.includes(_key))) continue; if (v && 'object' === typeof v) _addEntries(v, _key); else _entries.push([_key, v]); } }; _addEntries(value, ''); return Object.fromEntries(_entries); }; exports._dotFlat = _dotFlat; /** * Unflatten dot flattened `object` ~ reverse of `_dotFlat` * * @example * _dotInflate({'a.x':1,'b.y':2,'b.z.0':5,'b.z.1':6}) //{a:{x:1},b:{y:2,z:[5,6]}} * * @param value - parse value ~ `{[dot_path: string]: any}` * @returns `{[key: string]: any}` parsed result | `{}` when value is invalid */ const _dotInflate = (value) => { const entries = Object.entries((0, exports._dotFlat)(value)); const buffer = {}; for (const [path, path_value] of entries) { const keys = path.split('.'); if (keys.length === 1) { const key = keys[0]; buffer[key] = path_value; continue; } const item = keys.slice().reverse().reduce((prev, key) => ({ [key]: prev }), path_value); let keys_item = item; let keys_buffer = buffer; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const val = keys_item = keys_item[key]; if (!keys_buffer.hasOwnProperty(key)) keys_buffer[key] = val; keys_buffer = keys_buffer[key]; } } const _norm = (val) => { if (Object(val) !== val) return val; let keys, len = 0; if ((len = (keys = Object.keys(val)).length) && Object.keys([...Array(len)]).join(',') === keys.join(',')) val = Object.values(val); for (const key in val) val[key] = _norm(val[key]); return val; }; return _norm(buffer); }; exports._dotInflate = _dotInflate; /** * Get validated object dot path (i.e. `'a.b.c'` to refer to `{a:{b:{c:1}}}`) * * @param dot_path - dot separated keys * @param operations - supports operations (i.e. '!reverse'/'!slice=0') ~ tests dot keys using `/^[-_0-9a-zA-Z]+\=([^\=\.]*)$/` instead of default `/^[-_0-9a-zA-Z]+$/` * @param _failure - `FailError` mode ~ `0` = silent (default) | `1` = logs warning | `2` = logs error | `3` = throws error * @returns `string` valid dot path */ const _validDotPath = (dot_path, operations = false, _failure = 0) => { try { if (!(dot_path = (0, _string_1._str)(dot_path, true))) throw new TypeError('Invalid dot path value.'); const parts = []; for (let v of dot_path.split('.')) { if (!!(v = v.trim())) parts.push(v); } if (!parts.length) throw new TypeError(`Invalid dot path format "${dot_path}".`); const buffer = []; for (let i = 0; i < parts.length; i++) { let part = parts[i]; let valid = /^[-_0-9a-zA-Z]+$/.test(part); if (!valid && operations) { if (['!reverse', '!slice'].includes(part)) valid = true; else if (part.indexOf('=') > -1) { const _invalid = []; for (let v of part.split(',')) { if ((v = v.trim()) && !/^[-_0-9a-zA-Z]+\=([^\=\.]*)$/.test(v)) _invalid.push(v); } if (!_invalid.length) valid = true; } } if (!valid) throw new TypeError(`Invalid dot path key "${part}".`); buffer.push(part); } return buffer.join('.'); } catch (e) { new FailError(e, _failure, { dot_path, operations }); return ''; } }; exports._validDotPath = _validDotPath; /** * Get parsed `boolean` value * * @param value - parse value * @param strict - strict mode ~ support only `boolean-like` value (i.e. `'true'|'false'|true|false|1|0`) returns `undefined` if unsupported when enabled. * @param trim - trim `string` value (default `true`) * @returns * - `boolean` * - `undefined` when invalid if `strict` is enabled * - `'false' => false` | `!!value` when strict is disabled */ const _bool = (value, strict = false, trim = true) => { if (trim && 'string' === typeof value) value = value.trim(); if (strict && !['true', 'false', true, false, 1, 0].includes(value)) return undefined; return value === 'false' ? false : !!value; }; exports._bool = _bool; /** * Resolve dot path object value ~ supports array operations chaining * * @example * * //simple usage * _dotGet('x', {'x':1}) => 1 * _dotGet('a.b.c', {'a':{'b':{'c':1}}}) => 1 * _dotGet('a.b.d', {'a':{'b':{'c':1}}}) => null * _dotGet('a.0', {'a':['x','y']}) => 'x' * * //array reverse operation (done slice copy) * _dotGet('0.!reverse', [[3,2,1]]) => [3,2,1] * * //array slice operation * _dotGet('0.!slice', [[1,2,3]]) => [1,2,3] * * //array slice negative `-number` * _dotGet('0.-2', [[1,2,3]]) => [2,3] * * //array `key=value` searching * _dotGet('0.a=2', [[{'a':1,'b':2},{'a':2,'b':3}]]) => {'a':2,'b':3} * _dotGet('0.a=1,b=2', [[{'a':1,'b':2,'c':3},{'a':2,'b':3,'c':4}]]) => {'a':1,'b':2,'c':3} * * @param path - dot separated keys ~ optional array operations * @param target - traverse object * @param ignoreCase - whether to ignore case when matching keys (default: `false`) * @param _failure - `FailError` mode ~ `0` = silent (default) | `1` = logs warning | `2` = logs error | `3` = throws error * @param _default - default result on failure * @returns `any` dot path match result */ const _dotGet = (path, target, ignoreCase = false, _failure = 0, _default) => { try { const keys = (path = (0, exports._validDotPath)(path, true, _failure)).split('.'); if (!keys.length) throw new TypeError('Invalid resolve dot path format.'); let abort = false, value = keys.reduce((prev, key) => { if (abort) return prev; //not found if (prev && 'object' === typeof prev) { const prop = (0, exports._getProp)(prev, key, ignoreCase); if (prop.exists) return prop.value; //key value if (Array.isArray(prev)) { if (key === '!reverse') return prev.slice().reverse(); //array reverse (slice) if (key === '!slice') return prev.slice(); //array slice //array slice `-number` let tmp; if ((tmp = (0, _number_1._num)(key, 0)) < 0 && Number.isInteger(tmp)) return prev.slice(tmp); //array search if (prev.length && key.indexOf('=') > -1) { const search_entries = []; for (let val of key.split(',')) { if (!(val = val.trim())) continue; let arr = val.split('='); if (arr.length !== 2) return []; let k = arr[0].trim(); let v = decodeURIComponent(arr[1]); if (k) search_entries.push([k, (0, _json_1._jsonParse)(v, v)]); } let index = -1; if (search_entries.length) { for (let i = 0; i < prev.length; i++) { const entry = prev[i]; const matches = []; for (const v of search_entries) { const prop = (0, exports._getProp)(entry, v[0], ignoreCase); if (prop.exists && prop.value === v[1]) matches.push(v); } if (matches.length && matches.length === search_entries.length) { index = i; break; } } } if (index > -1) return prev[index]; abort = true; return undefined; } } } //not found abort = true; return undefined; }, target); return !abort ? value : _default; } catch (e) { new FailError(e, _failure, { path, target, ignoreCase, _default }, 'DotGetError'); return _default; } }; exports._dotGet = _dotGet; /** * @deprecated * Get coerced `number/string/JSON` value ~ `value.valueOf()` * * @param value - parse value * @returns `any` ~ `object`|`undefined`|`boolean`|`number`|`bigint`|`string`|`symbol` */ const _valueOf = (value) => { if (!(value && 'object' === typeof value)) return value; let val = value.valueOf(); if (val === value) { if (Object(value[Symbol.toPrimitive]) === value[Symbol.toPrimitive] && !isNaN(val = Number(value))) return val; //hint number if ((val = (0, _string_1._stringable)(value)) !== false) return val; //hint string | value.toString() if ('function' === typeof value.toJSON && (val = value.toJSON()) !== value) return val; //value.toJSON() } return val; //value.valueOf() }; exports._valueOf = _valueOf; /** * Check if value is empty ~ `null`/`undefined`/`NaN`/`''`/`{}`/`![...value]` * * @param value - parse value * @param trim - trim whitespace ~ when value is `string/Buffer` * @returns `boolean` */ const _empty = (value, trim = false) => { if ([null, undefined, NaN, ''].includes(value)) return true; //default empty if (['function', 'boolean', 'number'].includes(typeof value)) return false; //function/boolean/number - ignore if ('string' === typeof value || (0, Buffer_1._isBuffer)(value)) return !(0, _string_1._str)(value, trim).length; //string/Buffer - !length if ('object' !== typeof value) return false; //non object - ignore if (value instanceof Map || value instanceof Set) return !value.size; //Map/Set - !size if (Array.isArray(value)) return !value.length; //Array - !length if (Object(value[Symbol.iterator]) === value[Symbol.iterator]) return ![...value].length; //value[Symbol.iterator] - !length if (!(0, exports._getAllProperties)(value).length) return true; //has no self properties return false; //ignore }; exports._empty = _empty; /** * Check if value can be iterated ~ `[...value]` * * @param value - parse value * @param _async - using `[Symbol.asyncIterator]` (default `false` ~ `[Symbol.iterator]`) * @returns `boolean` */ const _iterable = (value, _async = false) => 'function' === typeof value?.[_async ? Symbol.asyncIterator : Symbol.iterator]; exports._iterable = _iterable; /** * Validate `Object` value * * @param value - parse value * @param _filled - must not be empty `{}` * @returns `boolean` */ const _isObject = (value, _filled = false) => !!value && 'object' === typeof value && Object.getPrototypeOf(value) === Object.prototype && (_filled ? !(0, exports._empty)(value) : true); exports._isObject = _isObject; /** * Validate values iterable array list * * @param value - parse value * @param _mode - parse mode * - `0` = (default) `[Symbol.iterator].name` is 'values'|'[Symbol.iterator]' * - `1` = `Array.isArray` * - `2` = is iterable `[Symbol.iterator]` * @param _filled - must not be empty `[]` * @returns `boolean` */ const _isArray = (value, _filled = false, _mode = 0) => { _mode = [0, 1, 2].includes(_mode = parseInt(_mode)) ? _mode : 0; if (!Array.isArray(value)) { if (_mode === 1) return false; const it = value?.[Symbol.iterator]; if (Object(it) !== it) return false; if (_mode !== 2 && !['values', '[Symbol.iterator]'].includes(it.name)) return false; } try { const len = value.length ?? [...value].length; if (!(Number.isInteger(len) && len >= 0)) return false; return _filled ? !!len : true; } catch (e) { return false; } }; exports._isArray = _isArray; /** * Object array values * * @param value - parse array value * @param entries - enable get entries (i.e. `[key: any, value: any][]`) instead of default values (i.e. `any[]`) * @param object - enable get `Object.values(value)`/`Object.entries(value)` * @param flatten - flatten depth ~ `Array.flat` depth (alias: `-1` => `Array.flat(Infinity)`, `true|null` => `Array.flat()`) * @returns * - `any[]` values or `[key: any, value: any][]` when `entries` argument is `true` * - `[value]` when `value` argument is not iterable or arrayable * - `[]` when `value` argument is empty ~ `[]`/`{}`/`undefined` */ const _values = (value, entries = false, object = false, flatten) => { let items = value === undefined ? [] : entries ? [['0', value]] : [value]; if (value && 'object' === typeof value && 'function' !== typeof value) { if (Object(value[Symbol.iterator]) === value[Symbol.iterator]) { const has_entries = (items = [...value]).length && items.findIndex(v => !(Array.isArray(v) && v.length === 2 && Object.keys(v) + '' === '0,1' && ['string', 'number'].includes(typeof v[0]))) < 0; if (entries) items = has_entries ? items : Object.entries(items); else if (has_entries) { const values = []; for (const v of items) values.push(v[1]); items = values; } } else if (object) { const arr = Object.entries(value); if (arr.length || ((0, exports._empty)(value) && (0, exports._isObject)(value))) { if (!entries && arr.length) { const values = []; for (const v of arr) values.push(v[1]); items = values; } else items = arr; } } else if ((0, exports._empty)(value) && (0, exports._isObject)(value)) items = []; //{} } if ('undefined' !== typeof flatten) { let depth = flatten; if (flatten === -1) depth = Infinity; else if ([null, true].includes(depth)) depth = undefined; items = items.flat(depth); } return Object.values(items); }; exports._values = _values; /** * Flatten object array values * * @param value - parse array value * @param depth - flatten depth (default: `-1`) ~ `Array.flat` depth (alias: `-1` => `Array.flat(Infinity)`, `true|null` => `Array.flat()`) * @returns `any[]` - flattened values */ const _flatten = (value, depth) => (0, exports._values)(value, false, false, depth === undefined ? -1 : depth); exports._flatten = _flatten; /** * Get dump value with limit max string length * * @param value - parse value * @param maxStrLength - max string length [default: `200`] * @param first - summarize object array to count and first entry (i.e. `{count:number,first:any}`) * @returns `any` - dump value */ const _dumpVal = (value, maxStrLength = 200, first = false) => { const minStrLength = 20; value = (0, _json_1._jsonCopy)(value); maxStrLength = !(maxStrLength = (0, _number_1._int)(maxStrLength, 200)) ? 0 : (maxStrLength >= minStrLength ? maxStrLength : 200); const _maxStr = (v) => { if (!('string' === typeof v && v.length > maxStrLength)) return v; const append = `...(${v.length})`; return v.substring(0, maxStrLength - append.length) + append; }; const _get_first = (val) => { if (Array.isArray(val)) { let same_keys = 1, prev_keys = ''; for (let i = 0; i < val.length; i++) { const v = val[i]; if (Object(v) !== v) { same_keys = 0; break; } const keys = Object.keys(v); if (keys.length) { same_keys = 0; break; } const keys_val = keys.join(','); if (!i) prev_keys = keys_val; else if (keys_val !== prev_keys) { same_keys = 0; break; } } if (same_keys && val.length) return { count: val.length, first: _get_first(val[0]) }; } return val; }; const _parse = (val) => { if ('object' === typeof val && val) { for (let k in val) { if (!val.hasOwnProperty(k)) continue; val[k] = _parse(val[k]); } } else val = _maxStr(val); return val; }; return _parse(first ? _get_first(value) : value); }; exports._dumpVal = _dumpVal; /** * Sort `Array` **slice** values * - returns new array (i.e. `array.slice().sort(...)` does not affect original arrangement) * * @param array - sort `Array` * @param mode - sort mode * @param onCompare - custom compare callback * @param localeCompareConfig - method config [`String.localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) (default: `{locales:'en',options:{sensitivity:'base'}}`) ~ [options.sensitivity](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) * @returns Sorted `T[]` */ const _sort = (array, mode, onCompare, localeCompareConfig) => { try { // parse args const items = [...array].slice(); if (!items.length) return items; //<< cancel ~ empty const sort_mode = mode; const locale_compare_config = { locales: localeCompareConfig?.locales || 'en', options: { sensitivity: 'base', ...Object(localeCompareConfig?.options) }, }; // onCompare callback const _on_compare = 'function' === typeof onCompare ? onCompare : undefined; // fn => helper ~ sort compare const _sort_compare = (a, b, key) => { let x = a, y = b; if (_on_compare) { const result = _on_compare(a, b, key); const val = parseInt(result); if ([-1, 1, 0].includes(val)) return val; x = result?.[0] ?? x; y = result?.[1] ?? y; } let val = 0, str = 0; if ('string' === typeof x && 'string' === typeof y && 'function' === typeof x.localeCompare) { str = 1; val = x.localeCompare(y, locale_compare_config.locales, locale_compare_config.options); } else val = x > y ? 1 : x < y ? -1 : 0; return val; }; // fn => helper ~ sort mode order const _sort_order = (smode) => { let val = smode ?? 1; if ('string' === typeof val) { if (!(val = val.trim())) return 1; if (val.toLowerCase().startsWith('asc')) return 1; if (val.toLowerCase().startsWith('desc')) return -1; } if ((val = parseInt(val)) === -1) return -1; if (val !== 1) console.warn(`[-] unsupported _sort \`mode\` value (${smode}).`); return 1; }; // fn => helper ~ do compare const _do_compare = (a, b, smode, key) => { const scompare = _sort_compare(a, b, key); const sorder = _sort_order(smode); return scompare * sorder; }; // fn => helper ~ create sort method const _sort_method = () => { const sort_map = new Map(); let entry = undefined; if (Object(sort_mode) === sort_mode) { const _sort_entry = (v, k = '') => Array.isArray(v) && 'string' === typeof v[0] && !!(k = v[0].trim()) ? [k, (v[1] ?? '').trim() || 'asc'] : undefined; if ('function' === typeof sort_mode[Symbol.iterator]) { const items = [...sort_mode]; if (!!(entry = _sort_entry(items))) sort_map.set(entry[0], entry[1]); // [string,TSortMode] else for (const item of items) { // [string,TSortMode][] if (!!(entry = _sort_entry(item))) sort_map.set(entry[0], entry[1]); } } else for (const item of Object.entries(sort_mode)) { // {[string]: TSortMode} if (!!(entry = _sort_entry(item))) sort_map.set(entry[0], entry[1]); } } if (!sort_map.size) return (a, b) => _do_compare(a, b, sort_mode); const sort_entries = [...sort_map]; return (a, b) => { let after = 0; // 1 let before = 0; // -1 let last = 0; for (const [key, key_order] of sort_entries) { if (!(Object(a).hasOwnProperty(key) || Object(b).hasOwnProperty(key))) continue; const x = a?.[key]; const y = b?.[key]; const val = _do_compare(x, y, key_order, key); if (val) last = val; if (val === 1) after++; else if (val === -1) before++; } if (after && before && after === before) return last; return after > before ? 1 : after < before ? -1 : 0; }; }; //<< result ~ sorted items return items.sort(_sort_method()); } catch (err) { throw new Error(`[-] _sort error: ${err}`); } }; exports._sort = _sort; /** * Parse transform text template context values * * - template must be in dot path pattern where first delimited value is the context key name. * - template values must be put in curly brackets when within mixed text. * - dot path matching is case insensitive. * * @example * _trans('My name is {user.name}.', {User: {Name: 'Root'}}, 'NULL') => 'My name is Root.' * _trans('My phone number is {user.phone}.', {User: {Name: 'Root'}}, 'NULL') => 'My phone number is NULL.' * _trans('address.city', {Address: {City: 'Nairobi'}}, 'NULL') => 'Nairobi' * _trans('address.town', {Address: {City: 'Nairobi', town: undefined}}, 'NULL') => 'undefined' * _trans('No template.', {foo: 'bar'}, 'NULL') => 'No template.' * _trans('KES {item.amount}/=', {item: {amount: 4500}}, 'NULL', (value:string,path:string,name:string) => _commas(value, true, 2)) => 'No template.' * * * @param template - parse template ~ text with value template (e.g. `'My name is {user.name}'`) * @param context - values context ~ `{[name: string]: any}` * @param _default - default value when unable to resolve template value (default: `'NULL'`) * @param _format - format resolved value callback (this allows you to further edit resolved template context values) * @returns `string` transformed text where template values are replaced with resolved context values (see examples) */ const _trans = (template, context, _default = 'NULL', _format) => { // FIXME: refactor "_trans" implementation (not urgent) const pattern = /\{([_0-9a-zA-Z]+)((\.[_0-9a-zA-Z]+)*)\}/g; const value = (0, _string_1._str)(template); if (!value.trim()) return value; //-- ignores blank const missing = `!!_${Date.now()}_!!`; const _trans_format = 'function' === typeof _format ? _format : undefined; const _trans_get = (name, path = '') => { let val = (0, exports._dotGet)(name, context, true, 0, missing); if (val === missing) return missing; if (!!(path = (0, _string_1._str)(path, true))) val = (0, exports._dotGet)(path, val, true, 0, missing); if (val === missing) return missing; if (_trans_format) val = _trans_format(val, path, name); const text = Array.isArray(val) ? false : (0, _string_1._stringable)(val); return text !== false ? text : (0, _string_1._str)(val, false, true); }; if (!pattern.test(value)) { const val = _trans_get(value); return val !== missing ? val : value; } let default_val = (0, _string_1._str)(_default); return value.replace(pattern, (...args) => { const name = args[1]; const path = args[2].replace(/^\./, ''); let val = _trans_get(name, path); if (val === missing) val = default_val; return val; }); }; exports._trans = _trans; /** * Parse iterable values array list * * @param values - parse values * @returns `T[]` array list */ const _arrayList = (values) => (0, exports._isArray)(values, true) ? [...values] : []; exports._arrayList = _arrayList; /** * Parse iterable 2d array list * * @param values - parse values * @param objects - parse values as object list (object keys as columns) * @returns `any[][]` array list */ const _array2d = (values, objects = false) => { values = (0, exports._arrayList)(values); const rows = []; if (objects) { const cols = new Set(); for (let i = 0; i < values.length; i++) { const item = values[i]; if (Object(item) !== item) continue; for (const key of Object.keys(item)) cols.add(key); } if (cols.size) { const columns = [...cols]; rows.push(columns); for (let i = 0; i < values.length; i++) { const item = values[i], row = []; for (let j = 0; j < columns.length; j++) row.push(item[columns[j] ?? null]); rows.push(row); } } } else { let arr = [], row_cols = 0; for (let i = 0; i < values.length; i++) { if (!(0, exports._isArray)(values[i])) continue; const item = (0, exports._arrayList)(values[i]); if (item.length > row_cols) row_cols = item.length; if (item.length) arr.push(item); } for (let i = 0; i < arr.length; i++) { rows.push([...Array(row_cols)].map((_, j) => arr[i][j] ?? null)); } } return rows; }; exports._array2d = _array2d; /** * Invert 2d array rows to columns * * @param values - parse 2d array values * @param objects - parse values as object list (object keys as columns) * @returns `any[][]` */ const _rows2cols = (values, objects = false) => { const arr = (0, exports._array2d)(values, objects); if (!arr.length) return []; return arr[0].map((_, i) => arr.map(row => row[i])); }; exports._rows2cols = _rows2cols; /** * Map values (`object[]`) by key property ID value * - ID value is a trimmed `string` (lowercase when argument `_lowercase` is `true`) * * @param values - parse values array ~ `<T = any>[]` * @param prop - ID property name (default: `''` ~ uses `string` entry value as ID for scalar values array) * @param _lowercase - (default: `false`) use lowercase ID value for uniform ID value case * @param _texts - (default: `0`) parse text entry mode ~ **enabled when `prop` argument is blank** * - `0` => disabled * - `1` => trim text values * - `2` => stringify and trim text values * @param _silent - (default: `true`) do not log warnings when values entry with invalid ID is skipped * @returns `{[id: string]: T}` object with {ID=entry} mapping */ const _mapValues = (values, prop = '', _lowercase = false, _texts = 0, _silent = true) => { const buffer = {}, items = (0, exports._arrayList)(values), key = (0, _string_1._str)(prop, true); for (let i = 0; i < items.length; i++) { let entry = items[i], id = ''; if (!key) { if ((id = (0, _string_1._str)(entry, true)) && [1, 2].includes(_texts)) { if (_texts === 2) entry = (0, _string_1._str)(entry, true); else if ('string' === typeof entry) entry = (0, _string_1._str)(entry, true); } } else id = (0, _string_1._str)(entry?.[key], true); if (!id) { if (!_silent) console.warn('Invalid map values entry. The ID value is blank.', { i, key, entry }); continue; } if (_lowercase) id = id.toLowerCase(); buffer[id] = entry; } return buffer; }; exports._mapValues = _mapValues; /** * @class `FailError` _extends `Error`_ */ class FailError extends Error { /** * - error message */ message; /** * - error mode */ mode; /** * - error debug */ debug; /** * - error name */ name; /** * Failure error instance/handler * * @param reason - parse error message * @param mode - error mode ~ `0` = silent (default) | `1` = logs warning | `2` = logs error | `3` = throws error * @param debug - error debug * @param name - error name */ constructor(reason, mode = 0, debug = Symbol('undefined'), name) { const err_message = (0, _string_1._errorText)(reason) || 'Blank error message.'; const err_mode = [0, 1, 2, 3].includes(mode = (0, _number_1._posInt)(mode, 0, 3) ?? 0) ? mode : 0; const err_debug = 'symbol' === typeof debug && String(debug) === 'Symbol(default)' ? [] : [debug]; const err_name = (0, _string_1._str)(name, true) || (0, _string_1._str)(reason?.name, true) || 'FailError'; super(err_message); this.message = err_message; this.mode = err_mode; this.debug = err_debug[0]; this.name = err_name; if (err_mode === 1 || err_mode === 2) console[err_mode === 1 ? 'warn' : 'error']((0, _string_1._str)(this, true), ...err_debug); else if (err_mode === 3) throw this; } } exports.FailError = FailError; /** * Extract `object` value property entries * * @param value - parse `object` value * @param props - extract property names * @param _omit - (default: `false`) **exclude** property names extract mode * @param _undefined - (default: `false`) include `undefined` property names * @returns `{[prop: any]: any}` */ const _propsObj = (value, props, _omit = false, _undefined = false) => { const item = Object(value), keys = (0, exports._arrayList)(props); if (_omit) return Object.fromEntries(Object.entries(item).filter(v => !keys.includes(v[0]))); return keys.reduce((prev, key) => { if (!(0, exports._empty)(key, true)) { if (item.hasOwnProperty(key)) prev[key] = item[key]; else if (_undefined) prev[key] = undefined; } return prev; }, {}); }; exports._propsObj = _propsObj; /** * Split `T[]` array values into `T[][]` chunks array * * @param array - parse iterable/spreadable array * @param size - split array chunk length (default: `1`) ~ **_(`0` returns `[[...array]]`)_** * @returns `T[][]` */ const _chunks = (array, size = 1) => { const items = [...array], chunks = [], len = parseInt(size) || 0; if (len < 0) throw new TypeError(`Invalid \`_chunks\` \`chunk_length\` argument value (${size}).`); if (!len) return [items]; for (let i = 0; i < items.length; i += len) chunks.push(items.slice(i, i + len)); return chunks; }; exports._chunks = _chunks; /** * Get objects array with keys selection * * @param array - parse iterable/spreadable objects array `{[key:string]:any}[]` * @param keys - select keys `string[]` * @param omit - (default: `false`) `false` disabled, `true` omit select keys, `string[]` omit keys * @param filled_only - (default: `false`) omit keys that are empty values in all `array` items or omit item that have empty values in all keys * @returns `{[key:string]:any}[]` selection */ const _selectKeys = (array, keys, omit = false, filled_only = false) => { //fn => helper - get keys const _get_keys = (val, label = 'keys') => { if (!('object' === typeof val && val)) return []; try { return [...new Set([...val])]; } catch (error) { console.warn(`[-] invalid \`_selectKeys\` ${label} string array object.`); return []; } }; //normalize args array = [...array]; keys = _get_keys(keys); const omits = []; if (!!omit) { if (omit === true) { omits.push(...keys); keys = []; } else omits.push(..._get_keys(omit, 'omit')); } filled_only = !!filled_only; //parse array > check unfilled > skip ommited keys const items = []; const filled = new Set(), unfilled = {}; const keys_object = keys.length ? Object.fromEntries(keys.map(k => [k, undefined])) : {}; for (const obj of array) { if (Object(obj) !== obj) continue; const item = {}; for (const [key, val] of Object.entries({ ...keys_object, ...obj })) { if (filled_only && !filled.has(key)) { //filled check if ((0, exports._empty)(val, true)) { if (!unfilled.hasOwnProperty(key)) unfilled[key] = 1; } else { if (unfilled.hasOwnProperty(key)) delete unfilled[key]; filled.add(key); } } if (omits.includes(key)) continue; //skip omitted item[key] = val; } items.push(item); //+buffer add } //set selected > omit unfilled const selected = []; const unfilled_keys = Object.keys(unfilled); for (const item of items) { if (keys.length) { let unfilled = 0; const entries = []; for (const k of keys) { if (omits.includes(k)) continue; //skip omitted if (unfilled_keys.includes(k)) continue; //skip unfilled if (filled_only && (0, exports._empty)(item[k], true)) unfilled++; entries.push([k, item[k]]); } if (entries.length && entries.length !== unfilled) selected.push(Object.fromEntries(entries)); continue; } let unfilled = 0; const entries = []; const item_entries = Object.entries(item); for (const [key, val] of item_entries) { if (unfilled_keys.includes(key)) continue; //skip unfilled if (filled_only && (0, exports._empty)(val, true)) unfilled++; entries.push([key, val]); } if (entries.length && entries.length !== unfilled) selected.push(Object.fromEntries(entries)); } //<< result - selected return selected; }; exports._selectKeys = _selectKeys; /** * Dump tree structure * * @param value - parse value * @param options - `ITreeOptions` ~ _(see ITreeOptions docs)_ * @returns `string` */ const _tree = (value, options) => { const { name: _name = '', pad: _pad = 0, blanks = false, max_length = 200, wrap_length = 80, word_break = false, } = Object(options); let pad = (0, _number_1._posInt)(_pad, 0) ?? 0, name = (0, _string_1._str)(_name, true); if (name.length) { name = `[${name}]`; pad += 3; } const _parse = (val) => { if ([null, undefined].includes(val)) return String(val); if (['boolean', 'number'].includes(typeof val)) return String(val); if (Object(val) !== val) return (0, _json_1._jsonStringify)((0, _string_1._str)(val, true)); const it = val[Symbol.iterator], iterable = Object(it) === it; if (!iterable && (0, _string_1._stringable)(val)) return (0, _string_1._str)(val, true); if (!Object.entries(val = (0, _json_1._jsonCopy)(val)).length) return iterable ? '[]' : '{}'; return val; }; const _lines = (val) => { const node = '├───', node_end = '└───', node_space = ' ', node_border = '│ ', reg_quotes = /^\"(.*)\"$/gs; const lines = []; if ('string' === typeof (val = _parse(val))) return { type: 'value', lines: [val] }; const entries = Object.entries(val), len = entries.length; const it = val[Symbol.iterator], iterable = Object(it) === it; for (let i = 0; i < len; i++) { const [k, v] = entries[i], last = i + 1 === len; let skip = false; if (!blanks && [undefined, null, k].includes(v)) skip = true; let type = 'value', v_lines = [], v_len = 0; if (!skip) { const res = _lines(v); type = res.type; v_lines = res.lines; v_len = v_lines.length; if (!blanks && type === 'value' && !v_lines[0]) skip = true; } const is_list = iterable && it.name !== 'entries' && Number.isInteger(Number(k)) && Number(k) >= 0; const key = is_list ? `[${k}]` : k, list_value = is_list && type === 'value'; if (!(skip && list_value)) lines.push(list_value ? `${last ? node_end : node}${key}` : `${last ? node_end : node}${key}`); if (skip) continue; const key_pad = list_value ? ''.padStart(`[${k}]`.length + 1) : ''; const key_node = (last ? node_space : node_border) + key_pad; const proc_len = typeof process !== 'undefined' && Number.isInteger(process?.stdout?.columns) && key_node.length < (process.stdout.columns / 2) ? process.stdout.columns : 0; for (let x = 0; x < v_len; x++) { const v_last = x + 1 === v_len; let text = v_lines[x]; if (type === 'value') { let quoted = reg_quotes.test(text); if (quoted) text = text.replace(reg_quotes, '$1'); text = (0, _string_1._textMaxLength)(text, max_length, 2); if (quoted) text = `"${text}"`; const wrap_len = proc_len ? (proc_len - key_node.length) / 2 : wrap_length; const wrap_lines = (0, _string_1._wrapLines)(text, wrap_len, word_break); const text_node = v_last ? node_end : node; for (let n = 0; n < wrap_lines.length; n++) { const wrap_node = list_value ? (!n ? ' ' : key_node) : (!n ? text_node : (v_last ? node_space : node_border)); const wrap_line = wrap_lines[n]; if (list_value) { if (!n) lines.push(lines.pop() + wrap_node + wrap_line); else lines.push(wrap_node + wrap_line); } else lines.push(key_node + wrap_node + wrap_line); } } else lines.push(`${key_node}${text}`); } } return { type: 'node', lines }; }; const { lines } = _lines(value); return '\n' + (name ? `${name}\n` : '') + lines.map(line => pad > 0 ? ''.padStart(pad) + line : line).join('\n'); }; exports._tree = _tree; //# sourceMappingURL=_objects.js.map