UNPKG

@villedemontreal/general-utils

Version:
197 lines (176 loc) 6.04 kB
import * as _ from 'lodash'; // prettier-ignore export const getCartesianProduct = (...vectors: any[]): any[] => _.reduce( vectors, (accumulator, vector) => _.flatten( _.map(accumulator, product => _.map(vector, value => product.concat([value])) ) ), [[]] // accumulator, initially ); /** * Tells whether the provided value is or can be considered empty. * * Following types are always considered *NOT* empty: boolean, number, function, RegExp. * * @example * • isEmpty('') → true * • isEmpty('…') → false * • isEmpty({}) → true * • isEmpty([]) → true * • isEmpty(null) → true * • isEmpty(undefined) → true * • isEmpty(true) → false * • isEmpty(/^$/) → false * * @param collection Collection to remove the empty values from. * @param #removeEmptyValues */ // Rationale: Lodash's `#isEmpty` only deals with collections... export function isEmpty(value: any) { // Easy cases: let result: boolean | undefined = value === undefined || value === null || value === '' || undefined; const valueType = typeof value; // Some types just can't be "empty": if (result === undefined) { if ( valueType === 'boolean' || valueType === 'number' || valueType === 'function' || value instanceof RegExp || value instanceof Date || value instanceof Error ) { result = false; } } // Case of collections - leverage `_.size`: if (result === undefined && isCollection(value)) { result = _.isEmpty(value); } // Finally, if still not determined, return whether the value is "falsy": return result !== undefined ? result : !value; } /** Tells whether the provided value is or can be considered as a collection. */ export function isCollection(value: any) { // Note: `Set` & `Map` instances are told to true by `_.isObjectLike` so no additional testing is required. return _.isObjectLike(value) || _.isArrayLike(value); } /** * Removes empty values from the given collection. * * @example * • removeEmptyValues({A: 'a', B: '', C: null, D: 'd', E: undefined, F: true}) → {A: 'a', D: 'd', F: true} * * @param collection Collection from which to remove the empty values. * * @see #isEmpty */ export function removeEmptyValues<C>(collection: C): C { return filter(collection, (value) => !isEmpty(value)); } /** * Removes missing values from the given collection. * * @example * • removeMissingValues({A: 'a', B: '', C: null, D: 'd', E: undefined, F: true}) → {A: 'a', B: '', D: 'd', F: true} * * @param collection Collection from which to remove the missing values. */ export function removeMissingValues<C>(collection: C): C { return filter(collection, (value) => !_.isNil(value)); } /** * Filters the given collection using the provided predicate. * * @param collection Collection from which to filter the values. */ export function filter<C>(collection: C, predicate: (value: any) => boolean): C { let result: any = collection; if (!_.isNil(predicate)) { if (_.isArray(collection)) { result = _.filter(collection, predicate); } else { result = _.pickBy(collection, predicate); } } return result; } /** * Converts the provided collection into a dictionary. * * @param collection Collection to convert into a dictionary. * @param mapper Function used to make the keys of the resulting dictionary. */ export function toDictionary<T, C extends T[]>( collection: C, mapper: (value: T, index: number) => string = (value: T, index: number) => String(index), ): { [key: string]: T } { return _.reduce( collection, (accumulator, value, index) => { const key = mapper(value, index); (accumulator as any)[key] = value; return accumulator; }, {}, ); } /** * Tells whether the provided model is matching with the expected model. * * @example * • isMatching({'A': 1, 'B': 2, 'C': 3}, {'A': 1, 'B': true}) // → false * • isMatching({'A': 1, 'B': true, 'C': 3}, {'A': 1, 'B': true}) // → true * * @param model Model to check. * @param expectedModel Structure composed of fixed values, describing what the model should be matching with. * @param keyFilter Keys of the fields to consider for the operation. */ export function isMatching(model: any, expectedModel: any, keyFilter?: string[]) { let _expectedModel = expectedModel; if (!isEmpty(keyFilter)) { _expectedModel = _.pick(expectedModel, keyFilter); } return _.isMatch(model, _expectedModel); } export type CompatibilityRuleSet = { [key: string]: (value: any) => boolean }; /** * Tells whether the provided model is compatible with the expected model. * * This method is very similar to `#isMatching` but also offers the ability to * specify rules (predicates) instead of fixed values only. * * @example * • isCompatible({'A': 1, 'B': 2, 'C': 3}, {'A': 1, 'B': _.isBoolean}) // → false * • isCompatible({'A': 1, 'B': true, 'C': 3}, {'A': 1, 'B': _.isBoolean}) // → true * * @param model Model to check. * @param expectedModel Structure composed of both fixed values and rules, describing what the model should be matching with. * @param keyFilter Keys of the fields to consider for the operation. */ export function isCompatible(model: any, expectedModel: any, keyFilter?: string[]) { const modelSubSet: any = {}; const _expectedModel: any = {}; const compatibilityRules: CompatibilityRuleSet = {}; let isCompatibleRulesEmpty = true; _.forEach(expectedModel, (value, key) => { if (isEmpty(keyFilter) || (keyFilter && keyFilter.indexOf(key) > -1)) { if (typeof value !== 'function') { _expectedModel[key] = value; } else { compatibilityRules[key] = value; modelSubSet[key] = model[key]; isCompatibleRulesEmpty = false; } } }); let result = _.isMatch(model, _expectedModel); if (!isCompatibleRulesEmpty) { result = result && _.conformsTo(modelSubSet, compatibilityRules); } return result; }