@villedemontreal/general-utils
Version:
General utilities library
173 lines • 6.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCartesianProduct = void 0;
exports.isEmpty = isEmpty;
exports.isCollection = isCollection;
exports.removeEmptyValues = removeEmptyValues;
exports.removeMissingValues = removeMissingValues;
exports.filter = filter;
exports.toDictionary = toDictionary;
exports.isMatching = isMatching;
exports.isCompatible = isCompatible;
const _ = require("lodash");
// prettier-ignore
const getCartesianProduct = (...vectors) => _.reduce(vectors, (accumulator, vector) => _.flatten(_.map(accumulator, product => _.map(vector, value => product.concat([value])))), [[]] // accumulator, initially
);
exports.getCartesianProduct = getCartesianProduct;
/**
* 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...
function isEmpty(value) {
// Easy cases:
let result = value === undefined || value === null || value === '' || undefined;
const valueType = typeof value;
// Some types just can't be "empty":
if (result === undefined) {
// tslint:disable-next-line:cyclomatic-complexity
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. */
function isCollection(value) {
// 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
*/
function removeEmptyValues(collection) {
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.
*/
function removeMissingValues(collection) {
return filter(collection, (value) => !_.isNil(value));
}
/**
* Filters the given collection using the provided predicate.
*
* @param collection Collection from which to filter the values.
*/
function filter(collection, predicate) {
let result = 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.
*/
function toDictionary(collection, mapper = (value, index) => String(index)) {
return _.reduce(collection, (accumulator, value, index) => {
const key = mapper(value, index);
accumulator[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.
*/
function isMatching(model, expectedModel, keyFilter) {
let _expectedModel = expectedModel;
if (!isEmpty(keyFilter)) {
_expectedModel = _.pick(expectedModel, keyFilter);
}
return _.isMatch(model, _expectedModel);
}
/**
* 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.
*/
function isCompatible(model, expectedModel, keyFilter) {
const modelSubSet = {};
const _expectedModel = {};
const compatibilityRules = {};
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;
}
//# sourceMappingURL=collectionUtils.js.map