diff --git a/.eslintrc.json b/.eslintrc.json index 29091a9..f1eeca5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,12 +1,12 @@ { "env": { - "amd": true, "browser": true, "node": true }, "extends": "eslint:recommended", "parserOptions": { - "ecmaVersion": 5 + "ecmaVersion": 5, + "sourceType": "module" }, "root": true, "rules": { @@ -210,6 +210,10 @@ } } ], + "object-curly-spacing": [ + "error", + "always" + ], "one-var": [ "error", "never" diff --git a/.eslintrc.test.json b/.eslintrc.test.json index 0615adc..66060b3 100644 --- a/.eslintrc.test.json +++ b/.eslintrc.test.json @@ -1,6 +1,6 @@ { "env": { - "jasmine" : true, + "jest" : true, "node": true }, "extends": [ diff --git a/.travis.yml b/.travis.yml index 7a4e596..c4d1b8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: node_js before_script: - - npm install -g gulp + - npm install -g gulp-cli script: - gulp travis node_js: - "stable" + - "10" - "9" - "8" - "7" diff --git a/README.md b/README.md index 93e418f..e3d17ce 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,17 @@ Require it in node.js: var _ = require("lamb"); ``` -It's useful to alias it to have a shorter symbol, like `_`, as I did above and throughout the documentation: it's cleaner and the -`lamb` object itself can be used as a placeholder argument in [partial application](https://ascartabelli.github.io/lamb/module-lamb.html#partial). +Since version 0.57.0, Lamb is splitted in ES modules and can take advantage of tree-shaking capabilities of module bundlers: + +```javascript +import * as _ from "lamb"; +``` + +You can also import only the functions you want to use: + +```javascript +import { compose, map } from "lamb"; +``` In a browser, simply include the version you want from the `dist` folder: diff --git a/dist/lamb.js b/dist/lamb.js index 23f92cc..d187d11 100644 --- a/dist/lamb.js +++ b/dist/lamb.js @@ -1,58 +1,28 @@ /** - * @overview lamb - A lightweight, and docile, JavaScript library to help embracing functional programming. - * @author Andrea Scartabelli - * @version 0.57.0-alpha.3 - * @module lamb - * @license MIT - * @preserve - */ -(function (host) { - "use strict"; - - var lamb = Object.create(null); - var _ = {}; // internal placeholder for partial application - var _placeholder = lamb; // default value for public placeholder - - Object.defineProperties(lamb, { - /** - * The object used as a placeholder in partial application. Its default value is - * the lamb object itself.
- * The property is public so that you can make Lamb use your own placeholder, however - * you can't change it at will or the partially applied functions you defined before the - * change won't recognize the former placeholder. - * @alias module:lamb.@@lamb/placeholder - * @category Special properties - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.53.0 - * @type Object - */ - "@@lamb/placeholder": { - get: function () { - return _placeholder; - }, - set: function (value) { - _placeholder = value; - } - }, - - /** - * The current library version. - * @alias module:lamb.@@lamb/version - * @category Special properties - * @readonly - * @since 0.53.0 - * @type String - */ - "@@lamb/version": {value: "0.57.0-alpha.3"} - }); - - // prototype shortcuts - var _objectProto = Object.prototype; - var _stringProto = String.prototype; - - // constants - var MAX_ARRAY_LENGTH = 4294967295; +* @overview lamb - A lightweight, and docile, JavaScript library to help embracing functional programming. +* @author Andrea Scartabelli +* @version 0.57.0-rc.2 +* @module lamb +* @license MIT +* @preserve +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = global || self, factory(global.lamb = {})); +}(this, function (exports) { 'use strict'; + + /** + * The placeholder object used in partial application. + * @memberof module:lamb + * @alias module:lamb.__ + * @category Special properties + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.57.0 + * @type {Object} + */ + var __ = {}; /** * Builds a function that returns a constant value. @@ -85,89 +55,103 @@ } /** - * Returns a function that is the composition of the functions given as parameters. - * The first function consumes the result of the function that follows. + * Verifies that the two supplied values are the same value using the "SameValueZero" comparison.
+ * With this comparison NaN is equal to itself, but 0 and -0 are + * considered the same value.
+ * See also {@link module:lamb.isSVZ|isSVZ} for a curried version building a predicate and + * {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} to perform a "SameValue" comparison. * @example - * var sayHi = function (name) { return "Hi, " + name; }; - * var capitalize = function (s) { - * return s[0].toUpperCase() + s.substr(1).toLowerCase(); - * }; - * var fixNameAndSayHi = _.compose(sayHi, capitalize); - * - * sayHi("bOb") // => "Hi, bOb" - * fixNameAndSayHi("bOb") // "Hi, Bob" - * - * var users = [{name: "fred"}, {name: "bOb"}]; - * var sayHiToUser = _.compose(fixNameAndSayHi, _.getKey("name")); + * var testObject = {}; * - * _.map(users, sayHiToUser) // ["Hi, Fred", "Hi, Bob"] + * _.areSVZ({}, testObject) // => false + * _.areSVZ(testObject, testObject) // => true + * _.areSVZ("foo", "foo") // => true + * _.areSVZ(0, -0) // => true + * _.areSVZ(0 / 0, NaN) // => true * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.pipe|pipe} - * @since 0.1.0 - * @param {Function} a - * @param {Function} b - * @returns {Function} + * @category Logic + * @see {@link module:lamb.isSVZ|isSVZ} + * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.50.0 + * @param {*} a + * @param {*} b + * @returns {Boolean} */ - function compose (a, b) { - return arguments.length ? function () { - return a.call(this, b.apply(this, arguments)); - } : identity; + function areSVZ (a, b) { + return a !== a ? b !== b : a === b; // eslint-disable-line no-self-compare } /** - * Creates generic functions out of methods. - * @author A very little change on a great idea by [Irakli Gozalishvili]{@link https://github.com/Gozala/}. - * Thanks for this *beautiful* one-liner (never liked your "unbind" naming choice, though). - * @memberof module:lamb - * @category Function - * @function + * Builds a function that passes only two arguments to the given function.
+ * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, + * exposed for convenience. * @example - * var join = _.generic(Array.prototype.join); - * - * join([1, 2, 3, 4, 5], "-") // => "1-2-3-4-5" - * - * // the function will work with any array-like object - * join("hello", "-") // => "h-e-l-l-o" + * _.list(1, 2, 3, 4, 5) // => [1, 2, 3, 4, 5] + * _.binary(_.list)(1, 2, 3, 4, 5) // => [1, 2] * - * @since 0.1.0 - * @param {Function} method + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.aritize|aritize} + * @see {@link module:lamb.unary|unary} + * @since 0.10.0 + * @param {Function} fn * @returns {Function} */ - var generic = Function.bind.bind(Function.call); + function binary (fn) { + return function (a, b) { + return fn.call(this, a, b); + }; + } /** - * The I combinator. Any value passed to the function is simply returned as it is. + * "Clamps" a number within the given limits, both included.
+ * The function will convert to number all its parameters before starting any + * evaluation, and will return NaN if min is greater + * than max. * @example - * var foo = {bar: "baz"}; - * - * _.identity(foo) === foo // true + * _.clamp(-5, 0, 10) // => 0 + * _.clamp(5, 0, 10) // => 5 + * _.clamp(15, 0, 10) // => 10 + * _.clamp(0, 0, 10) // => 0 + * _.clamp(10, 0, 10) // => 10 + * _.is(_.clamp(-0, 0, 10), -0) // => true + * _.clamp(10, 20, 15) // => NaN * * @memberof module:lamb - * @category Function - * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus} - * @since 0.1.0 - * @param {*} value - * @returns {*} The value passed as parameter. + * @category Math + * @see {@link module:lamb.clampWithin|clampWithin} + * @since 0.13.0 + * @param {Number} n + * @param {Number} min + * @param {Number} max + * @returns {Number} */ - function identity (value) { - return value; + function clamp (n, min, max) { + n = +n; + min = +min; + max = +max; + + if (min > max) { + return NaN; + } else { + return n < min ? min : n > max ? max : n; + } } /** - * Builds a partially applied function. The lamb object itself can be - * used as a placeholder argument and it's useful to alias it with a short symbol - * such as _.
- * You can use a custom placeholder by setting the - * {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} property. + * Builds a partially applied function.
+ * The {@link module:lamb.__|__} object can be used as a placeholder for arguments.
* @example + * var __ = _.__; * var users = [ * {id: 1, name: "John", active: true, confirmedMail: true}, * {id: 2, name: "Jane", active: true, confirmedMail: false}, * {id: 3, name: "Mario", active: false, confirmedMail: false} * ]; - * var isKeyTrue = _.partial(_.hasKeyValue, [_, true]); + * var isKeyTrue = _.partial(_.hasKeyValue, [__, true]); * var isActive = isKeyTrue("active"); * var hasConfirmedMail = isKeyTrue("confirmedMail"); * @@ -180,7 +164,7 @@ * @see {@link module:lamb.asPartial|asPartial} * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} + * @see {@link module:lamb.__|__} The placeholder object. * @since 0.1.0 * @param {Function} fn * @param {Array} args @@ -198,7 +182,7 @@ for (var i = 0, boundArg; i < argsLen; i++) { boundArg = args[i]; - newArgs[i] = _isPlaceholder(boundArg) ? arguments[lastIdx++] : boundArg; + newArgs[i] = boundArg === __ ? arguments[lastIdx++] : boundArg; } for (var len = arguments.length; lastIdx < len; lastIdx++) { @@ -210,304 +194,216 @@ } /** - * Like {@link module:lamb.partial|partial} will build a partially applied function and - * it will accept placeholders.
- * The difference is that the bound arguments will be appended to the ones received by - * the resulting function. - * @example - * Explaining the difference with partial: - * var f1 = _.partial(_.list, ["a", "b", "c"]); - * var f2 = _.partialRight(_.list, ["a", "b", "c"]); - * - * f1("d", "e") // => ["a", "b", "c", "d", "e"] - * f2("d", "e") // => ["d", "e", "a", "b", "c"] - * - * @example - * Explaining placeholder substitutions: - * var f1 = _.partial(_.list, ["a", _, _, "d"]); - * var f2 = _.partialRight(_.list, ["a", _, _, "d"]); - * - * f1("b", "c", "e") // => ["a", "b", "c", "d", "e"] - * f2("b", "c", "e") // => ["b", "a", "c", "e", "d"] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.partial|partial} - * @see {@link module:lamb.asPartial|asPartial} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} + * Builds a partial application of a ternary function so that its first parameter + * is expected as the last one.
+ * The shouldAritize parameter is for the "reduce" functions, where + * the absence of the initialValue transforms a "fold" operation into a + * "reduce" one. + * @private * @param {Function} fn - * @param {Array} args - * @since 0.52.0 + * @param {Boolean} shouldAritize * @returns {Function} */ - function partialRight (fn, args) { - return function () { - if (!Array.isArray(args)) { - return fn.apply(this, arguments); - } - - var lastIdx = arguments.length - 1; - var argsLen = args.length; - var boundArgs = Array(argsLen); - var newArgs = []; - - for (var i = argsLen - 1, boundArg; i > -1; i--) { - boundArg = args[i]; - boundArgs[i] = _isPlaceholder(boundArg) ? arguments[lastIdx--] : boundArg; - } - - for (i = 0; i <= lastIdx; i++) { - newArgs[i] = arguments[i]; - } - - for (var j = 0; j < argsLen; j++) { - newArgs[i++] = boundArgs[j]; - } + function _makePartial3 (fn, shouldAritize) { + return function (a, b) { + var f = shouldAritize && arguments.length !== 2 ? binary(fn) : fn; - return fn.apply(this, newArgs); + return partial(f, [__, a, b]); }; } - lamb.always = always; - lamb.compose = compose; - lamb.generic = generic; - lamb.identity = identity; - lamb.partial = partial; - lamb.partialRight = partialRight; - /** - * Builds an array with the received arguments excluding the first one.
- * To be used with the arguments object, which needs to be passed to the apply - * method of this function. - * @private + * A curried version of {@link module:lamb.clamp|clamp}, expecting a min + * and a max value, that builds a function waiting for the number to clamp. + * @example + * _.clampWithin(0, 10)(-5) // => 0 + * _.clampWithin(0, 10)(5) // => 5 + * _.clampWithin(0, 10)(15) // => 10 + * _.clampWithin(0, 10)(0) // => 0 + * _.clampWithin(0, 10)(10) // => 10 + * _.is(_.clampWithin(0, 10)(-0), -0) // => true + * _.clampWithin(20, 15)(10) // => NaN + * + * @memberof module:lamb + * @category Math * @function - * @param {...*} value - * @returns {Array} + * @see {@link module:lamb.clamp|clamp} + * @since 0.47.0 + * @param {Number} min + * @param {Number} max + * @returns {Function} */ - var _argsTail = _argsToArrayFrom(1); + var clampWithin = _makePartial3(clamp); /** - * Builds helper functions to extract portions of the arguments - * object rather efficiently without having to write for loops - * manually for each case.
- * The arguments object needs to be passed to the apply method - * of the generated function. - * @private - * @param {Number} idx - * @returns {Function} + * The I combinator. Any value passed to the function is simply returned as it is. + * @example + * var foo = {bar: "baz"}; + * + * _.identity(foo) === foo // true + * + * @memberof module:lamb + * @category Function + * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus} + * @since 0.1.0 + * @param {*} value + * @returns {*} The value passed as parameter. */ - function _argsToArrayFrom (idx) { - return function () { - var argsLen = arguments.length || idx; - var len = argsLen - idx; - var result = Array(len); - - for (var i = 0; i < len; i++) { - result[i] = arguments[i + idx]; - } - - return result; - }; + function identity (value) { + return value; } /** - * Keeps building a partial application of the received function as long - * as it's called with placeholders; applies the original function to - * the collected parameters otherwise.
- * The function checks only the public placeholder to gain a little performance - * as no function in Lamb is built with {@link module:lamb.asPartial|asPartial}. - * @private - * @param {Function} fn - * @param {Array} argsHolder - * @returns {Function|*} - */ - function _asPartial (fn, argsHolder) { - return function () { - var argsLen = arguments.length; - var lastIdx = 0; - var newArgs = []; - - for (var i = 0, len = argsHolder.length, boundArg; i < len; i++) { - boundArg = argsHolder[i]; - newArgs[i] = boundArg === _placeholder && lastIdx < argsLen ? arguments[lastIdx++] : boundArg; - } - - while (lastIdx < argsLen) { - newArgs[i++] = arguments[lastIdx++]; - } - - for (i = 0; i < argsLen; i++) { - if (arguments[i] === _placeholder) { - return _asPartial(fn, newArgs); - } - } - - for (i = 0, len = newArgs.length; i < len; i++) { - if (newArgs[i] === _placeholder) { - newArgs[i] = void 0; - } - } - - return fn.apply(this, newArgs); - }; + * Returns a function that is the composition of the functions given as parameters. + * The first function consumes the result of the function that follows. + * @example + * var sayHi = function (name) { return "Hi, " + name; }; + * var capitalize = function (s) { + * return s[0].toUpperCase() + s.substr(1).toLowerCase(); + * }; + * var fixNameAndSayHi = _.compose(sayHi, capitalize); + * + * sayHi("bOb") // => "Hi, bOb" + * fixNameAndSayHi("bOb") // "Hi, Bob" + * + * var users = [{name: "fred"}, {name: "bOb"}]; + * var sayHiToUser = _.compose(fixNameAndSayHi, _.getKey("name")); + * + * _.map(users, sayHiToUser) // ["Hi, Fred", "Hi, Bob"] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.pipe|pipe} + * @since 0.1.0 + * @param {Function} a + * @param {Function} b + * @returns {Function} + */ + function compose (a, b) { + return arguments.length ? function () { + return a.call(this, b.apply(this, arguments)); + } : identity; } + var MAX_ARRAY_LENGTH = 4294967295; + var MAX_SAFE_INTEGER = 9007199254740991; + /** - * Creates a function to check the given predicates.
- * Used to build the {@link module:lamb.allOf|allOf} and the - * {@link module:lamb.anyOf|anyOf} functions. + * Converts a value to a valid array length, thus an integer within + * 0 and 232 - 1 (both included). * @private - * @param {Boolean} checkAll - * @returns {Function} + * @param {*} value + * @returns {Number} */ - function _checkPredicates (checkAll) { - return function (predicates) { - if (!Array.isArray(predicates)) { - throw _makeTypeErrorFor(predicates, "array"); - } - - return function () { - for (var i = 0, len = predicates.length, result; i < len; i++) { - result = predicates[i].apply(this, arguments); - - if (checkAll && !result) { - return false; - } else if (!checkAll && result) { - return true; - } - } - - return checkAll; - }; - }; + function _toArrayLength (value) { + return clamp(value, 0, MAX_ARRAY_LENGTH) >>> 0; } /** - * The default comparer for sorting functions.
- * If the given values are of different types they - * will be both converted to strings.
- * Uses the SameValueZero comparison. - * @private - * @param {*} a - * @param {*} b - * @returns {Number} -1 | 0 | 1 + * Executes the provided iteratee for each element of the given array-like object.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example Adding a CSS class to all elements of a NodeList in a browser environment: + * var addClass = _.curry(function (className, element) { + * element.classList.add(className); + * }); + * var paragraphs = document.querySelectorAll("#some-container p"); + * + * _.forEach(paragraphs, addClass("main")); + * // each "p" element in the container will have the "main" class now + * + * @memberof module:lamb + * @category Array + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Undefined} */ - function _comparer (a, b) { - var result = 0; - - if (typeof a !== typeof b) { - a = String(a); - b = String(b); - } - - /* eslint-disable no-self-compare */ - - if (!areSVZ(a, b)) { - if (a > b || a !== a) { - result = 1; - } else if (a < b || b !== b) { - result = -1; - } + function forEach (arrayLike, iteratee) { + for (var i = 0, len = _toArrayLength(arrayLike.length); i < len; i++) { + iteratee(arrayLike[i], i, arrayLike); } - - /* eslint-enable no-self-compare */ - - return result; } /** - * Accepts a list of sorting criteria with at least one element - * and builds a function that compares two values with such criteria. - * @private - * @param {Sorter[]} criteria + * Creates generic functions out of methods. + * @author A very little change on a great idea by [Irakli Gozalishvili]{@link https://github.com/Gozala/}. + * Thanks for this *beautiful* one-liner (never liked your "unbind" naming choice, though). + * @memberof module:lamb + * @category Function + * @function + * @example + * var join = _.generic(Array.prototype.join); + * + * join([1, 2, 3, 4, 5], "-") // => "1-2-3-4-5" + * + * // the function will work with any array-like object + * join("hello", "-") // => "h-e-l-l-o" + * + * @since 0.1.0 + * @param {Function} method * @returns {Function} */ - function _compareWith (criteria) { - return function (a, b) { - var len = criteria.length; - var criterion = criteria[0]; - var result = criterion.compare(a.value, b.value); - - for (var i = 1; result === 0 && i < len; i++) { - criterion = criteria[i]; - result = criterion.compare(a.value, b.value); - } - - if (result === 0) { - result = a.index - b.index; - } + var generic = Function.bind.bind(Function.call); - return criterion.isDescending ? -result : result; - }; + /** + * Verifies if a value is null. + * @example + * _.isNull(null) // => true + * _.isNull(void 0) // => false + * _.isNull(false) // => false + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isNil|isNil} if you want to check for undefined too. + * @since 0.1.0 + * @param {*} value + * @returns {Boolean} + */ + function isNull (value) { + return value === null; } /** - * Used by curry functions to collect arguments until the arity is consumed, - * then applies the original function. - * @private - * @param {Function} fn - * @param {Number} arity - * @param {Boolean} isRightCurry - * @param {Boolean} isAutoCurry - * @param {Array} argsHolder - * @returns {Function} + * Verifies if a value is undefined. + * @example + * _.isUndefined(null) // => false + * _.isUndefined(void 0) // => true + * _.isUndefined(false) // => false + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isNil|isNil} if you want to check for null too. + * @since 0.1.0 + * @param {*} value + * @returns {Boolean} */ - function _currier (fn, arity, isRightCurry, isAutoCurry, argsHolder) { - return function () { - var holderLen = argsHolder.length; - var argsLen = arguments.length; - var newArgsLen = holderLen + (argsLen > 1 && isAutoCurry ? argsLen : 1); - var newArgs = Array(newArgsLen); - - for (var i = 0; i < holderLen; i++) { - newArgs[i] = argsHolder[i]; - } - - for (; i < newArgsLen; i++) { - newArgs[i] = arguments[i - holderLen]; - } + function isUndefined (value) { + return value === void 0; + } - if (newArgsLen >= arity) { - return fn.apply(this, isRightCurry ? newArgs.reverse() : newArgs); - } else { - return _currier(fn, arity, isRightCurry, isAutoCurry, newArgs); - } - }; + /** + * Verifies if a value is null or undefined. + * @example + * _.isNil(NaN) // => false + * _.isNil({}) // => false + * _.isNil(null) // => true + * _.isNil(void 0) // => true + * _.isNil() // => true + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isNull|isNull} + * @see {@link module:lamb.isUndefined|isUndefined} + * @since 0.1.0 + * @param {*} value + * @returns {Boolean} + */ + function isNil (value) { + return isNull(value) || isUndefined(value); } /** - * Prepares a function for currying. If it's not auto-currying and the arity - * is 2 or 3 returns optimized functions, otherwise delegates the currying - * to the _currier function.
- * If the desumed arity isn't greater than one, it will return the received - * function itself, instead. - * @private - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @param {Boolean} [isRightCurry=false] - * @param {Boolean} [isAutoCurry=false] - * @returns {Function} - */ - function _curry (fn, arity, isRightCurry, isAutoCurry) { - if (arity >>> 0 !== arity) { - arity = fn.length; - } - - if (isAutoCurry && arity > 1 || arity > 3) { - return _currier(fn, arity, isRightCurry, isAutoCurry, []); - } else if (arity === 2) { - return _curry2(fn, isRightCurry); - } else if (arity === 3) { - return _curry3(fn, isRightCurry); - } else { - return fn; - } - } - - /** - * Curries a function of arity 2. + * Curries a function of arity 2. * @private * @param {Function} fn * @param {Boolean} [isRightCurry=false] @@ -522,422 +418,617 @@ } /** - * Curries a function of arity 3. - * @private - * @param {Function} fn - * @param {Boolean} [isRightCurry=false] + * A curried version of {@link module:lamb.areSVZ|areSVZ}.
+ * Accepts a value and builds a predicate that checks whether the value + * and the one received by the predicate are the same using the "SameValueZero" + * comparison.
+ * See also {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} + * to perform a "SameValue" comparison. + * @example + * var john = {name: "John", surname: "Doe"}; + * var isJohn = _.isSVZ(john); + * var isZero = _.isSVZ(0); + * var isReallyNaN = _.isSVZ(NaN); + * + * isJohn(john) // => true + * isJohn({name: "John", surname: "Doe"}) // => false + * + * isZero(0) // => true + * isZero(-0) // => true + * + * isNaN(NaN) // => true + * isNaN("foo") // => true + * + * isReallyNaN(NaN) // => true + * isReallyNaN("foo") // => false + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.areSVZ|areSVZ} + * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.1.0 + * @param {*} value * @returns {Function} */ - function _curry3 (fn, isRightCurry) { - return function (a) { - return function (b) { - return function (c) { - return isRightCurry ? fn.call(this, c, b, a) : fn.call(this, a, b, c); - }; - }; - }; - } + var isSVZ = _curry2(areSVZ); /** - * Flattens an array. - * @private - * @param {Array} array - The source array - * @param {Boolean} isDeep - Whether to perform a deep flattening or not - * @param {Array} output - An array to collect the result - * @param {Number} idx - The next index to be filled in the output - * @returns {Array} The output array filled with the results + * Builds a new array by applying the iteratee function to each element of the + * received array-like object.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example + * _.map(["Joe", "Mario", "Jane"], _.invoker("toUpperCase")) // => ["JOE", "MARIO", "JANE"] + * + * _.map([4, 9, 16], Math.sqrt); // => [2, 3, 4] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.mapWith|mapWith} + * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Array} */ - function _flatten (array, isDeep, output, idx) { - for (var i = 0, len = array.length, value, j, vLen; i < len; i++) { - value = array[i]; - - if (!Array.isArray(value)) { - output[idx++] = value; - } else if (isDeep) { - _flatten(value, true, output, idx); - idx = output.length; - } else { - vLen = value.length; - output.length += vLen; + function map (arrayLike, iteratee) { + var len = _toArrayLength(arrayLike.length); + var result = Array(len); - for (j = 0; j < vLen; j++) { - output[idx++] = value[j]; - } - } + for (var i = 0; i < len; i++) { + result[i] = iteratee(arrayLike[i], i, arrayLike); } - return output; + return result; } /** - * Converts a value to a number and returns it if it's not NaN, otherwise - * returns zero. - * @private - * @param {*} value - * @returns {Number} + * A curried version of {@link module:lamb.map|map} that uses the provided iteratee to + * build a function expecting the array-like object to act upon. + * @example + * var square = function (n) { return n * n; }; + * var getSquares = _.mapWith(square); + * + * getSquares([1, 2, 3, 4, 5]) // => [1, 4, 9, 16, 25] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.map|map} + * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} + * @since 0.1.0 + * @param {ListIteratorCallback} iteratee + * @returns {function} */ - function _forceToNumber (value) { - var n = +value; - - return n === n ? n : 0; // eslint-disable-line no-self-compare - } + var mapWith = _curry2(map, true); /** - * Establishes at which index an element should be inserted in a sorted array to respect - * the array order. Needs the comparer used to sort the array. - * @private - * @param {Array} array - * @param {*} element - * @param {Function} comparer - * @param {Number} start - * @param {Number} end - * @returns {Number} + * Like {@link module:lamb.partial|partial} will build a partially applied function and + * it will accept placeholders.
+ * The difference is that the bound arguments will be appended to the ones received by + * the resulting function. + * @example + * Explaining the difference with partial: + * var f1 = _.partial(_.list, ["a", "b", "c"]); + * var f2 = _.partialRight(_.list, ["a", "b", "c"]); + * + * f1("d", "e") // => ["a", "b", "c", "d", "e"] + * f2("d", "e") // => ["d", "e", "a", "b", "c"] + * + * @example + * Explaining placeholder substitutions: + * var __ = _.__; + * var f1 = _.partial(_.list, ["a", __, __, "d"]); + * var f2 = _.partialRight(_.list, ["a", __, __, "d"]); + * + * f1("b", "c", "e") // => ["a", "b", "c", "d", "e"] + * f2("b", "c", "e") // => ["b", "a", "c", "e", "d"] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.partial|partial} + * @see {@link module:lamb.asPartial|asPartial} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.__|__} The placeholder object. + * @param {Function} fn + * @param {Array} args + * @since 0.52.0 + * @returns {Function} */ - function _getInsertionIndex (array, element, comparer, start, end) { - if (array.length === 0) { - return 0; - } + function partialRight (fn, args) { + return function () { + if (!Array.isArray(args)) { + return fn.apply(this, arguments); + } - var pivot = (start + end) >> 1; - var result = comparer( - {value: element, index: pivot}, - {value: array[pivot], index: pivot} - ); + var lastIdx = arguments.length - 1; + var argsLen = args.length; + var boundArgs = Array(argsLen); + var newArgs = []; - if (end - start <= 1) { - return result < 0 ? pivot : pivot + 1; - } else if (result < 0) { - return _getInsertionIndex(array, element, comparer, start, pivot); - } else if (result === 0) { - return pivot + 1; - } else { - return _getInsertionIndex(array, element, comparer, pivot, end); - } - } + for (var i = argsLen - 1, boundArg; i > -1; i--) { + boundArg = args[i]; + boundArgs[i] = boundArg === __ ? arguments[lastIdx--] : boundArg; + } - /** - * Gets the number of consecutive elements satisfying a predicate in an array-like object. - * @private - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Number} - */ - function _getNumConsecutiveHits (arrayLike, predicate) { - var idx = 0; - var len = arrayLike.length; + for (i = 0; i <= lastIdx; i++) { + newArgs[i] = arguments[i]; + } - while (idx < len && predicate(arrayLike[idx], idx, arrayLike)) { - idx++; - } + for (var j = 0; j < argsLen; j++) { + newArgs[i++] = boundArgs[j]; + } - return idx; + return fn.apply(this, newArgs); + }; } /** - * Builds the prefix or suffix to be used when padding a string. + * Builds a reduce function. The step parameter must be 1 + * to build {@link module:lamb.reduce|reduce} and -1 to build + * {@link module:lamb.reduceRight|reduceRight}. * @private - * @param {String} source - * @param {String} char - * @param {Number} len - * @returns {String} + * @param {Number} step + * @returns {Function} */ - function _getPadding (source, char, len) { - if (!isNil(source) && type(source) !== "String") { - source = String(source); - } + function _makeReducer (step) { + return function (arrayLike, accumulator, initialValue) { + var len = _toArrayLength(arrayLike.length); + var idx = step === 1 ? 0 : len - 1; + var nCalls; + var result; - return _repeat(String(char)[0] || "", Math.ceil(len - source.length)); - } + if (arguments.length === 3) { + nCalls = len; + result = initialValue; + } else { + if (len === 0) { + throw new TypeError("Reduce of empty array-like with no initial value"); + } - /** - * Checks if a path is valid in the given object and retrieves the path target. - * @private - * @param {Object} obj - * @param {String[]} parts - * @param {Boolean} walkNonEnumerables - * @returns {Object} - */ - function _getPathInfo (obj, parts, walkNonEnumerables) { - if (isNil(obj)) { - throw _makeTypeErrorFor(obj, "object"); - } - - var target = obj; - var i = -1; - var len = parts.length; - var key; - - while (++i < len) { - key = _getPathKey(target, parts[i], walkNonEnumerables); - - if (isUndefined(key)) { - break; + result = arrayLike[idx]; + idx += step; + nCalls = len - 1; } - target = target[key]; - } + for (; nCalls--; idx += step) { + result = accumulator(result, arrayLike[idx], idx, arrayLike); + } - return i === len ? {isValid: true, target: target} : {isValid: false, target: void 0}; + return result; + }; } /** - * Helper to retrieve the correct key while evaluating a path. - * @private - * @param {Object} target - * @param {String} key - * @param {Boolean} includeNonEnumerables - * @returns {String|Number|Undefined} + * Reduces (or folds) the values of an array-like object, starting from the first, to a new + * value using the provided accumulator function.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example + * _.reduce([1, 2, 3, 4], _.sum) // => 10 + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.reduceRight|reduceRight} + * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] + * @returns {*} */ - function _getPathKey (target, key, includeNonEnumerables) { - if (includeNonEnumerables && key in Object(target) || _isEnumerable(target, key)) { - return key; - } - - var n = +key; - var len = target && target.length; - - return n >= -len && n < len ? n < 0 ? n + len : n : void 0; - } + var reduce = _makeReducer(1); /** - * Builds a "grouping function" for an array-like object. - * @private - * @param {Function} makeValue + * A partial application of {@link module:lamb.reduce|reduce} that uses the + * provided accumulator and the optional initialValue to + * build a function expecting the array-like object to act upon. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.reduceWith(_.sum)(arr) // => 15 + * _.reduceWith(_.subtract)(arr) // => -13 + * _.reduceWith(_.subtract, 0)(arr) // => -15 + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.reduceRightWith|reduceRightWith} + * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} + * @since 0.27.0 + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] * @returns {Function} */ - function _groupWith (makeValue) { - return function (arrayLike, iteratee) { - var result = {}; - var len = arrayLike.length; - - for (var i = 0, element, key; i < len; i++) { - element = arrayLike[i]; - key = iteratee(element, i, arrayLike); - result[key] = makeValue(result[key], element); - } - - return result; - }; - } + var reduceWith = _makePartial3(reduce, true); /** - * Makes an object immutable by recursively calling Object.freeze - * on its members. + * Converts a value to an integer. * @private - * @param {Object} obj - * @param {Array} seen - * @returns {Object} The obj parameter itself, not a copy. + * @param {*} value + * @returns {Number} */ - function _immutable (obj, seen) { - if (seen.indexOf(obj) === -1) { - seen.push(Object.freeze(obj)); - - forEach(Object.getOwnPropertyNames(obj), function (key) { - var value = obj[key]; + function _toInteger (value) { + var n = +value; - if (typeof value === "object" && !isNull(value)) { - _immutable(value, seen); - } - }); + if (n !== n) { // eslint-disable-line no-self-compare + return 0; + } else if (n % 1 === 0) { + return n; + } else { + return Math.floor(Math.abs(n)) * (n < 0 ? -1 : 1); } - - return obj; } /** - * If a method with the given name exists on the target, applies it to the provided - * arguments and returns the result. Returns undefined otherwise.
- * The arguments for the method are built by concatenating the array of bound arguments, - * optionally received by {@link module:lamb.invoker|invoker}, with the final set of, also - * optional, args. - * @private - * @param {Array} boundArgs - * @param {String} methodName - * @param {Object} target - * @param {...*} [args] - * @returns {*} + * Builds an array by extracting a portion of an array-like object.
+ * Note that unlike the native array method this function ensures that dense + * arrays are returned.
+ * Also, unlike the native method, the start and end + * parameters aren't optional and will be simply converted to integer.
+ * See {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a + * slice to the end of the array-like. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.slice(arr, 0, 2) // => [1, 2] + * _.slice(arr, 2, -1) // => [3, 4] + * _.slice(arr, -3, 5) // => [3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.sliceAt|sliceAt} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @since 0.1.0 + * @param {ArrayLike} arrayLike - Any array like object. + * @param {Number} start - Index at which to begin extraction. + * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. + * @returns {Array} */ - function _invoker (boundArgs, methodName, target) { - var method = target[methodName]; + function slice (arrayLike, start, end) { + var len = _toArrayLength(arrayLike.length); + var begin = _toInteger(start); + var upTo = _toInteger(end); - if (typeof method !== "function") { - return void 0; + if (begin < 0) { + begin = begin < -len ? 0 : begin + len; } - var boundArgsLen = boundArgs.length; - var ofs = 3 - boundArgsLen; - var len = arguments.length - ofs; - var args = Array(len); - - for (var i = 0; i < boundArgsLen; i++) { - args[i] = boundArgs[i]; + if (upTo < 0) { + upTo = upTo < -len ? 0 : upTo + len; + } else if (upTo > len) { + upTo = len; } - for (; i < len; i++) { - args[i] = arguments[i + ofs]; + var resultLen = upTo - begin; + var result = resultLen > 0 ? Array(resultLen) : []; + + for (var i = 0; i < resultLen; i++) { + result[i] = arrayLike[begin + i]; } - return method.apply(target, args); + return result; } /** - * Accepts a target object and a key name and verifies that the target is an array and that - * the key is an existing index. - * @private - * @param {Object} target - * @param {String|Number} key - * @returns {Boolean} + * Given the start and end bounds, builds a partial application + * of {@link module:lamb.slice|slice} expecting the array-like object to slice.
+ * See also {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a + * slice to the end of the array-like. + * @example + * var arr = [1, 2, 3, 4, 5]; + * var s = "hello"; + * var dropFirstAndLast = _.sliceAt(1, -1); + * + * dropFirstAndLast(arr) // => [2, 3, 4] + * dropFirstAndLast(s) // => ["e", "l", "l"] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.slice|slice} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @since 0.48.0 + * @param {Number} start - Index at which to begin extraction. + * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. + * @returns {Function} */ - function _isArrayIndex (target, key) { - var n = +key; + var sliceAt = _makePartial3(slice); - return Array.isArray(target) && n % 1 === 0 && !(n < 0 && _isEnumerable(target, key)); - } + var objectProtoToString = Object.prototype.toString; /** - * Checks whether the specified key is an enumerable property of the given object or not. - * @private - * @param {Object} obj - * @param {String} key - * @returns {Boolean} + * Retrieves the "type tag" from the given value. + * @example + * var x = 5; + * var y = new Number(5); + * + * typeof x // => "number" + * typeof y // => "object" + * _.type(x) // => "Number" + * _.type(y) // => "Number" + * + * _.type(Object.prototype.toString) // => "Function" + * _.type(/a/) // => "RegExp" + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isType|isType} + * @since 0.9.0 + * @param {*} value + * @returns {String} */ - function _isEnumerable (obj, key) { - return key in Object(obj) && (_isOwnEnumerable(obj, key) || ~_safeEnumerables(obj).indexOf(key)); + function type (value) { + return objectProtoToString.call(value).slice(8, -1); } /** - * Checks whether the specified key is a own enumerable property of the given object or not. - * @private - * @function - * @param {Object} obj - * @param {String} key - * @returns {Boolean} + * Appends the given value at the end of a copy of the provided array-like object. + * @example + * var arr = [1, 2, 3, 4]; + * + * _.appendTo(arr, 5) // => [1, 2, 3, 4, 5] + * _.appendTo(arr, [5]) // => [1, 2, 3, 4, [5]] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.append|append} + * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} + * @since 0.44.0 + * @param {ArrayLike} arrayLike + * @param {*} value + * @returns {Array} */ - var _isOwnEnumerable = generic(_objectProto.propertyIsEnumerable); + function appendTo (arrayLike, value) { + return slice(arrayLike, 0, arrayLike.length).concat([value]); + } /** - * Checks whether the given value is the internal or the public placeholder. - * @private + * A curried version of {@link module:lamb.appendTo|appendTo} that uses the value to append + * to build a function expecting the array-like object to act upon. + * @example + * var arr = [1, 2, 3, 4]; + * + * _.append(5)(arr) // => [1, 2, 3, 4, 5] + * _.append([5])(arr) // => [1, 2, 3, 4, [5]] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.appendTo|appendTo} + * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} + * @since 0.44.0 + * @param {*} value + * @returns {Function} + */ + var append = _curry2(appendTo, true); + + /** + * Checks if an array-like object contains the given value.
+ * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can + * check for NaN, but 0 and -0 are the same value.
+ * See also {@link module:lamb.contains|contains} for a curried version building a predicate. + * @example + * var numbers = [0, 1, 2, 3, NaN]; + * + * _.isIn(numbers, 1) // => true + * _.isIn(numbers, 0) // => true + * _.isIn(numbers, -0) // => true + * _.isIn(numbers, NaN) // => true + * _.isIn(numbers, 5) // => false + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.contains|contains} + * @since 0.13.0 + * @param {ArrayLike} arrayLike * @param {*} value * @returns {Boolean} */ - function _isPlaceholder (value) { - return value === _ || value === _placeholder; + function isIn (arrayLike, value) { + var result = false; + + for (var i = 0, len = arrayLike.length; i < len; i++) { + if (areSVZ(value, arrayLike[i])) { + result = true; + break; + } + } + + return result; } /** - * Accepts an object and build a function expecting a key to create a "pair" with the key - * and its value. - * @private + * Builds a predicate to check if an array-like object contains the given value.
+ * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can + * check for NaN, but 0 and -0 are the same value.
+ * See also {@link module:lamb.isIn|isIn} for an uncurried version. + * @example + * var containsNaN = _.contains(NaN); + * + * containsNaN([0, 1, 2, 3, NaN]) // => true + * + * @memberof module:lamb + * @category Array * @function - * @param {Object} obj + * @see {@link module:lamb.isIn|isIn} + * @since 0.13.0 + * @param {*} value * @returns {Function} */ - var _keyToPairIn = _curry2(function (obj, key) { - return [key, obj[key]]; - }); + var contains = _curry2(isIn, true); /** - * Helper to build the {@link module:lamb.everyIn|everyIn} or the - * {@link module:lamb.someIn|someIn} function. + * Builds a "grouping function" for an array-like object. * @private - * @param {Boolean} defaultResult + * @param {Function} makeValue * @returns {Function} */ - function _makeArrayChecker (defaultResult) { - return function (arrayLike, predicate) { - for (var i = 0, len = arrayLike.length; i < len; i++) { - if (defaultResult ^ !!predicate(arrayLike[i], i, arrayLike)) { - return !defaultResult; - } + function _groupWith (makeValue) { + return function (arrayLike, iteratee) { + var result = {}; + var len = arrayLike.length; + + for (var i = 0, element, key; i < len; i++) { + element = arrayLike[i]; + key = iteratee(element, i, arrayLike); + result[key] = makeValue(result[key], element); } - return defaultResult; + return result; }; } /** - * Helper to build the {@link module:lamb.flatten|flatten} and - * {@link module:lamb.shallowFlatten|shallowFlatten} functions. - * @private + * Transforms an array-like object in a lookup table with the keys generated by the provided + * iteratee, having as values the count of matches for the key. + * @example + * var persons = [ + * {"name": "Jane", "age": 12}, + * {"name": "John", "age": 40}, + * {"name": "Mario", "age": 17}, + * {"name": "Paolo", "age": 15} + * ]; + * var getAgeStatus = function (person) { return person.age >= 18 ? "adult" : "minor"; }; + * + * _.count(persons, getAgeStatus) // => {"adult": 1, "minor": 3} + * + * @memberof module:lamb + * @category Array * @function - * @param {Boolean} isDeep - * @returns {Function} + * @see {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.21.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Object} */ - var _makeArrayFlattener = _curry2(function (isDeep, array) { - return Array.isArray(array) ? _flatten(array, isDeep, [], 0) : slice(array, 0, array.length); + var count = _groupWith(function (a) { + return a ? ++a : 1; }); /** - * Builds a list of sorting criteria from a list of sorter functions. Returns a list containing - * a single default sorting criterion if the sorter list is empty. - * @private - * @param {Function[]} sorters - * @returns {Sorter[]} + * A curried version of {@link module:lamb.count|count} that uses the provided iteratee to + * build a function expecting the array-like object to act upon. + * @example + * var persons = [ + * {"name": "Jane", "city": "New York"}, + * {"name": "John", "city": "New York"}, + * {"name": "Mario", "city": "Rome"}, + * {"name": "Paolo"} + * ]; + * var getCityOrUnknown = _.adapter([_.getKey("city"), _.always("Unknown")]); + * var countByCity = _.countBy(getCityOrUnknown); + * + * countByCity(persons) // => {"New York": 2, "Rome": 1, "Unknown": 1} + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.count|count} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.21.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} */ - function _makeCriteria (sorters) { - return sorters && sorters.length ? map(sorters, _makeCriterion) : [_sorter()]; - } + var countBy = _curry2(count, true); /** - * Converts a sorting function to a sorting criterion if necessary. - * @private - * @param {Function} criterion - * @returns {Sorter} + * Builds an array comprised of all values of the array-like object passing the predicate + * test.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example + * var isLowerCase = function (s) { return s.toLowerCase() === s; }; + * + * _.filter(["Foo", "bar", "baZ"], isLowerCase) // => ["bar"] + * + * // the function will work with any array-like object + * _.filter("fooBAR", isLowerCase) // => ["f", "o", "o"] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.filterWith|filterWith} + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @since 0.1.0 + * @returns {Array} */ - function _makeCriterion (criterion) { - return criterion && typeof criterion.compare === "function" ? criterion : _sorter(criterion); + function filter (arrayLike, predicate) { + var len = arrayLike.length; + var result = []; + + for (var i = 0; i < len; i++) { + predicate(arrayLike[i], i, arrayLike) && result.push(arrayLike[i]); + } + + return result; } /** - * Builds a partial application of a ternary function so that its first parameter - * is expected as the last one.
- * The shouldAritize parameter is for the "reduce" functions, where - * the absence of the initialValue transforms a "fold" operation into a - * "reduce" one. - * @private - * @param {Function} fn - * @param {Boolean} shouldAritize + * Returns a predicate that negates the given one. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var isOdd = _.not(isEven); + * + * isOdd(5) // => true + * isOdd(4) // => false + * + * @memberof module:lamb + * @category Logic + * @since 0.1.0 + * @param {Function} predicate * @returns {Function} */ - function _makePartial3 (fn, shouldAritize) { - return function (a, b) { - var f = shouldAritize && arguments.length !== 2 ? binary(fn) : fn; - - return partial(f, [_, a, b]); + function not (predicate) { + return function () { + return !predicate.apply(this, arguments); }; } /** - * Builds a reduce function. The step parameter must be 1 - * to build {@link module:lamb.reduce|reduce} and -1 to build - * {@link module:lamb.reduceRight|reduceRight}. - * @private - * @param {Number} step - * @returns {Function} - */ - function _makeReducer (step) { - return function (arrayLike, accumulator, initialValue) { - var len = _toArrayLength(arrayLike.length); - var idx = step === 1 ? 0 : len - 1; - var nCalls; - var result; - - if (arguments.length === 3) { - nCalls = len; - result = initialValue; - } else { - if (len === 0) { - throw new TypeError("Reduce of empty array-like with no initial value"); - } + * Using the provided iteratee, builds a function that will return an array comprised of the + * unique elements of an array-like object. The values being compared are the ones returned by + * the iteratee.
+ * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.uniques|uniques} if you don't need to transform your values before the + * comparison. + * @example + * var data = [ + * {id: "1", name: "John"}, + * {id: "4", name: "Jane"}, + * {id: "5", name: "Joe"}, + * {id: "1", name: "Mario"}, + * {id: "5", name: "Paolo"}, + * ]; + * var uniquesById = _.uniquesBy(_.getKey("id")); + * + * uniquesById(data) // => [{id: "1", name: "John"}, {id: "4", name: "Jane"}, {id: "5", name: "Joe"}] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.uniques|uniques} + * @since 0.51.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} + */ + function uniquesBy (iteratee) { + return function (arrayLike) { + var result = []; - result = arrayLike[idx]; - idx += step; - nCalls = len - 1; - } + for (var i = 0, len = arrayLike.length, seen = [], value; i < len; i++) { + value = iteratee(arrayLike[i], i, arrayLike); - for (; nCalls--; idx += step) { - result = accumulator(result, arrayLike[idx], idx, arrayLike); + if (!isIn(seen, value)) { + seen.push(value); + result.push(arrayLike[i]); + } } return result; @@ -945,324 +1036,164 @@ } /** - * Builds a TypeError stating that it's not possible to convert the given value to the - * desired type. - * @private - * @param {*} value - * @param {String} desiredType - * @returns {TypeError} + * Returns an array comprised of the unique elements of the given array-like object.
+ * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} + * to test the equality of values.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.uniquesBy|uniquesBy} if you need to transform your values before + * the comparison or if you have to extract them from complex ones. + * @example + * _.uniques([-0, 1, 2, 0, 2, 3, 4, 3, 5, 1]) // => [-0, 1, 2, 3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.uniquesBy|uniquesBy} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @returns {Array} */ - function _makeTypeErrorFor (value, desiredType) { - return new TypeError("Cannot convert " + type(value).toLowerCase() + " to " + desiredType); - } + var uniques = uniquesBy(identity); /** - * Merges the received objects using the provided function to retrieve their keys. - * @private - * @param {Function} getKeys - * @param {Object} a - * @param {Object} b - * @returns {Function} + * Returns an array of unique items present only in the first of the two given + * array-like objects. To determine uniqueness the function uses the + * ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * @example + * var a1 = [1, 2, 1, 3, 4]; + * var a2 = [2, 4, 5, 6]; + * var a3 = [3, 4, 5, 2, 1]; + * + * _.difference(a1, a2) // => [1, 3] + * _.difference(a2, a3) // => [6] + * _.difference(a1, a3) // => [] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.intersection|intersection} + * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} + * @see {@link module:lamb.pull|pull}, {@link module:lamb.pullFrom|pullFrom} + * @since 0.6.0 + * @param {ArrayLike} arrayLike + * @param {ArrayLike} other + * @returns {Array} */ - function _merge (getKeys, a, b) { - return reduce([a, b], function (result, source) { - forEach(getKeys(source), function (key) { - result[key] = source[key]; - }); + function difference (arrayLike, other) { + var isNotInOther = partial(not(isIn), [other]); - return result; - }, {}); + return uniques(filter(arrayLike, isNotInOther)); } /** - * Using the provided function to retrieve the keys, builds a new function - * expecting an object to create a list of key / value pairs. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} + * Builds an array without the first n elements of the given array or array-like object. + * Note that, being this only a shortcut for a specific use case of {@link module:lamb.slice|slice}, + * n can be a negative number. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.dropFrom(arr, 2) // => [3, 4, 5] + * _.dropFrom(arr, -1) // => [5] + * _.dropFrom(arr, -10) // => [1, 2, 3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @since 0.51.0 + * @param {ArrayLike} arrayLike + * @param {Number} n + * @returns {Array} */ - var _pairsFrom = _curry2(function (getKeys, obj) { - return map(getKeys(obj), _keyToPairIn(obj)); - }); + function dropFrom (arrayLike, n) { + return slice(arrayLike, n, arrayLike.length); + } /** - * A null-safe function to repeat the source string the desired amount of times. - * @private - * @param {String} source - * @param {Number} times - * @returns {String} + * A curried version of {@link module:lamb.dropFrom|dropFrom} that expects the number of elements + * to drop to build a function waiting for the list to take the elements from.
+ * See the note and examples for {@link module:lamb.dropFrom|dropFrom} about passing a + * negative n. + * @example + * var drop2 = _.drop(2); + * + * drop2([1, 2, 3, 4, 5]) // => [3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @function + * @since 0.5.0 + * @see {@link module:lamb.dropFrom|dropFrom} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @param {Number} n + * @returns {Function} */ - function _repeat (source, times) { - var result = ""; - - for (var i = 0; i < times; i++) { - result += source; - } - - return result; - } + var drop = _curry2(dropFrom, true); /** - * Builds a list of the enumerable properties of an object. - * The function is null-safe, unlike the public one. + * Gets the number of consecutive elements satisfying a predicate in an array-like object. * @private - * @param {Object} obj - * @returns {String[]} + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Number} */ - function _safeEnumerables (obj) { - var result = []; + function _getNumConsecutiveHits (arrayLike, predicate) { + var idx = 0; + var len = arrayLike.length; - for (var key in obj) { - result.push(key); + while (idx < len && predicate(arrayLike[idx], idx, arrayLike)) { + idx++; } - return result; + return idx; } /** - * A null-safe version of Object.keys. - * @private - * @function - * @param {Object} obj - * @returns {String[]} - */ - var _safeKeys = compose(Object.keys, Object); - - /** - * A generic version of String.prototype.search - * @private - * @function - * @param {String} s - * @param {RegExp} pattern - * @return {Number} + * Builds a function that drops the first n elements satisfying a predicate + * from an array or array-like object. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var dropWhileIsEven = _.dropWhile(isEven); + * + * dropWhileIsEven([2, 4, 6, 8]) // => [] + * dropWhileIsEven([2, 4, 7, 8]) // => [7, 8] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.takeWhile|takeWhile} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @since 0.5.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} */ - var _search = generic(_stringProto.search); + function dropWhile (predicate) { + return function (arrayLike) { + return slice(arrayLike, _getNumConsecutiveHits(arrayLike, predicate), arrayLike.length); + }; + } /** - * Sets, or creates, a property in a copy of the provided object to the desired value. + * Helper to build the {@link module:lamb.everyIn|everyIn} or the + * {@link module:lamb.someIn|someIn} function. * @private - * @param {Object} source - * @param {String} key - * @param {*} value - * @returns {Object} + * @param {Boolean} defaultResult + * @returns {Function} */ - function _setIn (source, key, value) { - var result = {}; - - for (var prop in source) { - result[prop] = source[prop]; - } - - result[key] = value; + function _makeArrayChecker (defaultResult) { + return function (arrayLike, predicate) { + for (var i = 0, len = arrayLike.length; i < len; i++) { + if (defaultResult ^ !!predicate(arrayLike[i], i, arrayLike)) { + return !defaultResult; + } + } - return result; + return defaultResult; + }; } - /** - * Sets an index in an array-like object.
- * If provided with an updater function it will use it to update the current value, - * otherwise sets the index to the specified value. - * @private - * @param {ArrayLike} arrayLike - * @param {Number} idx - * @param {*} [value] - * @param {Function} [updater] - * @returns {Array} - */ - function _setIndex (arrayLike, idx, value, updater) { - var result = slice(arrayLike, 0, arrayLike.length); - var n = _toNaturalIndex(idx, result.length); - - if (n === n) { // eslint-disable-line no-self-compare - result[n] = arguments.length === 4 ? updater(arrayLike[n]) : value; - } - - return result; - } - - /** - * Sets the object's property targeted by the given path to the desired value.
- * Works with arrays and is able to set their indexes, even negative ones. - * @private - * @param {Object|Array} obj - * @param {String[]} parts - * @param {*} value - * @returns {Object|Array} - */ - function _setPathIn (obj, parts, value) { - var key = parts[0]; - var partsLen = parts.length; - var v; - - if (partsLen === 1) { - v = value; - } else { - var targetKey = _getPathKey(obj, key, false); - - v = _setPathIn( - isUndefined(targetKey) ? targetKey : obj[targetKey], - slice(parts, 1, partsLen), - value - ); - } - - return _isArrayIndex(obj, key) ? _setIndex(obj, key, v) : _setIn(obj, key, v); - } - - /** - * Builds a sorting criterion. If the comparer function is missing, the default - * comparer will be used instead. - * @private - * @param {Function} reader - * @param {Boolean} isDescending - * @param {Function} [comparer] - * @returns {Sorter} - */ - function _sorter (reader, isDescending, comparer) { - if (typeof reader !== "function" || reader === identity) { - reader = null; - } - - if (typeof comparer !== "function") { - comparer = _comparer; - } - - return { - isDescending: isDescending === true, - compare: function (a, b) { - if (reader) { - a = reader(a); - b = reader(b); - } - - return comparer(a, b); - } - }; - } - - /** - * Using the provided function to retrieve the keys of an object, builds - * a function expecting an object to create an array containing a list - * of the keys in its first index and the corresponding list of values - * in the second one. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} - */ - var _tearFrom = _curry2(function (getKeys, obj) { - return reduce(getKeys(obj), function (result, key) { - result[0].push(key); - result[1].push(obj[key]); - - return result; - }, [[], []]); - }); - - /** - * Converts a value to a valid array length, thus an integer within - * 0 and 232 - 1 (both included). - * @private - * @param {*} value - * @returns {Number} - */ - function _toArrayLength (value) { - return clamp(value, 0, MAX_ARRAY_LENGTH) >>> 0; - } - - /** - * Converts a value to an integer. - * @private - * @param {*} value - * @returns {Number} - */ - function _toInteger (value) { - var n = +value; - - if (n !== n) { // eslint-disable-line no-self-compare - return 0; - } else if (n % 1 === 0) { - return n; - } else { - return Math.floor(Math.abs(n)) * (n < 0 ? -1 : 1); - } - } - - /** - * Checks if the given number, even negative, represents an array-like index - * within the provided length. If so returns its natural number equivalent.
- * Returns NaN otherwise. - * @private - * @param {Number} idx - * @param {Number} len - * @returns {Number} - */ - function _toNaturalIndex (idx, len) { - idx = _toInteger(idx); - - return idx >= -len && idx < len ? idx < 0 ? idx + len : idx : NaN; - } - - /** - * Splits a sting path using the provided separator and returns an array - * of path parts. - * @private - * @param {String} path - * @param {String} separator - * @returns {String[]} - */ - function _toPathParts (path, separator) { - return String(path).split(separator || "."); - } - - /** - * Creates a non-null-safe version of the provided "getKeys" function. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} - */ - var _unsafeKeyListFrom = _curry2(function (getKeys, obj) { - if (isNil(obj)) { - throw _makeTypeErrorFor(obj, "object"); - } - - return getKeys(obj); - }); - - /** - * Using the provided function to retrieve the keys of an object, builds - * a function expecting an object to create the list of values for such keys. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} - */ - var _valuesFrom = _curry2(function (getKeys, obj) { - return map(getKeys(obj), partial(getIn, [obj])); - }); - - /** - * Builds a predicate to check if an array-like object contains the given value.
- * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can - * check for NaN, but 0 and -0 are the same value.
- * See also {@link module:lamb.isIn|isIn} for an uncurried version. - * @example - * var containsNaN = _.contains(NaN); - * - * containsNaN([0, 1, 2, 3, NaN]) // => true - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.isIn|isIn} - * @since 0.13.0 - * @param {*} value - * @returns {Function} - */ - var contains = _curry2(isIn, true); - /** * Checks if all the elements in an array-like object satisfy the given predicate.
* The function will stop calling the predicate as soon as it returns a falsy value.
@@ -1327,37 +1258,6 @@ */ var every = _curry2(everyIn, true); - /** - * Builds an array comprised of all values of the array-like object passing the predicate - * test.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @example - * var isLowerCase = function (s) { return s.toLowerCase() === s; }; - * - * _.filter(["Foo", "bar", "baZ"], isLowerCase) // => ["bar"] - * - * // the function will work with any array-like object - * _.filter("fooBAR", isLowerCase) // => ["f", "o", "o"] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.filterWith|filterWith} - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @since 0.1.0 - * @returns {Array} - */ - function filter (arrayLike, predicate) { - var len = arrayLike.length; - var result = []; - - for (var i = 0; i < len; i++) { - predicate(arrayLike[i], i, arrayLike) && result.push(arrayLike[i]); - } - - return result; - } - /** * A curried version of {@link module:lamb.filter|filter} that uses the given predicate * to build a function expecting the array-like object to act upon. @@ -1381,8 +1281,8 @@ var filterWith = _curry2(filter, true); /** - * Searches for an element satisfying the predicate in the given array-like object and returns it if - * the search is successful. Returns undefined otherwise. + * Searches for an element satisfying the predicate in the given array-like object and returns its + * index if the search is successful. Returns -1 otherwise. * @example * var persons = [ * {"name": "Jane", "surname": "Doe", "age": 12}, @@ -1391,27 +1291,34 @@ * {"name": "Paolo", "surname": "Bianchi", "age": 40} * ]; * - * _.find(persons, _.hasKeyValue("age", 40)) // => {"name": "John", "surname": "Doe", "age": 40} - * _.find(persons, _.hasKeyValue("age", 41)) // => undefined + * _.findIndex(persons, _.hasKeyValue("age", 40)) // => 1 + * _.findIndex(persons, _.hasKeyValue("age", 41)) // => -1 * * @memberof module:lamb * @category Array - * @see {@link module:lamb.findWhere|findWhere} - * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} + * @see {@link module:lamb.findIndexWhere|findIndexWhere} + * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} * @since 0.7.0 * @param {ArrayLike} arrayLike * @param {ListIteratorCallback} predicate - * @returns {*} + * @returns {Number} */ - function find (arrayLike, predicate) { - var idx = findIndex(arrayLike, predicate); - - return idx === -1 ? void 0 : arrayLike[idx]; - } + function findIndex (arrayLike, predicate) { + var result = -1; - /** - * Searches for an element satisfying the predicate in the given array-like object and returns its - * index if the search is successful. Returns -1 otherwise. + for (var i = 0, len = arrayLike.length; i < len; i++) { + if (predicate(arrayLike[i], i, arrayLike)) { + result = i; + break; + } + } + + return result; + } + + /** + * Searches for an element satisfying the predicate in the given array-like object and returns it if + * the search is successful. Returns undefined otherwise. * @example * var persons = [ * {"name": "Jane", "surname": "Doe", "age": 12}, @@ -1420,514 +1327,614 @@ * {"name": "Paolo", "surname": "Bianchi", "age": 40} * ]; * - * _.findIndex(persons, _.hasKeyValue("age", 40)) // => 1 - * _.findIndex(persons, _.hasKeyValue("age", 41)) // => -1 + * _.find(persons, _.hasKeyValue("age", 40)) // => {"name": "John", "surname": "Doe", "age": 40} + * _.find(persons, _.hasKeyValue("age", 41)) // => undefined * * @memberof module:lamb * @category Array - * @see {@link module:lamb.findIndexWhere|findIndexWhere} - * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} + * @see {@link module:lamb.findWhere|findWhere} + * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} * @since 0.7.0 * @param {ArrayLike} arrayLike * @param {ListIteratorCallback} predicate - * @returns {Number} + * @returns {*} */ - function findIndex (arrayLike, predicate) { - var result = -1; - - for (var i = 0, len = arrayLike.length; i < len; i++) { - if (predicate(arrayLike[i], i, arrayLike)) { - result = i; - break; - } - } + function find (arrayLike, predicate) { + var idx = findIndex(arrayLike, predicate); - return result; + return idx === -1 ? void 0 : arrayLike[idx]; } /** - * A curried version of {@link module:lamb.findIndex|findIndex} that uses the given predicate - * to build a function expecting the array-like object to search. + * A curried version of {@link module:lamb.find|find} expecting the array-like object + * to search. * @example * var isEven = function (n) { return n % 2 === 0; }; - * var findEvenIdx = _.findIndexWhere(isEven); + * var findEven = _.findWhere(isEven); * - * findEvenIdx([1, 3, 4, 5, 7]) // => 2 - * findEvenIdx([1, 3, 5, 7]) // => -1 + * findEven([1, 3, 4, 5, 7]) // => 4 + * findEven([1, 3, 5, 7]) // => undefined * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.findIndex|findIndex} - * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} + * @see {@link module:lamb.find|find} + * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} * @since 0.41.0 * @param {ListIteratorCallback} predicate * @returns {Function} */ - var findIndexWhere = _curry2(findIndex, true); + var findWhere = _curry2(find, true); /** - * A curried version of {@link module:lamb.find|find} expecting the array-like object - * to search. + * A curried version of {@link module:lamb.findIndex|findIndex} that uses the given predicate + * to build a function expecting the array-like object to search. * @example * var isEven = function (n) { return n % 2 === 0; }; - * var findEven = _.findWhere(isEven); + * var findEvenIdx = _.findIndexWhere(isEven); * - * findEven([1, 3, 4, 5, 7]) // => 4 - * findEven([1, 3, 5, 7]) // => undefined + * findEvenIdx([1, 3, 4, 5, 7]) // => 2 + * findEvenIdx([1, 3, 5, 7]) // => -1 * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.find|find} - * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} + * @see {@link module:lamb.findIndex|findIndex} + * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} * @since 0.41.0 * @param {ListIteratorCallback} predicate * @returns {Function} */ - var findWhere = _curry2(find, true); + var findIndexWhere = _curry2(findIndex, true); /** - * Executes the provided iteratee for each element of the given array-like object.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @example Adding a CSS class to all elements of a NodeList in a browser environment: - * var addClass = _.curry(function (className, element) { - * element.classList.add(className); - * }); - * var paragraphs = document.querySelectorAll("#some-container p"); + * Similar to {@link module:lamb.map|map}, but if the mapping function returns an array this will + * be concatenated, rather than pushed, to the final result. + * @example Showing the difference with map: + * var words = ["foo", "bar"]; + * var toCharArray = function (s) { return s.split(""); }; * - * _.forEach(paragraphs, addClass("main")); - * // each "p" element in the container will have the "main" class now + * _.map(words, toCharArray) // => [["f", "o", "o"], ["b", "a", "r"]] + * _.flatMap(words, toCharArray) // => ["f", "o", "o", "b", "a", "r"] * * @memberof module:lamb * @category Array + * @see {@link module:lamb.flatMapWith|flatMapWith} + * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} * @since 0.1.0 - * @param {ArrayLike} arrayLike + * @param {Array} array * @param {ListIteratorCallback} iteratee - * @returns {Undefined} + * @returns {Array} */ - function forEach (arrayLike, iteratee) { - for (var i = 0, len = _toArrayLength(arrayLike.length); i < len; i++) { - iteratee(arrayLike[i], i, arrayLike); - } + function flatMap (array, iteratee) { + return reduce(array, function (result, el, idx, arr) { + var v = iteratee(el, idx, arr); + + if (!Array.isArray(v)) { + v = [v]; + } + + for (var i = 0, len = v.length, rLen = result.length; i < len; i++) { + result[rLen + i] = v[i]; + } + + return result; + }, []); } /** - * Checks if an array-like object contains the given value.
- * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can - * check for NaN, but 0 and -0 are the same value.
- * See also {@link module:lamb.contains|contains} for a curried version building a predicate. + * A curried version of {@link module:lamb.flatMap|flatMap} that uses provided iteratee + * to build a function expecting the array to act upon. * @example - * var numbers = [0, 1, 2, 3, NaN]; + * var toCharArray = function (s) { return s.split(""); }; + * var wordsToCharArray = _.flatMapWith(toCharArray); * - * _.isIn(numbers, 1) // => true - * _.isIn(numbers, 0) // => true - * _.isIn(numbers, -0) // => true - * _.isIn(numbers, NaN) // => true - * _.isIn(numbers, 5) // => false + * wordsToCharArray(["foo", "bar"]) // => ["f", "o", "o", "b", "a", "r"] * * @memberof module:lamb * @category Array - * @see {@link module:lamb.contains|contains} - * @since 0.13.0 - * @param {ArrayLike} arrayLike - * @param {*} value - * @returns {Boolean} + * @function + * @see {@link module:lamb.flatMap|flatMap} + * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} + * @since 0.11.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} */ - function isIn (arrayLike, value) { - var result = false; + var flatMapWith = _curry2(flatMap, true); - for (var i = 0, len = arrayLike.length; i < len; i++) { - if (areSVZ(value, arrayLike[i])) { - result = true; - break; + /** + * Flattens an array. + * @private + * @param {Array} array - The source array + * @param {Boolean} isDeep - Whether to perform a deep flattening or not + * @param {Array} output - An array to collect the result + * @param {Number} idx - The next index to be filled in the output + * @returns {Array} The output array filled with the results + */ + function _flatten (array, isDeep, output, idx) { + for (var i = 0, len = array.length, value, j, vLen; i < len; i++) { + value = array[i]; + + if (!Array.isArray(value)) { + output[idx++] = value; + } else if (isDeep) { + _flatten(value, true, output, idx); + idx = output.length; + } else { + vLen = value.length; + output.length += vLen; + + for (j = 0; j < vLen; j++) { + output[idx++] = value[j]; + } } } - return result; + return output; } /** - * Generates an array with the values passed as arguments.
- * Behaves like ES6's [Array.of]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of}. - * @example - * _.list(1, 2, 3) // => [1, 2, 3] - * - * @memberof module:lamb - * @category Array + * Helper to build the {@link module:lamb.flatten|flatten} and + * {@link module:lamb.shallowFlatten|shallowFlatten} functions. + * @private * @function - * @since 0.1.0 - * @param {...*} value - * @returns {Array} + * @param {Boolean} isDeep + * @returns {Function} */ - var list = _argsToArrayFrom(0); + var _makeArrayFlattener = _curry2(function (isDeep, array) { + return Array.isArray(array) ? _flatten(array, isDeep, [], 0) : slice(array, 0, array.length); + }); /** - * Builds a new array by applying the iteratee function to each element of the - * received array-like object.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @example - * _.map(["Joe", "Mario", "Jane"], _.invoker("toUpperCase")) // => ["JOE", "MARIO", "JANE"] + * Flattens an array. + * @example Showing the difference with shallowFlatten: + * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; * - * _.map([4, 9, 16], Math.sqrt); // => [2, 3, 4] + * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] + * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] * * @memberof module:lamb * @category Array - * @see {@link module:lamb.mapWith|mapWith} - * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} + * @function + * @see {@link module:lamb.shallowFlatten|shallowFlatten} * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee + * @param {Array} array * @returns {Array} */ - function map (arrayLike, iteratee) { - var len = _toArrayLength(arrayLike.length); - var result = Array(len); + var flatten = _makeArrayFlattener(true); - for (var i = 0; i < len; i++) { - result[i] = iteratee(arrayLike[i], i, arrayLike); - } + /** + * Checks if the given number, even negative, represents an array-like index + * within the provided length. If so returns its natural number equivalent.
+ * Returns NaN otherwise. + * @private + * @param {Number} idx + * @param {Number} len + * @returns {Number} + */ + function _toNaturalIndex (idx, len) { + idx = _toInteger(idx); - return result; + return idx >= -len && idx < len ? idx < 0 ? idx + len : idx : NaN; } /** - * A curried version of {@link module:lamb.map|map} that uses the provided iteratee to - * build a function expecting the array-like object to act upon. + * Retrieves the element at the given index in an array-like object.
+ * Like {@link module:lamb.slice|slice} the index can be negative.
+ * If the index isn't supplied, or if its value isn't an integer within the array-like bounds, + * the function will return undefined.
+ * getIndex will throw an exception when receives null or + * undefined in place of an array-like object, but returns undefined + * for any other value. * @example - * var square = function (n) { return n * n; }; - * var getSquares = _.mapWith(square); + * var arr = [1, 2, 3, 4, 5]; * - * getSquares([1, 2, 3, 4, 5]) // => [1, 4, 9, 16, 25] + * _.getIndex(arr, 1) // => 2 + * _.getIndex(arr, -1) // => 5 * * @memberof module:lamb * @category Array - * @function - * @see {@link module:lamb.map|map} - * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} - * @since 0.1.0 - * @param {ListIteratorCallback} iteratee - * @returns {function} + * @see {@link module:lamb.getAt|getAt} + * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. + * @since 0.23.0 + * @param {ArrayLike} arrayLike + * @param {Number} index + * @returns {*} */ - var mapWith = _curry2(map, true); - + function getIndex (arrayLike, index) { + var idx = _toNaturalIndex(index, _toArrayLength(arrayLike.length)); + + return idx === idx ? arrayLike[idx] : void 0; // eslint-disable-line no-self-compare + } + /** - * Reduces (or folds) the values of an array-like object, starting from the first, to a new - * value using the provided accumulator function.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * A curried version of {@link module:lamb.getIndex|getIndex} that uses the provided index + * to build a function expecting the array-like object holding the element we want to retrieve. * @example - * _.reduce([1, 2, 3, 4], _.sum) // => 10 + * var getFifthElement = _.getAt(4); + * + * getFifthElement([1, 2, 3, 4, 5]) // => 5 + * getFifthElement("foo bar") // => "b" + * getFifthElement([]) // => undefined + * getFifthElement("foo") // => undefined + * + * @example Using negative indexes: + * _.getAt(-2)([1, 2, 3]) // => 2 + * _.getAt(-3)("foo") // => "f" * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.reduceRight|reduceRight} - * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] - * @returns {*} + * @since 0.16.0 + * @see {@link module:lamb.getIndex|getIndex} + * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. + * @param {Number} index + * @returns {Function} */ - var reduce = _makeReducer(1); + var getAt = _curry2(getIndex, true); /** - * Same as {@link module:lamb.reduce|reduce}, but starts the fold operation from the last - * element instead.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * Transforms an array-like object into a lookup table using the provided iteratee as a grouping + * criterion to generate keys and values. + * @example + * var persons = [ + * {"name": "Jane", "city": "New York"}, + * {"name": "John", "city": "New York"}, + * {"name": "Mario", "city": "Rome"}, + * {"name": "Paolo"} + * ]; + * var getCity = _.getKey("city"); + * var personsByCity = _.group(persons, getCity); + * + * // "personsByCity" holds: + * // { + * // "New York": [ + * // {"name": "Jane", "city": "New York"}, + * // {"name": "John", "city": "New York"} + * // ], + * // "Rome": [ + * // {"name": "Mario", "city": "Rome"} + * // ], + * // "undefined": [ + * // {"name": "Paolo"} + * // ] + * // } + * + * @example Adding a custom value for missing keys: + * + * var getCityOrUnknown = _.adapter([getCity, _.always("Unknown")]); + * + * var personsByCity = _.group(persons, getCityOrUnknown); + * + * // "personsByCity" holds: + * // { + * // "New York": [ + * // {"name": "Jane", "city": "New York"}, + * // {"name": "John", "city": "New York"} + * // ], + * // "Rome": [ + * // {"name": "Mario", "city": "Rome"} + * // ], + * // "Unknown": [ + * // {"name": "Paolo"} + * // ] + * // } + * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.reduce|reduce} - * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} - * @since 0.1.0 + * @see {@link module:lamb.groupBy|groupBy} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.7.0 * @param {ArrayLike} arrayLike - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] - * @returns {*} + * @param {ListIteratorCallback} iteratee + * @returns {Object} */ - var reduceRight = _makeReducer(-1); + var group = _groupWith(function (a, b) { + if (!a) { + return [b]; + } + + a[a.length] = b; + + return a; + }); /** - * A partial application of {@link module:lamb.reduce|reduceRight} that uses the - * provided accumulator and the optional initialValue to - * build a function expecting the array-like object to act upon. + * A curried version of {@link module:lamb.group|group} that uses the provided iteratee + * to build a function expecting the array-like object to act upon. * @example - * var arr = [1, 2, 3, 4, 5]; + * var persons = [ + * {"name": "Jane", "age": 12}, + * {"name": "John", "age": 40}, + * {"name": "Mario", "age": 18}, + * {"name": "Paolo", "age": 15} + * ]; * - * _.reduceRightWith(_.sum)(arr) // => 15 - * _.reduceRightWith(_.subtract)(arr) // => -5 - * _.reduceRightWith(_.subtract, 0)(arr) // => -15 + * var getAgeStatus = function (person) { return person.age > 20 ? "over 20" : "under 20"; }; + * var groupByAgeStatus = _.groupBy(getAgeStatus); + * + * var personsByAgeStatus = groupByAgeStatus(persons); + * + * // "personsByAgeStatus" holds: + * // { + * // "under 20": [ + * // {"name": "Jane", "age": 12}, + * // {"name": "Mario", "age": 18}, + * // {"name": "Paolo", "age": 15} + * // ], + * // "over 20": [ + * // {"name": "John", "age": 40} + * // ] + * // } * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.reduceWith|reduceWith} - * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} - * @since 0.27.0 - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] + * @see {@link module:lamb.group|group} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.7.0 + * @param {ListIteratorCallback} iteratee * @returns {Function} */ - var reduceRightWith = _makePartial3(reduceRight, true); + var groupBy = _curry2(group, true); /** - * A partial application of {@link module:lamb.reduce|reduce} that uses the - * provided accumulator and the optional initialValue to - * build a function expecting the array-like object to act upon. + * Retrieves the first element of an array-like object.
+ * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.reduceWith(_.sum)(arr) // => 15 - * _.reduceWith(_.subtract)(arr) // => -13 - * _.reduceWith(_.subtract, 0)(arr) // => -15 + * _.head([1, 2, 3]) // => 1 + * _.head("hello") // => "h" + * _.head([]) // => undefined * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.reduceRightWith|reduceRightWith} - * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} - * @since 0.27.0 - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] - * @returns {Function} + * @see {@link module:lamb.last|last} + * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {*} */ - var reduceWith = _makePartial3(reduce, true); + var head = getAt(0); /** - * Reverses a copy of the given array-like object. + * Similar to {@link module:lamb.group|group}, but the generated lookup table will have + * only one element of the original array-like object for each value.
+ * Should be used only when you're sure that your iteratee won't produce + * duplicate keys, otherwise only the last evaluated element will be in the result. * @example - * var arr = [1, 2, 3]; + * var users = [ + * {id: 1, name: "John"}, + * {id: 2, name: "Jane"}, + * {id: 3, name: "Mario"}, + * {id: 4, name: "John"} + * ]; * - * _.reverse(arr) // => [3, 2, 1]; + * var indexedUsers = _.index(users, _.getKey("id")); * - * // `arr` still is [1, 2, 3] + * // "indexedUsers" holds: + * // { + * // "1": {id: 1, name: "John"}, + * // "2": {id: 2, name: "Jane"}, + * // "3": {id: 3, name: "Mario"}, + * // "4": {id: 4, name: "John"} + * // } + * + * @example Result of an iteratee producing a duplicate key: + * var users = [ + * {id: 1, name: "John"}, + * {id: 2, name: "Jane"}, + * {id: 3, name: "Mario"}, + * {id: 4, name: "John"} + * ]; + * + * var indexedUsers = _.index(users, _.getKey("name")); + * + * // "indexedUsers" holds: + * // { + * // "John": {"id": 4, "name": "John"}, + * // "Jane": {"id": 2, "name": "Jane"}, + * // "Mario": {"id": 3, "name": "Mario"} + * // } * * @memberof module:lamb * @category Array - * @since 0.19.0 + * @function + * @see {@link module:lamb.indexBy|indexBy} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @since 0.21.0 * @param {ArrayLike} arrayLike - * @returns {Array} + * @param {ListIteratorCallback} iteratee + * @returns {Object} */ - function reverse (arrayLike) { - var len = _toArrayLength(arrayLike.length); - var result = Array(len); - - for (var i = 0, ofs = len - 1; i < len; i++) { - result[i] = arrayLike[ofs - i]; - } - - return result; - } + var index = _groupWith(function (a, b) { + return b; + }); /** - * Builds an array by extracting a portion of an array-like object.
- * Note that unlike the native array method this function ensures that dense - * arrays are returned.
- * Also, unlike the native method, the start and end - * parameters aren't optional and will be simply converted to integer.
- * See {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a - * slice to the end of the array-like. + * A curried version of {@link module:lamb.index|index} that uses the provided iteratee + * to build a function expecting the array-like object to act upon. * @example - * var arr = [1, 2, 3, 4, 5]; + * var users = [ + * {id: 1, name: "John"}, + * {id: 2, name: "Jane"}, + * {id: 3, name: "Mario"} + * ]; + * var indexByID = _.indexBy(_.getKey("id")); * - * _.slice(arr, 0, 2) // => [1, 2] - * _.slice(arr, 2, -1) // => [3, 4] - * _.slice(arr, -3, 5) // => [3, 4, 5] + * var indexedUsers = indexByID(users); + * + * // "indexedUsers" holds: + * // { + * // "1": {id: 1, name: "John"}, + * // "2": {id: 2, name: "Jane"}, + * // "3": {id: 3, name: "Mario"} + * // } * * @memberof module:lamb * @category Array - * @see {@link module:lamb.sliceAt|sliceAt} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - Any array like object. - * @param {Number} start - Index at which to begin extraction. - * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. - * @returns {Array} + * @function + * @see {@link module:lamb.index|index} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @since 0.21.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} */ - function slice (arrayLike, start, end) { - var len = _toArrayLength(arrayLike.length); - var begin = _toInteger(start); - var upTo = _toInteger(end); - - if (begin < 0) { - begin = begin < -len ? 0 : begin + len; - } + var indexBy = _curry2(index, true); - if (upTo < 0) { - upTo = upTo < -len ? 0 : upTo + len; - } else if (upTo > len) { - upTo = len; - } + /** + * Returns a copy of the given array-like object without the last element. + * @example + * _.init([1, 2, 3, 4]) // => [1, 2, 3] + * _.init([1]) // => [] + * _.init([]) // => [] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.tail|tail} + * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {Array} + */ + var init = partial(slice, [__, 0, -1]); - var resultLen = upTo - begin; - var result = resultLen > 0 ? Array(resultLen) : []; + /** + * Inserts the provided element in a copy of an array-like object at the + * specified index.
+ * If the index is greater than the length of the array-like, the element + * will be appended at the end.
+ * Negative indexes are allowed; when a negative index is out of bounds + * the element will be inserted at the start of the resulting array. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.insert(arr, 3, 99) // => [1, 2, 3, 99, 4, 5] + * _.insert(arr, -2, 99) // => [1, 2, 3, 99, 4, 5] + * _.insert(arr, 10, 99) // => [1, 2, 3, 4, 5, 99] + * _.insert(arr, -10, 99) // => [99, 1, 2, 3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.insertAt|insertAt} + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {Number} index + * @param {*} element + * @returns {Array} + */ + function insert (arrayLike, index, element) { + var result = slice(arrayLike, 0, arrayLike.length); - for (var i = 0; i < resultLen; i++) { - result[i] = arrayLike[begin + i]; - } + result.splice(index, 0, element); return result; } /** - * Given the start and end bounds, builds a partial application - * of {@link module:lamb.slice|slice} expecting the array-like object to slice.
- * See also {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a - * slice to the end of the array-like. + * Builds a partial application of {@link module:lamb.insert|insert} + * expecting the array-like object to act upon. * @example * var arr = [1, 2, 3, 4, 5]; - * var s = "hello"; - * var dropFirstAndLast = _.sliceAt(1, -1); * - * dropFirstAndLast(arr) // => [2, 3, 4] - * dropFirstAndLast(s) // => ["e", "l", "l"] + * _.insertAt(3, 99)(arr) // => [1, 2, 3, 99, 4, 5] + * _.insertAt(-2, 99)(arr) // => [1, 2, 3, 99, 4, 5] + * _.insertAt(10, 99)(arr) // => [1, 2, 3, 4, 5, 99] + * _.insertAt(-10, 99)(arr) // => [99, 1, 2, 3, 4, 5] * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.slice|slice} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @since 0.48.0 - * @param {Number} start - Index at which to begin extraction. - * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. + * @see {@link module:lamb.insert|insert} + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} + * @since 0.27.0 + * @param {Number} index + * @param {*} element * @returns {Function} */ - var sliceAt = _makePartial3(slice); + var insertAt = _makePartial3(insert); /** - * Checks if at least one element in an array-like object satisfies the given predicate.
- * The function will stop calling the predicate as soon as it returns a truthy value.
- * Note that unlike the native - * [Array.prototype.some]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some}, - * this function won't skip deleted or unassigned indexes. + * Returns an array of every unique item that is included in all two given arrays + * or array-like objects.
+ * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. * @example - * var persons = [ - * {"name": "Jane", "age": 12, active: false}, - * {"name": "John", "age": 40, active: false}, - * {"name": "Mario", "age": 17, active: false}, - * {"name": "Paolo", "age": 15, active: false} - * ]; - * var isAdult = _.keySatisfies(_.isGTE(18), "age"); - * var isActive = _.hasKeyValue("active", true); - * - * _.someIn(persons, isAdult) // => true - * _.someIn(persons, isActive) // => false - * - * @example Showing the difference with Array.prototype.some: - * var arr = new Array(5); - * arr[3] = 99; + * var a1 = [1, 2, 3, 4]; + * var a2 = [2, 5, 4, 2, 6]; + * var a3 = [5, 6, 7]; * - * arr.some(_.isUndefined) // => false - * _.someIn(arr, _.isUndefined) // => true + * _.intersection(a1, a2) // => [2, 4] + * _.intersection(a2, a3) // => [5, 6] + * _.intersection(a1, a3) // => [] * * @memberof module:lamb * @category Array - * @function - * @see {@link module:lamb.some|some} - * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} - * @since 0.39.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Boolean} + * @see {@link module:lamb.difference|difference} + * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} + * @since 0.5.0 + * @param {ArrayLike} a + * @param {ArrayLike} b + * @returns {Array} */ - var someIn = _makeArrayChecker(false); + function intersection (a, b) { + var result = []; + var lenA = a.length; + + if (lenA && b.length) { + for (var i = 0; i < lenA; i++) { + !isIn(result, a[i]) && isIn(b, a[i]) && result.push(a[i]); + } + } + + return result; + } /** - * A curried version of {@link module:lamb.someIn|someIn} that uses the given predicate to - * build a function waiting for the array-like to act upon. + * Retrieves the last element of an array-like object.
+ * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. * @example - * var data = [1, 3, 5, 6, 7, 8]; - * var isEven = function (n) { return n % 2 === 0; }; - * var containsEvens = _.some(isEven); - * var containsStrings = _.some(_.isType("String")); - * - * containsEvens(data) // => true - * containsStrings(data) // => false + * _.last([1, 2, 3]) // => 3 + * _.last("hello") // => "o" + * _.last([]) // => undefined * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.someIn|someIn} - * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} - * @since 0.39.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} + * @see {@link module:lamb.head|head} + * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {*} */ - var some = _curry2(someIn, true); - - lamb.contains = contains; - lamb.every = every; - lamb.everyIn = everyIn; - lamb.filter = filter; - lamb.filterWith = filterWith; - lamb.find = find; - lamb.findIndex = findIndex; - lamb.findIndexWhere = findIndexWhere; - lamb.findWhere = findWhere; - lamb.forEach = forEach; - lamb.isIn = isIn; - lamb.list = list; - lamb.map = map; - lamb.mapWith = mapWith; - lamb.reduce = reduce; - lamb.reduceRight = reduceRight; - lamb.reduceRightWith = reduceRightWith; - lamb.reduceWith = reduceWith; - lamb.reverse = reverse; - lamb.slice = slice; - lamb.sliceAt = sliceAt; - lamb.some = some; - lamb.someIn = someIn; + var last = getAt(-1); /** - * Accepts a series of functions and builds a function that applies the received - * arguments to each one and returns the first non-undefined value.
- * Meant to work in synergy with {@link module:lamb.case|case} and - * {@link module:lamb.invoker|invoker}, can be useful as a strategy pattern for functions, - * to mimic conditional logic or pattern matching, and also to build polymorphic functions. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var filterString = _.compose(_.invoker("join", ""), _.filter); - * var filterAdapter = _.adapter([ - * _.invoker("filter"), - * _.case(_.isType("String"), filterString) - * ]); - * - * filterAdapter([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] - * filterAdapter("123456", isEven) // => "246" - * filterAdapter({}, isEven) // => undefined - * - * // by its nature is composable - * var filterWithDefault = _.adapter([filterAdapter, _.always("Not implemented")]); - * - * filterWithDefault([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] - * filterWithDefault("123456", isEven) // => "246" - * filterWithDefault({}, isEven) // => "Not implemented" - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.case|case} - * @see {@link module:lamb.invoker|invoker} - * @since 0.6.0 - * @param {Function[]} functions + * Builds helper functions to extract portions of the arguments + * object rather efficiently without having to write for loops + * manually for each case.
+ * The arguments object needs to be passed to the apply method + * of the generated function. + * @private + * @param {Number} idx * @returns {Function} */ - function adapter (functions) { - if (!Array.isArray(functions)) { - throw _makeTypeErrorFor(functions, "array"); - } - + function _argsToArrayFrom (idx) { return function () { - var len = functions.length; - var result; + var argsLen = arguments.length || idx; + var len = argsLen - idx; + var result = Array(len); for (var i = 0; i < len; i++) { - result = functions[i].apply(this, arguments); - - if (!isUndefined(result)) { - break; - } + result[i] = arguments[i + idx]; } return result; @@ -1935,3836 +1942,3304 @@ } /** - * Accepts an array of predicates and builds a new one that returns true if they are all satisfied - * by the same arguments. The functions in the array will be applied one at a time until a - * false value is produced, which is returned immediately. + * Generates an array with the values passed as arguments.
+ * Behaves like ES6's [Array.of]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of}. * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var isPositiveEven = _.allOf([isEven, _.isGT(0)]); - * - * isPositiveEven(-2) // => false - * isPositiveEven(11) // => false - * isPositiveEven(6) // => true + * _.list(1, 2, 3) // => [1, 2, 3] * * @memberof module:lamb - * @category Logic + * @category Array * @function - * @see {@link module:lamb.anyOf|anyOf} * @since 0.1.0 - * @param {Function[]} predicates - * @returns {Function} + * @param {...*} value + * @returns {Array} */ - var allOf = _checkPredicates(true); + var list = _argsToArrayFrom(0); /** - * Accepts an array of predicates and builds a new one that returns true if at least one of them is - * satisfied by the received arguments. The functions in the array will be applied one at a time - * until a true value is produced, which is returned immediately. + * Splits an array-like object in two lists: the first with the elements satisfying the given predicate, + * the others with the remaining elements. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + * + * _.partition(numbers, isEven) // => [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.partitionWith|partitionWith} + * @since 0.11.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Array} + */ + function partition (arrayLike, predicate) { + var result = [[], []]; + var len = arrayLike.length; + + for (var i = 0, el; i < len; i++) { + el = arrayLike[i]; + result[predicate(el, i, arrayLike) ? 0 : 1].push(el); + } + + return result; + } + + /** + * A curried version of {@link module:lamb.partition|partition} that uses the provided + * predicate to build a function expecting the array-like object to act upon. * @example * var users = [ - * {id: 1, name: "John", group: "guest"}, - * {id: 2, name: "Jane", group: "root"}, - * {id: 3, name: "Mario", group: "admin"} + * {"name": "Jane", "surname": "Doe", "active": false}, + * {"name": "John", "surname": "Doe", "active": true}, + * {"name": "Mario", "surname": "Rossi", "active": true}, + * {"name": "Paolo", "surname": "Bianchi", "active": false} * ]; - * var isInGroup = _.partial(_.hasKeyValue, ["group"]); - * var isSuperUser = _.anyOf([isInGroup("admin"), isInGroup("root")]); + * var isActive = _.hasKeyValue("active", true); + * var splitByActiveStatus = _.partitionWith(isActive); * - * isSuperUser(users[0]) // => false - * isSuperUser(users[1]) // => true - * isSuperUser(users[2]) // => true + * splitByActiveStatus(users) // => + * // [[ + * // {"name": "John", "surname": "Doe", "active": true}, + * // {"name": "Mario", "surname": "Rossi", "active": true} + * // ], [ + * // {"name": "Jane", "surname": "Doe", "active": false}, + * // {"name": "Paolo", "surname": "Bianchi", "active": false} + * // ]] * * @memberof module:lamb - * @category Logic + * @category Array * @function - * @see {@link module:lamb.allOf|allOf} - * @since 0.1.0 - * @param {Function[]} predicates + * @see {@link module:lamb.partition|partition} + * @since 0.11.0 + * @param {ListIteratorCallback} predicate * @returns {Function} */ - var anyOf = _checkPredicates(false); + var partitionWith = _curry2(partition, true); /** - * Verifies that the two supplied values are the same value using the "SameValue" comparison.
- * Note that this doesn't behave as the strict equality operator, but rather as a shim of ES6's - * [Object.is]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is}. - * Differences are that 0 and -0 aren't the same value and, finally, - * NaN is equal to itself.
- * See also {@link module:lamb.is|is} for a curried version building a predicate and - * {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} to perform a "SameValueZero" - * comparison. + * Returns the value of the object property with the given key. * @example - * var testObject = {}; + * var user = {name: "John"}; * - * _.areSame({}, testObject) // => false - * _.areSame(testObject, testObject) // => true - * _.areSame("foo", "foo") // => true - * _.areSame(0, -0) // => false - * _.areSame(0 / 0, NaN) // => true + * _.getIn(user, "name") // => "John"; + * _.getIn(user, "surname") // => undefined * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.is|is} - * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} - * @since 0.50.0 - * @param {*} a - * @param {*} b - * @returns {Boolean} + * @category Object + * @see {@link module:lamb.getKey|getKey} + * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} + * @since 0.18.0 + * @param {Object} obj + * @param {String} key + * @returns {*} */ - function areSame (a, b) { - return a === 0 && b === 0 ? 1 / a === 1 / b : areSVZ(a, b); + function getIn (obj, key) { + return obj[key]; } /** - * Verifies that the two supplied values are the same value using the "SameValueZero" comparison.
- * With this comparison NaN is equal to itself, but 0 and -0 are - * considered the same value.
- * See also {@link module:lamb.isSVZ|isSVZ} for a curried version building a predicate and - * {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} to perform a "SameValue" comparison. + * A curried version of {@link module:lamb.getIn|getIn}.
+ * Receives a property name and builds a function expecting the object from which we want to retrieve + * the property. * @example - * var testObject = {}; + * var user1 = {name: "john"}; + * var user2 = {name: "jane"}; + * var getName = _.getKey("name"); * - * _.areSVZ({}, testObject) // => false - * _.areSVZ(testObject, testObject) // => true - * _.areSVZ("foo", "foo") // => true - * _.areSVZ(0, -0) // => true - * _.areSVZ(0 / 0, NaN) // => true + * getName(user1) // => "john" + * getName(user2) // => "jane" * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.isSVZ|isSVZ} - * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} - * @since 0.50.0 - * @param {*} a - * @param {*} b - * @returns {Boolean} + * @category Object + * @function + * @see {@link module:lamb.getIn|getIn} + * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} + * @since 0.1.0 + * @param {String} key + * @returns {Function} */ - function areSVZ (a, b) { - return a !== a ? b !== b : a === b; // eslint-disable-line no-self-compare - } + var getKey = _curry2(getIn, true); /** - * Builds a case for {@link module:lamb.adapter|adapter}.
- * The function will apply the received arguments to fn if the predicate is satisfied - * with the same arguments, otherwise will return undefined.
- * See also {@link module:lamb.condition|condition} to build a condition with two branching functions - * and {@link module:lamb.unless|unless} and {@link module:lamb.when|when} where one of the branches - * is the identity function. + * "Plucks" the values of the specified key from a list of objects. * @example - * var halveIfNumber = _.case(_.isType("Number"), _.divideBy(2)); + * var persons = [ + * {"name": "Jane", "surname": "Doe", "age": 12}, + * {"name": "John", "surname": "Doe", "age": 40}, + * {"name": "Mario", "surname": "Rossi", "age": 18}, + * {"name": "Paolo", "surname": "Bianchi", "age": 15} + * ]; * - * halveIfNumber(2) // => 1 - * halveIfNumber("2") // => undefined + * _.pluck(persons, "age") // => [12, 40, 18, 15] * - * @alias module:lamb.case - * @category Logic - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.condition|condition} - * @see {@link module:lamb.unless|unless} - * @see {@link module:lamb.when|when} - * @since 0.51.0 - * @param {Function} predicate - * @param {Function} fn - * @returns {Function} + * var lists = [ + * [1, 2], + * [3, 4, 5], + * [6] + * ]; + * + * _.pluck(lists, "length") // => [2, 3, 1] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.pluckKey|pluckKey} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {String} key + * @returns {Array} */ - function case_ (predicate, fn) { - return function () { - return predicate.apply(this, arguments) ? fn.apply(this, arguments) : void 0; - }; + function pluck (arrayLike, key) { + return map(arrayLike, getKey(key)); } /** - * Builds a function that will apply the received arguments to trueFn, - * if the predicate is satisfied with the same arguments, or to falseFn otherwise.
- * Although you can use other conditions as trueFn or falseFn, - * it's probably better to use {@link module:lamb.adapter|adapter} to build more complex behaviours.
- * See also {@link module:lamb.unless|unless} and {@link module:lamb.when|when} as they are - * shortcuts to common use cases. + * A curried version of {@link module:lamb.pluck|pluck} expecting the key to retrieve to + * build a function waiting for the array-like object to act upon. * @example - * var isEven = function (n) { return n % 2 === 0}; - * var halveEvenAndDoubleOdd = _.condition(isEven, _.divideBy(2), _.multiplyBy(2)); + * var persons = [ + * {"name": "Jane", "surname": "Doe", "age": 12}, + * {"name": "John", "surname": "Doe", "age": 40}, + * {"name": "Mario", "surname": "Rossi", "age": 18}, + * {"name": "Paolo", "surname": "Bianchi", "age": 15} + * ]; + * var getAges = _.pluckKey("age"); * - * halveEvenAndDoubleOdd(5) // => 10 - * halveEvenAndDoubleOdd(6) // => 3 + * getAges(persons) // => [12, 40, 18, 15] * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.unless|unless} - * @see {@link module:lamb.when|when} - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.case|case} - * @since 0.2.0 - * @param {Function} predicate - * @param {Function} trueFn - * @param {Function} falseFn + * @category Array + * @function + * @see {@link module:lamb.pluck|pluck} + * @since 0.12.0 + * @param {String} key * @returns {Function} */ - function condition (predicate, trueFn, falseFn) { - return function () { - return (predicate.apply(this, arguments) ? trueFn : falseFn).apply(this, arguments); - }; - } + var pluckKey = compose(mapWith, getKey); /** - * Verifies that the first given value is greater than the second.
- * Wraps the native > operator within a function. + * Creates an array copy of the given array-like object without the + * specified values.
+ * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. * @example - * var pastDate = new Date(2010, 2, 12); - * var today = new Date(); + * var arr = [1, 2, 3, 4, 5]; * - * _.gt(today, pastDate) // => true - * _.gt(pastDate, today) // => false - * _.gt(3, 4) // => false - * _.gt(3, 3) // => false - * _.gt(3, 2) // => true - * _.gt(0, -0) // => false - * _.gt(-0, 0) // => false - * _.gt("a", "A") // => true - * _.gt("b", "a") // => true + * _.pullFrom(arr, [2, 5]) // => [1, 3, 4] + * + * @example It's not the same as {@link module:lamb.difference|difference}: + * + * var arr = [1,1,2,3,4,4,5]; + * + * _.pullFrom(arr, [1, 2]) // => [3, 4, 4, 5] + * _.difference(arr, [1, 2]) // => [3, 4, 5] * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.gte|gte} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} + * @category Array + * @see {@link module:lamb.pull|pull} + * @see {@link module:lamb.difference|difference} + * @since 0.45.0 + * @param {ArrayLike} arrayLike + * @param {ArrayLike} values + * @returns {Array} */ - function gt (a, b) { - return a > b; + function pullFrom (arrayLike, values) { + return values ? filter(arrayLike, function (element) { + return !isIn(values, element); + }) : slice(arrayLike, 0, arrayLike.length); } /** - * Verifies that the first given value is greater than or equal to the second. - * Regarding equality, beware that this is simply a wrapper for the native - * >= operator, so -0 === 0. + * A curried version of {@link module:lamb.pullFrom|pullFrom} expecting + * a list of values to build a function waiting for an array-like object.
+ * The new function will create an array copy of the array-like without + * the specified values.
+ * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
+ * See examples in {@link module:lamb.pullFrom|pullFrom} about the + * relationship with {@link module:lamb.difference|difference}. * @example - * _.gte(3, 4) // => false - * _.gte(3, 3) // => true - * _.gte(3, 2) // => true - * _.gte(0, -0) // => true - * _.gte(-0, 0) // => true + * var scores = [40, 20, 30, 10]; + * var newScores = [30, 10]; + * var pullNewScores = _.pull(newScores); + * + * pullNewScores(scores) // => [40, 20] * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.gt|gt} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} + * @category Array + * @function + * @see {@link module:lamb.pullFrom|pullFrom} + * @see {@link module:lamb.difference|difference} + * @since 0.45.0 + * @param {ArrayLike} values + * @returns {Function} */ - function gte (a, b) { - return a >= b; - } + var pull = _curry2(pullFrom, true); /** - * A curried version of {@link module:lamb.areSame|areSame}.
- * Accepts a value and builds a predicate that checks whether the value - * and the one received by the predicate are the same using the "SameValue" - * comparison.
- * See also {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} - * to perform a "SameValueZero" comparison. - * @example - * var john = {name: "John", surname: "Doe"}; - * var isJohn = _.is(john); - * var isNegativeZero = _.is(-0); - * var isReallyNaN = _.is(NaN); - * - * isJohn(john) // => true - * isJohn({name: "John", surname: "Doe"}) // => false - * - * isNegativeZero(0) // => false - * isNegativeZero(-0) // => true - * - * isNaN(NaN) // => true - * isNaN("foo") // => true - * - * isReallyNaN(NaN) // => true - * isReallyNaN("foo") // => false - * + * Same as {@link module:lamb.reduce|reduce}, but starts the fold operation from the last + * element instead.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. * @memberof module:lamb - * @category Logic + * @category Array * @function - * @see {@link module:lamb.areSame|areSame} - * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @see {@link module:lamb.reduce|reduce} + * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} * @since 0.1.0 - * @param {*} value - * @returns {Function} + * @param {ArrayLike} arrayLike + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] + * @returns {*} */ - var is = _curry2(areSame); + var reduceRight = _makeReducer(-1); /** - * A right curried version of {@link module:lamb.gt|gt}.
- * Accepts a value and builds a predicate that checks whether the value - * is greater than the one received by the predicate. + * A partial application of {@link module:lamb.reduce|reduceRight} that uses the + * provided accumulator and the optional initialValue to + * build a function expecting the array-like object to act upon. * @example - * var isGreaterThan5 = _.isGT(5); + * var arr = [1, 2, 3, 4, 5]; * - * isGreaterThan5(3) // => false - * isGreaterThan5(5) // => false - * isGreaterThan5(7) // => true + * _.reduceRightWith(_.sum)(arr) // => 15 + * _.reduceRightWith(_.subtract)(arr) // => -5 + * _.reduceRightWith(_.subtract, 0)(arr) // => -15 * * @memberof module:lamb - * @category Logic + * @category Array * @function - * @see {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value + * @see {@link module:lamb.reduceWith|reduceWith} + * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} + * @since 0.27.0 + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] * @returns {Function} */ - var isGT = _curry2(gt, true); + var reduceRightWith = _makePartial3(reduceRight, true); /** - * A right curried version of {@link module:lamb.gte|gte}.
- * Accepts a value and builds a predicate that checks whether the value - * is greater than or equal to the one received by the predicate. + * Reverses a copy of the given array-like object. * @example - * var isPositiveOrZero = _.isGTE(0); + * var arr = [1, 2, 3]; * - * isPositiveOrZero(-3) // => false - * isPositiveOrZero(-0) // => true - * isPositiveOrZero(0) // => true - * isPositiveOrZero(5) // => true + * _.reverse(arr) // => [3, 2, 1]; + * + * // `arr` still is [1, 2, 3] * * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.isGT|isGT} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value - * @returns {Function} + * @category Array + * @since 0.19.0 + * @param {ArrayLike} arrayLike + * @returns {Array} */ - var isGTE = _curry2(gte, true); + function reverse (arrayLike) { + var len = _toArrayLength(arrayLike.length); + var result = Array(len); + + for (var i = 0, ofs = len - 1; i < len; i++) { + result[i] = arrayLike[ofs - i]; + } + + return result; + } /** - * A right curried version of {@link module:lamb.lt|lt}.
- * Accepts a value and builds a predicate that checks whether the value - * is less than the one received by the predicate. + * Returns a copy of the given array-like with the element rotated by the desired amount. + * Negative indexes are allowed. * @example - * var isLessThan5 = _.isLT(5); + * var arr = [1, 2, 3, 4, 5]; * - * isLessThan5(7) // => false - * isLessThan5(5) // => false - * isLessThan5(3) // => true + * _.rotate(arr, 3) // => [3, 4, 5, 1, 2] + * _.rotate(arr, -3) // => [4, 5, 1, 2, 3] + * _.rotate(arr, 11) // => [5, 1, 2, 3, 4] * * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value - * @returns {Function} + * @category Array + * @see {@link module:lamb.rotateBy|rotateBy} + * @since 0.55.0 + * @param {ArrayLike} arrayLike + * @param {Number} amount + * @returns {Array} */ - var isLT = _curry2(lt, true); + function rotate (arrayLike, amount) { + var len = arrayLike.length; + var shift = amount % len; + + return slice(arrayLike, -shift, len).concat(slice(arrayLike, 0, -shift)); + } /** - * A right curried version of {@link module:lamb.lte|lte}.
- * Accepts a value and builds a predicate that checks whether the value - * is less than or equal to the one received by the predicate. + * A curried version of {@link module:lamb.rotate|rotate}.
+ * Uses the given amount to build a function expecting the array to rotate by that amount. * @example - * var isNegativeOrZero = _.isLTE(0); + * var arr = [1, 2, 3, 4, 5]; + * var rotateByTwo = _.rotateBy(2); * - * isNegativeOrZero(5) // => false - * isNegativeOrZero(-0) // => true - * isNegativeOrZero(0) // => true - * isNegativeOrZero(-3) // => true + * rotateByTwo(arr) // => [4, 5, 1, 2, 3] * * @memberof module:lamb - * @category Logic + * @category Array * @function - * @see {@link module:lamb.isLT|isLT} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value + * @see {@link module:lamb.rotate|rotate} + * @since 0.55.0 + * @param {Number} amount * @returns {Function} */ - var isLTE = _curry2(lte, true); + var rotateBy = _curry2(rotate, true); /** - * A curried version of {@link module:lamb.areSVZ|areSVZ}.
- * Accepts a value and builds a predicate that checks whether the value - * and the one received by the predicate are the same using the "SameValueZero" - * comparison.
- * See also {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} - * to perform a "SameValue" comparison. - * @example - * var john = {name: "John", surname: "Doe"}; - * var isJohn = _.isSVZ(john); - * var isZero = _.isSVZ(0); - * var isReallyNaN = _.isSVZ(NaN); - * - * isJohn(john) // => true - * isJohn({name: "John", surname: "Doe"}) // => false + * Sets an index in an array-like object.
+ * If provided with an updater function it will use it to update the current value, + * otherwise sets the index to the specified value. + * @private + * @param {ArrayLike} arrayLike + * @param {Number} idx + * @param {*} [value] + * @param {Function} [updater] + * @returns {Array} + */ + function _setIndex (arrayLike, idx, value, updater) { + var result = slice(arrayLike, 0, arrayLike.length); + var n = _toNaturalIndex(idx, result.length); + + if (n === n) { // eslint-disable-line no-self-compare + result[n] = arguments.length === 4 ? updater(arrayLike[n]) : value; + } + + return result; + } + + /** + * A curried version of {@link module:lamb.setIndex|setIndex} that builds + * a function that creates a copy of an array-like object with the given + * index changed to the desired value.
+ * If the index is not an integer or if it's out of bounds, the function + * will return a copy of the original array.
+ * Negative indexes are allowed. + * @example + * var arr = [1, 2, 3, 4, 5]; * - * isZero(0) // => true - * isZero(-0) // => true + * _.setAt(2, 99)(arr) // => [1, 2, 99, 4, 5] + * arr // => [1, 2, 3, 4, 5] * - * isNaN(NaN) // => true - * isNaN("foo") // => true + * _.setAt(10, 99)(arr) // => [1, 2, 3, 4, 5] (not a reference to `arr`) * - * isReallyNaN(NaN) // => true - * isReallyNaN("foo") // => false + * @example Using negative indexes: + * _.setAt(-1, 99)(arr) // => [1, 2, 3, 4, 99] * * @memberof module:lamb - * @category Logic + * @category Array * @function - * @see {@link module:lamb.areSVZ|areSVZ} - * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} - * @since 0.1.0 + * @see {@link module:lamb.setIndex|setIndex} + * @since 0.17.0 + * @param {Number} index * @param {*} value * @returns {Function} */ - var isSVZ = _curry2(areSVZ); + var setAt = _makePartial3(_setIndex); /** - * Verifies that the first given value is less than the second.
- * Wraps the native < operator within a function. + * Builds a new function that passes only the specified amount of arguments to the original one.
+ * As {@link module:lamb.slice|slice} is used to extract the arguments, you can also + * pass a negative arity. * @example - * var pastDate = new Date(2010, 2, 12); - * var today = new Date(); + * Math.max(10, 11, 45, 99) // => 99 + * _.aritize(Math.max, 2)(10, 11, 45, 99) // => 11 * - * _.lt(today, pastDate) // => false - * _.lt(pastDate, today) // => true - * _.lt(3, 4) // => true - * _.lt(3, 3) // => false - * _.lt(3, 2) // => false - * _.lt(0, -0) // => false - * _.lt(-0, 0) // => false - * _.lt("a", "A") // => false - * _.lt("a", "b") // => true + * @example Using a negative arity: + * _.aritize(Math.max, -1)(10, 11, 45, 99) // => 45 * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.lte|lte} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} + * @category Function + * @see {@link module:lamb.binary|binary}, {@link module:lamb.unary|unary} for common use cases shortcuts + * @since 0.1.0 + * @param {Function} fn + * @param {Number} arity + * @returns {Function} */ - function lt (a, b) { - return a < b; + function aritize (fn, arity) { + return function () { + var n = _toInteger(arity); + var args = list.apply(null, arguments).slice(0, n); + + for (var i = args.length; i < n; i++) { + args[i] = void 0; + } + + return fn.apply(this, args); + }; } /** - * Verifies that the first given value is less than or equal to the second. - * Regarding equality, beware that this is simply a wrapper for the native - * <= operator, so -0 === 0. + * Creates a copy of an array-like object with the given index changed to + * the desired value.
+ * If the index is not an integer or if it's out of bounds, the function + * will return a copy of the original array.
+ * Negative indexes are allowed. * @example - * _.lte(3, 4) // => true - * _.lte(3, 3) // => true - * _.lte(3, 2) // => false - * _.lte(0, -0) // => true - * _.lte(-0, 0) // => true + * var arr = [1, 2, 3]; + * + * _.setIndex(arr, 1, 99) // => [1, 99, 3] + * _.setIndex(arr, -1, 99) // => [1, 2, 99] + * _.setIndex(arr, 10, 99) // => [1, 2, 3] (not a reference to `arr`) * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.lt|lt} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} + * @category Array + * @function + * @see {@link module:lamb.setAt|setAt} + * @since 0.23.0 + * @param {ArrayLike} arrayLike + * @param {Number} index + * @param {*} value + * @returns {Array} */ - function lte (a, b) { - return a <= b; - } + var setIndex = aritize(_setIndex, 3); /** - * Returns a predicate that negates the given one. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var isOdd = _.not(isEven); + * Flattens the "first level" of an array. + * @example Showing the difference with flatten: + * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; * - * isOdd(5) // => true - * isOdd(4) // => false + * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] + * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] * * @memberof module:lamb - * @category Logic - * @since 0.1.0 - * @param {Function} predicate - * @returns {Function} + * @category Array + * @function + * @see {@link module:lamb.flatten|flatten} + * @since 0.9.0 + * @param {Array} array + * @returns {Array} */ - function not (predicate) { - return function () { - return !predicate.apply(this, arguments); - }; - } + var shallowFlatten = _makeArrayFlattener(false); /** - * Builds a unary function that will check its argument against the given predicate. - * If the predicate isn't satisfied, the provided fn function will be - * applied to the same value. The received argument is returned as it is otherwise.
- * See {@link module:lamb.when|when} for the opposite behaviour.
- * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, - * where its trueFn parameter is the [identity function]{@link module:lamb.identity}. + * Checks if at least one element in an array-like object satisfies the given predicate.
+ * The function will stop calling the predicate as soon as it returns a truthy value.
+ * Note that unlike the native + * [Array.prototype.some]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some}, + * this function won't skip deleted or unassigned indexes. * @example - * var isEven = function (n) { return n % 2 === 0}; - * var halveUnlessIsEven = _.unless(isEven, _.divideBy(2)); + * var persons = [ + * {"name": "Jane", "age": 12, active: false}, + * {"name": "John", "age": 40, active: false}, + * {"name": "Mario", "age": 17, active: false}, + * {"name": "Paolo", "age": 15, active: false} + * ]; + * var isAdult = _.keySatisfies(_.isGTE(18), "age"); + * var isActive = _.hasKeyValue("active", true); * - * halveUnlessIsEven(5) // => 2.5 - * halveUnlessIsEven(6) // => 6 + * _.someIn(persons, isAdult) // => true + * _.someIn(persons, isActive) // => false + * + * @example Showing the difference with Array.prototype.some: + * var arr = new Array(5); + * arr[3] = 99; + * + * arr.some(_.isUndefined) // => false + * _.someIn(arr, _.isUndefined) // => true * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.condition|condition} - * @see {@link module:lamb.when|when} - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.case|case} - * @since 0.42.0 - * @param {Function} predicate - * @param {Function} fn - * @returns {Function} + * @category Array + * @function + * @see {@link module:lamb.some|some} + * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} + * @since 0.39.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Boolean} */ - function unless (predicate, fn) { - return function (value) { - return predicate.call(this, value) ? value : fn.call(this, value); - }; - } + var someIn = _makeArrayChecker(false); /** - * Builds a unary function that will check its argument against the given predicate. - * If the predicate is satisfied, the provided fn function will be - * applied to the same value. The received argument is returned as it is otherwise.
- * See {@link module:lamb.unless|unless} for the opposite behaviour.
- * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, - * where its falseFn parameter is the [identity function]{@link module:lamb.identity}. + * A curried version of {@link module:lamb.someIn|someIn} that uses the given predicate to + * build a function waiting for the array-like to act upon. * @example + * var data = [1, 3, 5, 6, 7, 8]; * var isEven = function (n) { return n % 2 === 0; }; - * var halveIfEven = _.when(isEven, _.divideBy(2)); + * var containsEvens = _.some(isEven); + * var containsStrings = _.some(_.isType("String")); * - * halveIfEven(5) // => 5 - * halveIfEven(6) // => 3 + * containsEvens(data) // => true + * containsStrings(data) // => false * * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.condition|condition} - * @see {@link module:lamb.unless|unless} - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.case|case} - * @since 0.42.0 - * @param {Function} predicate - * @param {Function} fn - * @returns {Function} - */ - function when (predicate, fn) { - return function (value) { - return predicate.call(this, value) ? fn.call(this, value) : value; - }; - } - - lamb.adapter = adapter; - lamb.allOf = allOf; - lamb.anyOf = anyOf; - lamb.areSame = areSame; - lamb.areSVZ = areSVZ; - lamb.case = case_; - lamb.condition = condition; - lamb.gt = gt; - lamb.gte = gte; - lamb.is = is; - lamb.isGT = isGT; - lamb.isGTE = isGTE; - lamb.isLT = isLT; - lamb.isLTE = isLTE; - lamb.isSVZ = isSVZ; - lamb.lt = lt; - lamb.lte = lte; - lamb.not = not; - lamb.unless = unless; - lamb.when = when; - - /** - * A curried version of {@link module:lamb.sum|sum}. - * @example - * var add5 = _.add(5); - * - * _.add5(4) // => 9 - * _.add5(-2) // => 3 - * - * @memberof module:lamb - * @category Math + * @category Array * @function - * @see {@link module:lamb.sum|sum} - * @since 0.1.0 - * @param {Number} a + * @see {@link module:lamb.someIn|someIn} + * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} + * @since 0.39.0 + * @param {ListIteratorCallback} predicate * @returns {Function} */ - var add = _curry2(sum, true); + var some = _curry2(someIn, true); /** - * "Clamps" a number within the given limits, both included.
- * The function will convert to number all its parameters before starting any - * evaluation, and will return NaN if min is greater - * than max. - * @example - * _.clamp(-5, 0, 10) // => 0 - * _.clamp(5, 0, 10) // => 5 - * _.clamp(15, 0, 10) // => 10 - * _.clamp(0, 0, 10) // => 0 - * _.clamp(10, 0, 10) // => 10 - * _.is(_.clamp(-0, 0, 10), -0) // => true - * _.clamp(10, 20, 15) // => NaN - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.clampWithin|clampWithin} - * @since 0.13.0 - * @param {Number} n - * @param {Number} min - * @param {Number} max - * @returns {Number} + * Accepts a list of sorting criteria with at least one element + * and builds a function that compares two values with such criteria. + * @private + * @param {Sorter[]} criteria + * @returns {Function} */ - function clamp (n, min, max) { - n = +n; - min = +min; - max = +max; + function _compareWith (criteria) { + return function (a, b) { + var len = criteria.length; + var criterion = criteria[0]; + var result = criterion.compare(a.value, b.value); - if (min > max) { - return NaN; - } else { - return n < min ? min : n > max ? max : n; - } + for (var i = 1; result === 0 && i < len; i++) { + criterion = criteria[i]; + result = criterion.compare(a.value, b.value); + } + + if (result === 0) { + result = a.index - b.index; + } + + return criterion.isDescending ? -result : result; + }; } /** - * A curried version of {@link module:lamb.clamp|clamp}, expecting a min - * and a max value, that builds a function waiting for the number to clamp. - * @example - * _.clampWithin(0, 10)(-5) // => 0 - * _.clampWithin(0, 10)(5) // => 5 - * _.clampWithin(0, 10)(15) // => 10 - * _.clampWithin(0, 10)(0) // => 0 - * _.clampWithin(0, 10)(10) // => 10 - * _.is(_.clampWithin(0, 10)(-0), -0) // => true - * _.clampWithin(20, 15)(10) // => NaN - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.clamp|clamp} - * @since 0.47.0 - * @param {Number} min - * @param {Number} max - * @returns {Function} + * The default comparer for sorting functions.
+ * If the given values are of different types they + * will be both converted to strings.
+ * Uses the SameValueZero comparison. + * @private + * @param {*} a + * @param {*} b + * @returns {Number} -1 | 0 | 1 */ - var clampWithin = _makePartial3(clamp); + function _comparer (a, b) { + var result = 0; - /** - * A curried version of {@link module:lamb.subtract|subtract} that expects the - * subtrahend to build a function waiting for the minuend. - * @example - * var deduct5 = _.deduct(5); - * - * deduct5(12) // => 7 - * deduct5(3) // => -2 - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.subtract|subtract} - * @since 0.50.0 - * @param {Number} a - * @returns {Function} - */ - var deduct = _curry2(subtract, true); + if (typeof a !== typeof b) { + a = String(a); + b = String(b); + } - /** - * Divides two numbers. - * @example - * _.divide(5, 2) // => 2.5 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.divideBy|divideBy} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ - function divide (a, b) { - return a / b; - } + if (!areSVZ(a, b)) { + // eslint-disable-next-line no-self-compare + result = a > b || a !== a ? 1 : -1; + } - /** - * A curried version of {@link module:lamb.divide|divide} that expects a divisor to - * build a function waiting for the dividend. - * @example - * var halve = divideBy(2); - * - * halve(10) // => 5 - * halve(5) // => 2.5 - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.divide|divide} - * @since 0.50.0 - * @param {Number} a - * @returns {Function} - */ - var divideBy = _curry2(divide, true); + return result; + } /** - * Generates a sequence of values of the desired length with the provided iteratee. - * The values being iterated, and received by the iteratee, are the results generated so far. - * @example - * var fibonacci = function (n, idx, results) { - * return n + (results[idx - 1] || 0); - * }; - * - * _.generate(1, 10, fibonacci) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.range|range} - * @since 0.21.0 - * @param {*} start - The starting value - * @param {Number} len - The desired length for the sequence - * @param {ListIteratorCallback} iteratee - * @returns {Array} + * Builds a sorting criterion. If the comparer function is missing, the default + * comparer will be used instead. + * @private + * @param {Function} reader + * @param {Boolean} isDescending + * @param {Function} [comparer] + * @returns {Sorter} */ - function generate (start, len, iteratee) { - var result = [start]; + function _sorter (reader, isDescending, comparer) { + if (typeof reader !== "function" || reader === identity) { + reader = null; + } - for (var i = 0, limit = len - 1; i < limit; i++) { - result.push(iteratee(result[i], i, result)); + if (typeof comparer !== "function") { + comparer = _comparer; } - return result; + return { + isDescending: isDescending === true, + compare: function (a, b) { + if (reader) { + a = reader(a); + b = reader(b); + } + + return comparer(a, b); + } + }; } /** - * Verifies whether the received value is a finite number.
- * Behaves almost as a shim of ES6's [Number.isFinite]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite}, - * but with a difference: it will return true even for Number object's instances. - * @example - * _.isFinite(5) // => true - * _.isFinite(new Number(5)) // => true - * _.isFinite(Infinity) // => false - * _.isFinite(-Infinity) // => false - * _.isFinite("5") // => false - * _.isFinite(NaN) // => false - * _.isFinite(null) // => false - * - * @alias module:lamb.isFinite - * @category Math - * @since 0.46.0 - * @param {*} value - * @returns {Boolean} + * Converts a sorting function to a sorting criterion if necessary. + * @private + * @param {Function} criterion + * @returns {Sorter} */ - function isFinite_ (value) { - return type(value) === "Number" && isFinite(value); + function _makeCriterion (criterion) { + return criterion && typeof criterion.compare === "function" ? criterion : _sorter(criterion); } /** - * Verifies whether the received value is a number and an integer. - * Behaves almost as a shim of ES6's [Number.isInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger}, - * but with a difference: it will return true even for Number object's instances. - * @example - * _.isInteger(5) // => true - * _.isInteger(new Number(5)) // => true - * _.isInteger(2.5) // => false - * _.isInteger(Infinity) // => false - * _.isInteger(-Infinity) // => false - * _.isInteger("5") // => false - * _.isInteger(NaN) // => false - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.isSafeInteger|isSafeInteger} - * @since 0.46.0 - * @param {*} value - * @returns {Boolean} - */ - function isInteger (value) { - return type(value) === "Number" && value % 1 === 0; - } - - /** - * Verifies whether the received value is a "safe integer", meaning that is a number and that - * can be exactly represented as an IEEE-754 double precision number. - * The safe integers consist of all integers from -(253 - 1) inclusive to - * 253 - 1 inclusive.
- * Behaves almost as a shim of ES6's [Number.isSafeInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger}, - * but with a difference: it will return true even for Number object's instances. - * @example - * _.isSafeInteger(5) // => true - * _.isSafeInteger(new Number(5)) // => true - * _.isSafeInteger(Math.pow(2, 53) - 1) // => true - * _.isSafeInteger(Math.pow(2, 53)) // => false - * _.isSafeInteger(2e32) // => false - * _.isSafeInteger(2.5) // => false - * _.isSafeInteger(Infinity) // => false - * _.isSafeInteger(-Infinity) // => false - * _.isSafeInteger("5") // => false - * _.isSafeInteger(NaN) // => false - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.isInteger|isInteger} - * @since 0.46.0 - * @param {*} value - * @returns {Boolean} + * Builds a list of sorting criteria from a list of sorter functions. Returns a list containing + * a single default sorting criterion if the sorter list is empty. + * @private + * @param {Function[]} sorters + * @returns {Sorter[]} */ - function isSafeInteger (value) { - return isInteger(value) && Math.abs(value) <= 9007199254740991; + function _makeCriteria (sorters) { + return sorters && sorters.length ? map(sorters, _makeCriterion) : [_sorter()]; } /** - * Performs the modulo operation and should not be confused with the - * {@link module:lamb.remainder|remainder}. - * The function performs a floored division to calculate the result and not - * a truncated one, hence the sign of the dividend is not kept, unlike the - * {@link module:lamb.remainder|remainder}. - * @example - * _.modulo(5, 3) // => 2 - * _.remainder(5, 3) // => 2 + * Returns a [stably]{@link https://en.wikipedia.org/wiki/Sorting_algorithm#Stability} sorted + * copy of an array-like object using the given criteria.
+ * Sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, but you + * can also pass simple "reader" functions and default ascending sorters will be built for you.
+ * A "reader" is a function that evaluates the array element and supplies the value to be used + * in the comparison.
+ * Please note that if the arguments received by the default comparer aren't of the same type, + * they will be compared as strings. * - * _.modulo(-5, 3) // => 1 - * _.remainder(-5, 3) // => -2 + * @example Stable sort: + * var persons = [ + * {"name": "John", "surname" :"Doe"}, + * {"name": "Mario", "surname": "Rossi"}, + * {"name": "John", "surname" :"Moe"}, + * {"name": "Jane", "surname": "Foe"} + * ]; * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.remainder|remainder} - * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ - function modulo (a, b) { - return a - (b * Math.floor(a / b)); - } - - /** - * Multiplies two numbers. - * @example - * _.multiply(5, 3) // => 15 + * var personsByName = _.sort(persons, [_.getKey("name")]); * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.multiplyBy|multiplyBy} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ - function multiply (a, b) { - return a * b; - } - - /** - * A curried version of {@link module:lamb.multiply|multiply}. - * @example - * var double = _.multiplyBy(2); + * // personsByName holds: + * // [ + * // {"name": "Jane", "surname": "Foe"}, + * // {"name": "John", "surname" :"Doe"}, + * // {"name": "John", "surname" :"Moe"}, + * // {"name": "Mario", "surname": "Rossi"} + * // ] * - * double(5) // => 10 + * @example Stable multi-sort: + * var personsByNameAscSurnameDesc = _.sort(persons, [ + * _.getKey("name"), + * _.sorterDesc(_.getKey("surname")) + * ]); * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.multiply|multiply} - * @since 0.50.0 - * @param {Number} a - * @returns {Function} - */ - var multiplyBy = _curry2(multiply, true); - - /** - * Generates a random integer between two given integers, both included. - * Note that no safety measure is taken if the provided arguments aren't integers, so - * you may end up with unexpected (not really) results. - * For example randomInt(0.1, 1.2) could be 2. - * @example + * // personsByNameAscSurnameDesc holds: + * // [ + * // {"name": "Jane", "surname": "Foe"}, + * // {"name": "John", "surname" :"Moe"}, + * // {"name": "John", "surname" :"Doe"}, + * // {"name": "Mario", "surname": "Rossi"} + * // ] * - * _.randomInt(1, 10) // => an integer >=1 && <= 10 + * @example Using custom comparers: + * var localeSorter = new Intl.Collator("it"); + * var chars = ["a", "è", "à", "é", "c", "b", "e"]; * - * @memberof module:lamb - * @category Math - * @since 0.1.0 - * @param {Number} min - * @param {Number} max - * @returns {Number} - */ - function randomInt (min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); - } - - /** - * Generates an arithmetic progression of numbers starting from start up to, - * but not including, limit, using the given step. - * @example - * _.range(2, 10) // => [2, 3, 4, 5, 6, 7, 8, 9] - * _.range(1, -10, -2) // => [1, -1, -3, -5, -7, -9] - * _.range(0, 3, 1) // => [0, 1, 2] - * _.range(-0, 3, 1) // => [-0, 1, 2] - * _.range(1, -10, 2) // => [] - * _.range(3, 5, -1) // => [] + * _.sort(chars, [localeSorter]) // => ["a", "à", "b", "c", "e", "é", "è"] * - * @example Behaviour if step happens to be zero: - * _.range(2, 10, 0) // => [2] - * _.range(2, -10, 0) // => [2] - * _.range(2, 2, 0) // => [] + * var localeSorterDesc = _.sorterDesc(_.identity, localeSorter.compare); + * + * _.sort(chars, [localeSorterDesc]) // => ["è", "é", "e", "c", "b", "à", "a"] * * @memberof module:lamb - * @category Math - * @see {@link module:lamb.generate|generate} - * @since 0.1.0 - * @param {Number} start - * @param {Number} limit - * @param {Number} [step=1] - * @returns {Number[]} + * @category Array + * @see {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} + * @since 0.15.0 + * @param {ArrayLike} arrayLike + * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] + * @returns {Array} */ - function range (start, limit, step) { - start = _forceToNumber(start); - limit = _forceToNumber(limit); - step = arguments.length === 3 ? _forceToNumber(step) : 1; + function sort (arrayLike, sorters) { + var criteria = _makeCriteria(sorters); + var len = _toArrayLength(arrayLike.length); + var result = Array(len); - if (step === 0) { - return limit === start ? [] : [start]; + for (var i = 0; i < len; i++) { + result[i] = { value: arrayLike[i], index: i }; } - var len = Math.max(Math.ceil((limit - start) / step), 0); - var result = Array(len); + result.sort(_compareWith(criteria)); - for (var i = 0, last = start; i < len; i++) { - result[i] = last; - last += step; + for (i = 0; i < len; i++) { + result[i] = result[i].value; } return result; } /** - * Gets the remainder of the division of two numbers. - * Not to be confused with the {@link module:lamb.modulo|modulo} as the remainder - * keeps the sign of the dividend and may lead to some unexpected results. - * @example - * // example of wrong usage of the remainder - * // (in this case the modulo operation should be used) - * var isOdd = function (n) { return _.remainder(n, 2) === 1; }; - * isOdd(-3) // => false as -3 % 2 === -1 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.modulo|modulo} - * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b + * Establishes at which index an element should be inserted in a sorted array to respect + * the array order. Needs the comparer used to sort the array. + * @private + * @param {Array} array + * @param {*} element + * @param {Function} comparer + * @param {Number} start + * @param {Number} end * @returns {Number} */ - function remainder (a, b) { - return a % b; + function _getInsertionIndex (array, element, comparer, start, end) { + if (array.length === 0) { + return 0; + } + + var pivot = (start + end) >> 1; + var result = comparer( + { value: element, index: pivot }, + { value: array[pivot], index: pivot } + ); + + if (end - start <= 1) { + return result < 0 ? pivot : pivot + 1; + } else if (result < 0) { + return _getInsertionIndex(array, element, comparer, start, pivot); + } else if (result === 0) { + return pivot + 1; + } else { + return _getInsertionIndex(array, element, comparer, pivot, end); + } } /** - * Subtracts two numbers. - * @example - * _.subtract(5, 3) // => 2 + * Inserts an element in a copy of a sorted array respecting the sort order. + * @example With simple values: + * _.sortedInsert([], 1) // => [1] + * _.sortedInsert([2, 4, 6], 5) // => [2, 4, 5, 6] + * _.sortedInsert([4, 2, 1], 3, _.sorterDesc()) // => [4, 3, 2, 1] * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.deduct|deduct} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ - function subtract (a, b) { - return a - b; - } - - /** - * Sums two numbers. - * @example - * _.sum(4, 5) // => 9 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.add|add} - * @since 0.50.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ - function sum (a, b) { - return a + b; - } - - lamb.add = add; - lamb.clamp = clamp; - lamb.clampWithin = clampWithin; - lamb.deduct = deduct; - lamb.divide = divide; - lamb.divideBy = divideBy; - lamb.generate = generate; - lamb.isFinite = isFinite_; - lamb.isInteger = isInteger; - lamb.isSafeInteger = isSafeInteger; - lamb.modulo = modulo; - lamb.multiply = multiply; - lamb.multiplyBy = multiplyBy; - lamb.randomInt = randomInt; - lamb.range = range; - lamb.remainder = remainder; - lamb.subtract = subtract; - lamb.sum = sum; - - /** - * Accepts a constructor and builds a predicate expecting an object, - * which will be tested to verify whether the prototype of the constructor - * is in its prototype chain.
- * Wraps in a convenient way the native - * [instanceof]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof} operator. - * @example - * function SomeObjA () {} - * - * var a = new SomeObjA(); - * var sObj = new String("foo"); - * var s = "foo"; - * - * _.isInstanceOf(Object)(a) // => true - * _.isInstanceOf(SomeObjA)(a) // => true - * - * _.isInstanceOf(Object)(sObj) // => true - * _.isInstanceOf(String)(sObj) // => true - * - * _.isInstanceOf(Object)(s) // => false - * _.isInstanceOf(String)(s) // => false - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isType|isType} - * @since 0.47.0 - * @param {*} constructor - * @returns {Function} - */ - function isInstanceOf (constructor) { - return function (obj) { - return obj instanceof constructor; - }; - } - - /** - * Verifies if a value is null or undefined. - * @example - * _.isNil(NaN) // => false - * _.isNil({}) // => false - * _.isNil(null) // => true - * _.isNil(void 0) // => true - * _.isNil() // => true - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isNull|isNull} - * @see {@link module:lamb.isUndefined|isUndefined} - * @since 0.1.0 - * @param {*} value - * @returns {Boolean} - */ - function isNil (value) { - return isNull(value) || isUndefined(value); - } - - /** - * Verifies if a value is null. - * @example - * _.isNull(null) // => true - * _.isNull(void 0) // => false - * _.isNull(false) // => false - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isNil|isNil} if you want to check for undefined too. - * @since 0.1.0 - * @param {*} value - * @returns {Boolean} - */ - function isNull (value) { - return value === null; - } - - /** - * Builds a predicate that expects a value to check against the specified type. - * @example - * var isString = _.isType("String"); - * - * isString("Hello") // => true - * isString(new String("Hi")) // => true - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.type|type} - * @since 0.1.0 - * @param {String} typeName - * @returns {Function} - */ - function isType (typeName) { - return function (value) { - return type(value) === typeName; - }; - } - - /** - * Verifies if a value is undefined. - * @example - * _.isUndefined(null) // => false - * _.isUndefined(void 0) // => true - * _.isUndefined(false) // => false - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isNil|isNil} if you want to check for null too. - * @since 0.1.0 - * @param {*} value - * @returns {Boolean} - */ - function isUndefined (value) { - return value === void 0; - } - - /** - * Retrieves the "type tag" from the given value. - * @example - * var x = 5; - * var y = new Number(5); - * - * typeof x // => "number" - * typeof y // => "object" - * _.type(x) // => "Number" - * _.type(y) // => "Number" - * - * _.type(Object.prototype.toString) // => "Function" - * _.type(/a/) // => "RegExp" - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isType|isType} - * @since 0.9.0 - * @param {*} value - * @returns {String} - */ - function type (value) { - return _objectProto.toString.call(value).slice(8, -1); - } - - lamb.isInstanceOf = isInstanceOf; - lamb.isNil = isNil; - lamb.isNull = isNull; - lamb.isType = isType; - lamb.isUndefined = isUndefined; - lamb.type = type; - - /** - * A curried version of {@link module:lamb.getIndex|getIndex} that uses the provided index - * to build a function expecting the array-like object holding the element we want to retrieve. - * @example - * var getFifthElement = _.getAt(4); - * - * getFifthElement([1, 2, 3, 4, 5]) // => 5 - * getFifthElement("foo bar") // => "b" - * getFifthElement([]) // => undefined - * getFifthElement("foo") // => undefined - * - * @example Using negative indexes: - * _.getAt(-2)([1, 2, 3]) // => 2 - * _.getAt(-3)("foo") // => "f" - * - * @memberof module:lamb - * @category Array - * @function - * @since 0.16.0 - * @see {@link module:lamb.getIndex|getIndex} - * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. - * @param {Number} index - * @returns {Function} - */ - var getAt = _curry2(getIndex, true); - - /** - * Returns the value of the object property with the given key. - * @example - * var user = {name: "John"}; - * - * _.getIn(user, "name") // => "John"; - * _.getIn(user, "surname") // => undefined - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.getKey|getKey} - * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} - * @since 0.18.0 - * @param {Object} obj - * @param {String} key - * @returns {*} - */ - function getIn (obj, key) { - return obj[key]; - } - - /** - * Retrieves the element at the given index in an array-like object.
- * Like {@link module:lamb.slice|slice} the index can be negative.
- * If the index isn't supplied, or if its value isn't an integer within the array-like bounds, - * the function will return undefined.
- * getIndex will throw an exception when receives null or - * undefined in place of an array-like object, but returns undefined - * for any other value. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.getIndex(arr, 1) // => 2 - * _.getIndex(arr, -1) // => 5 - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.getAt|getAt} - * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. - * @since 0.23.0 - * @param {ArrayLike} arrayLike - * @param {Number} index - * @returns {*} - */ - function getIndex (arrayLike, index) { - var idx = _toNaturalIndex(index, _toArrayLength(arrayLike.length)); - - return idx === idx ? arrayLike[idx] : void 0; // eslint-disable-line no-self-compare - } - - /** - * A curried version of {@link module:lamb.getIn|getIn}.
- * Receives a property name and builds a function expecting the object from which we want to retrieve - * the property. - * @example - * var user1 = {name: "john"}; - * var user2 = {name: "jane"}; - * var getName = _.getKey("name"); - * - * getName(user1) // => "john" - * getName(user2) // => "jane" - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.getIn|getIn} - * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} - * @since 0.1.0 - * @param {String} key - * @returns {Function} - */ - var getKey = _curry2(getIn, true); - - /** - * Builds a partial application of {@link module:lamb.getPathIn|getPathIn} with the given - * path and separator, expecting the object to act upon.
- * @example - * var user = { - * name: "John", - * surname: "Doe", - * login: { - * "user.name": "jdoe", - * password: "abc123" - * } - * }; - * - * var getPwd = _.getPath("login.password"); - * var getUsername = _.getPath("login/user.name", "/"); - * - * getPwd(user) // => "abc123"; - * getUsername(user) // => "jdoe" - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.getPathIn|getPathIn} - * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} - * @since 0.19.0 - * @param {String} path - * @param {String} [separator="."] - * @returns {Function} - */ - var getPath = _makePartial3(getPathIn); - - /** - * Gets a nested property value from an object using the given path.
- * The path is a string with property names separated by dots by default, but - * it can be customised with the optional third parameter.
- * You can use integers in the path, even negative ones, to refer to array-like - * object indexes, but the priority will be given to existing object keys: - * the last example explains this particular case. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * login: { - * "user.name": "jdoe", - * password: "abc123" - * }, - * scores: [ - * {id: 1, value: 10}, - * {id: 2, value: 20}, - * {id: 3, value: 30} - * ] - * }; - * - * _.getPathIn(user, "name") // => "John" - * _.getPathIn(user, "login.password") // => "abc123"; - * _.getPathIn(user, "login/user.name", "/") // => "jdoe" - * _.getPathIn(user, "name.foo") // => undefined - * _.getPathIn(user, "name.foo.bar") // => undefined - * - * @example Accessing array-like objects indexes: - * _.getPathIn(user, "login.password.1") // => "b" - * _.getPathIn(user, "scores.0") // => {id: 1, value: 10} - * _.getPathIn(user, "scores.-1.value") // => 30 - * - * @example Priority will be given to existing object keys over indexes: - * _.getPathIn(user, "scores.-1") // => {id: 3, value: 30} - * - * // let's do something funny - * user.scores["-1"] = "foo bar"; - * - * _.getPathIn(user, "scores.-1") // => "foo bar"; - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.getPath|getPath} - * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} - * @since 0.19.0 - * @param {Object|ArrayLike} obj - * @param {String} path - * @param {String} [separator="."] - * @returns {*} - */ - function getPathIn (obj, path, separator) { - return _getPathInfo(obj, _toPathParts(path, separator), true).target; - } - - /** - * Retrieves the first element of an array-like object.
- * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. - * @example - * _.head([1, 2, 3]) // => 1 - * _.head("hello") // => "h" - * _.head([]) // => undefined - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.last|last} - * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {*} - */ - var head = getAt(0); - - /** - * Retrieves the last element of an array-like object.
- * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. - * @example - * _.last([1, 2, 3]) // => 3 - * _.last("hello") // => "o" - * _.last([]) // => undefined - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.head|head} - * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {*} - */ - var last = getAt(-1); - - /** - * A curried version of {@link module:lamb.setIndex|setIndex} that builds - * a function that creates a copy of an array-like object with the given - * index changed to the desired value.
- * If the index is not an integer or if it's out of bounds, the function - * will return a copy of the original array.
- * Negative indexes are allowed. - * @example - * var arr = [1, 2, 3, 4, 5]; + * @example With complex values: + * var persons = [ + * {"name": "jane", "surname": "doe"}, + * {"name": "John", "surname": "Doe"}, + * {"name": "Mario", "surname": "Rossi"} + * ]; * - * _.setAt(2, 99)(arr) // => [1, 2, 99, 4, 5] - * arr // => [1, 2, 3, 4, 5] + * var getLowerCaseName = _.compose( + * _.invoker("toLowerCase"), + * _.getKey("name") + * ); * - * _.setAt(10, 99)(arr) // => [1, 2, 3, 4, 5] (not a reference to `arr`) + * var result = _.sortedInsert( + * persons, + * {"name": "marco", "surname": "Rossi"}, + * getLowerCaseName + * ); * - * @example Using negative indexes: - * _.setAt(-1, 99)(arr) // => [1, 2, 3, 4, 99] + * // `result` holds: + * // [ + * // {"name": "jane", "surname": "doe"}, + * // {"name": "John", "surname": "Doe"}, + * // {"name": "marco", "surname": "Rossi"}, + * // {"name": "Mario", "surname": "Rossi"} + * // ] * * @memberof module:lamb * @category Array - * @function - * @see {@link module:lamb.setIndex|setIndex} - * @since 0.17.0 - * @param {Number} index - * @param {*} value - * @returns {Function} + * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} + * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} to insert the element + * at a specific index + * @since 0.27.0 + * @param {ArrayLike} arrayLike + * @param {*} element + * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - The sorting criteria + * used to sort the array. + * @returns {Array} */ - var setAt = _makePartial3(_setIndex); + function sortedInsert (arrayLike, element, sorters) { + var result = slice(arrayLike, 0, arrayLike.length); - /** - * Sets the specified key to the given value in a copy of the provided object.
- * All the remaining enumerable keys of the source object will be simply copied in the - * result object without breaking references.
- * If the specified key is not part of the source object, it will be added to the - * result.
- * The main purpose of the function is to work on simple plain objects used as - * data structures, such as JSON objects, and makes no effort to play nice with - * objects created from an OOP perspective (it's not worth it).
- * For example the prototype of the result will be Object's regardless - * of the source's one. - * @example - * var user = {name: "John", surname: "Doe", age: 30}; - * - * _.setIn(user, "name", "Jane") // => {name: "Jane", surname: "Doe", age: 30} - * _.setIn(user, "gender", "male") // => {name: "John", surname: "Doe", age: 30, gender: "male"} - * - * // `user` still is {name: "John", surname: "Doe", age: 30} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.setKey|setKey} - * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} - * @since 0.18.0 - * @param {Object} source - * @param {String} key - * @param {*} value - * @returns {Object} - */ - function setIn (source, key, value) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "object"); + if (arguments.length === 1) { + return result; } - return _setIn(source, key, value); + var criteria = _makeCriteria(sorters); + var idx = _getInsertionIndex(result, element, _compareWith(criteria), 0, result.length); + + result.splice(idx, 0, element); + + return result; } /** - * Creates a copy of an array-like object with the given index changed to - * the desired value.
- * If the index is not an integer or if it's out of bounds, the function - * will return a copy of the original array.
- * Negative indexes are allowed. - * @example - * var arr = [1, 2, 3]; - * - * _.setIndex(arr, 1, 99) // => [1, 99, 3] - * _.setIndex(arr, -1, 99) // => [1, 2, 99] - * _.setIndex(arr, 10, 99) // => [1, 2, 3] (not a reference to `arr`) + * Creates an ascending sort criterion with the provided reader and + * comparer.
+ * See {@link module:lamb.sort|sort} for various examples. * * @memberof module:lamb * @category Array * @function - * @see {@link module:lamb.setAt|setAt} - * @since 0.23.0 - * @param {ArrayLike} arrayLike - * @param {Number} index - * @param {*} value - * @returns {Array} + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorterDesc|sorterDesc} + * @since 0.1.0 + * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a + * simple value from a complex one. The function should evaluate the array element and supply the + * value to be passed to the comparer. + * @param {Function} [comparer] An optional custom comparer function. + * @returns {Sorter} */ - var setIndex = aritize(_setIndex, 3); + var sorter = partial(_sorter, [__, false, __]); /** - * Builds a partial application of {@link module:lamb.setIn|setIn} with the provided - * key and value.
- * The resulting function expects the object to act upon.
- * Please refer to {@link module:lamb.setIn|setIn}'s description for explanations about - * how the copy of the source object is made. - * @example - * var user = {name: "John", surname: "Doe", age: 30}; - * var setAgeTo40 = _.setKey("age", 40); - * - * setAgeTo40(user) // => {name: "john", surname: "doe", age: 40} - * - * // `user` still is {name: "John", surname: "Doe", age: 30} + * Creates a descending sort criterion with the provided reader and + * comparer.
+ * See {@link module:lamb.sort|sort} for various examples. * * @memberof module:lamb - * @category Object + * @category Array * @function - * @see {@link module:lamb.setIn|setIn} - * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} - * @since 0.18.0 - * @param {String} key - * @param {*} value - * @returns {Function} - */ - var setKey = _makePartial3(setIn); - - /** - * Builds a partial application of {@link module:lamb.setPathIn|setPathIn} expecting the - * object to act upon.
- * See {@link module:lamb.setPathIn|setPathIn} for more details and examples. - * @example - * var user = {id: 1, status: {active: false}}; - * var activate = _.setPath("status.active", true); - * - * activate(user) // => {id: 1, status: {active: true}} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.setPathIn|setPathIn} - * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} - * @since 0.20.0 - * @param {String} path - * @param {*} value - * @param {String} [separator="."] - * @returns {Function} - */ - function setPath (path, value, separator) { - return function (source) { - return setPathIn(source, path, value, separator); - }; - } - - /** - * Allows to change a nested value in a copy of the provided object.
- * The function will delegate the "set action" to {@link module:lamb.setIn|setIn} or - * {@link module:lamb.setAt|setAt} depending on the value encountered in the path, - * so please refer to the documentation of those functions for specifics about the - * implementation.
- * Note anyway that the distinction will be between Arrays, delegated - * to {@link module:lamb.setAt|setAt}, and everything else (including array-like objects), - * which will be delegated to {@link module:lamb.setIn|setIn}.
- * As a result of that, array-like objects will be converted to objects having numbers as keys - * and paths targeting non-object values will be converted to empty objects.
- * You can anyway target array elements using integers in the path, even negative ones, but - * the priority will be given to existing, and enumerable, object keys.
- * Non-enumerable properties encountered in the path will be considered as non-existent properties.
- * Like {@link module:lamb.getPathIn|getPathIn} or {@link module:lamb.getPath|getPath} you can - * use custom path separators. - * @example - * var user = {id: 1, status: {active : false, scores: [2, 4, 6]}}; - * - * _.setPathIn(user, "status.active", true) // => {id: 1, status: {active : true, scores: [2, 4, 6]}} - * - * @example Targeting arrays: - * _.setPathIn(user, "status.scores.0", 8) // => {id: 1, status: {active : false, scores: [8, 4, 6]}} - * - * // you can use negative indexes as well - * _.setPathIn(user, "status.scores.-1", 8) // => {id: 1, status: {active : false, scores: [2, 4, 8]}} - * - * @example Arrays can also be part of the path and not necessarily its target: - * var user = {id: 1, scores: [ - * {value: 2, year: "2000"}, - * {value: 4, year: "2001"}, - * {value: 6, year: "2002"} - * ]}; - * - * var newUser = _.setPathIn(user, "scores.0.value", 8); - * // "newUser" holds: - * // {id: 1, scores: [ - * // {value: 8, year: "2000"}, - * // {value: 4, year: "2001"}, - * // {value: 6, year: "2002"} - * // ]} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.setPath|setPath} - * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} - * @since 0.20.0 - * @param {Object|Array} source - * @param {String} path - * @param {*} value - * @param {String} [separator="."] - * @returns {Object|Array} + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorter|sorter} + * @since 0.15.0 + * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a + * simple value from a complex one. The function should evaluate the array element and supply the + * value to be passed to the comparer. + * @param {Function} [comparer] An optional custom comparer function. + * @returns {Sorter} */ - function setPathIn (source, path, value, separator) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "object"); - } - - return _setPathIn(source, _toPathParts(path, separator), value); - } + var sorterDesc = partial(_sorter, [__, true, __]); /** - * Builds a function that creates a copy of an array-like object with the given index - * changed by applying the provided function to its value.
- * If the index is not an integer or if it's out of bounds, the function will return - * a copy of the original array.
- * Negative indexes are allowed. + * Builds a partial application of {@link module:lamb.sort|sort} using the provided criteria. + * The returned function expects the array-like object to sort. + * As usual, sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, + * but you can also pass simple "reader" functions and default ascending sorters will be built.
+ * A "reader" is a function that evaluates the array element and supplies the value to be used in + * the comparison.
+ * See {@link module:lamb.sort|sort} for more examples. + * * @example - * var arr = ["a", "b", "c"]; - * var toUpperCase = _.invoker("toUpperCase"); + * var sortAsNumbers = _.sortWith([parseFloat]); + * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; * - * _.updateAt(1, toUpperCase)(arr) // => ["a", "B", "c"] - * _.updateAt(-1, toUpperCase)(arr) // => ["a", "b", "C"] - * _.updateAt(10, toUpperCase)(arr) // => ["a", "b", "c"] (not a reference to `arr`) + * sortAsNumbers(weights) // => ["1 Kg", "2 Kg", "7 Kg", "10 Kg"] * * @memberof module:lamb * @category Array - * @see {@link module:lamb.updateIndex|updateIndex} - * @since 0.22.0 - * @param {Number} index - * @param {Function} updater + * @function + * @see {@link module:lamb.sort|sort} + * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} + * @since 0.15.0 + * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] * @returns {Function} */ - function updateAt (index, updater) { - return function (arrayLike) { - return _setIndex(arrayLike, index, null, updater); - }; - } + var sortWith = _curry2(sort, true); /** - * Creates a copy of the given object having the desired key value updated by applying - * the provided function to it.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the - * source is returned otherwise. + * Returns a copy of the given array-like object without the first element. * @example - * var user = {name: "John", visits: 2}; - * var toUpperCase = _.invoker("toUpperCase"); - * - * _.updateIn(user, "name", toUpperCase) // => {name: "JOHN", visits: 2} - * _.updateIn(user, "surname", toUpperCase) // => {name: "John", visits: 2} - * - * @example Non-enumerable properties will be treated as non-existent: - * var user = Object.create({name: "John"}, {visits: {value: 2}}); - * - * _.updateIn(user, "visits", _.add(1)) // => {name: "John", visits: 2} + * _.tail([1, 2, 3, 4]) // => [2, 3, 4] + * _.tail([1]) // => [] + * _.tail([]) // => [] * * @memberof module:lamb - * @category Object - * @see {@link module:lamb.updateKey|updateKey} - * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} - * @since 0.22.0 - * @param {Object} source - * @param {String} key - * @param {Function} updater - * @returns {Object} + * @category Array + * @function + * @see {@link module:lamb.init|init} + * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {Array} */ - function updateIn (source, key, updater) { - return _isEnumerable(source, key) ? - _setIn(source, key, updater(source[key])) : - _merge(enumerables, source, {}); - } + var tail = drop(1); /** - * Creates a copy of an array-like object with the given index changed by applying the - * provided function to its value.
- * If the index is not an integer or if it's out of bounds, the function will return - * a copy of the original array.
- * Negative indexes are allowed. + * Retrieves the first n elements from an array or array-like object.
+ * Note that, being this a shortcut for a common use case of {@link module:lamb.slice|slice}, + * n can be a negative number. * @example - * var arr = ["a", "b", "c"]; - * var toUpperCase = _.invoker("toUpperCase"); + * var arr = [1, 2, 3, 4, 5]; * - * _.updateIndex(arr, 1, toUpperCase) // => ["a", "B", "c"] - * _.updateIndex(arr, -1, toUpperCase) // => ["a", "b", "C"] - * _.updateIndex(arr, 10, toUpperCase) // => ["a", "b", "c"] (not a reference to `arr`) + * _.takeFrom(arr, 3) // => [1, 2, 3] + * _.takeFrom(arr, -1) // => [1, 2, 3, 4] + * _.takeFrom(arr, -10) // => [] * * @memberof module:lamb * @category Array - * @function - * @see {@link module:lamb.updateAt|updateAt} - * @since 0.23.0 + * @see {@link module:lamb.take|take} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @since 0.51.0 * @param {ArrayLike} arrayLike - * @param {Number} index - * @param {Function} updater + * @param {Number} n * @returns {Array} */ - var updateIndex = partial(_setIndex, [_, _, null, _]); + function takeFrom (arrayLike, n) { + return slice(arrayLike, 0, n); + } /** - * Builds a partial application of {@link module:lamb.updateIn|updateIn} with the provided - * key and updater, expecting the object to act upon.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the - * source is returned otherwise. + * A curried version of {@link module:lamb.takeFrom|takeFrom} that expects the number of elements + * to retrieve to build a function waiting for the list to take the elements from.
+ * See the note and examples for {@link module:lamb.takeFrom|takeFrom} about passing a + * negative n. * @example - * var user = {name: "John", visits: 2}; - * var incrementVisits = _.updateKey("visits", _.add(1)); + * var take2 = _.take(2); * - * incrementVisits(user) // => {name: "John", visits: 3} + * take2([1, 2, 3, 4, 5]) // => [1, 2] * * @memberof module:lamb - * @category Object + * @category Array * @function - * @see {@link module:lamb.updateIn|updateIn} - * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} - * @since 0.22.0 - * @param {String} key - * @param {Function} updater + * @see {@link module:lamb.takeFrom|takeFrom} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @since 0.5.0 + * @param {Number} n * @returns {Function} */ - var updateKey = _makePartial3(updateIn); + var take = _curry2(takeFrom, true); /** - * Builds a partial application of {@link module:lamb.updatePathIn|updatePathIn} - * expecting the object to act upon.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the - * source is returned otherwise.
- * Like the other "path" functions, negative indexes can be used to access array elements, but - * the priority will be given to existing, and enumerable, object keys. + * Builds a function that takes the first n elements satisfying a predicate from + * an array or array-like object. * @example - * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; - * var incrementScores = _.updatePath("status.scores", _.mapWith(_.add(1))) + * var isEven = function (n) { return n % 2 === 0; }; + * var takeWhileIsEven = _.takeWhile(isEven); * - * incrementScores(user) // => {id: 1, status: {scores: [3, 5, 7], visits: 0}} + * takeWhileIsEven([1, 2, 4, 6, 8]) // => [] + * takeWhileIsEven([2, 4, 7, 8]) // => [2, 4] * * @memberof module:lamb - * @category Object - * @see {@link module:lamb.updatePathIn|updatePathIn} - * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} - * @since 0.24.0 - * @param {String} path - * @param {Function} updater - * @param {String} [separator="."] + * @category Array + * @see {@link module:lamb.dropWhile|dropWhile} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @since 0.5.0 + * @param {ListIteratorCallback} predicate * @returns {Function} */ - function updatePath (path, updater, separator) { - return function (source) { - return updatePathIn(source, path, updater, separator); + function takeWhile (predicate) { + return function (arrayLike) { + return slice(arrayLike, 0, _getNumConsecutiveHits(arrayLike, predicate)); }; } /** - * Allows to change a nested value in a copy of the given object by applying the provided - * function to it.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the - * source is returned otherwise.
- * Like the other "path" functions, negative indexes can be used to access array elements, but - * the priority will be given to existing, and enumerable, object keys. - * @example - * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; - * var inc = _.add(1); - * - * _.updatePathIn(user, "status.visits", inc) // => {id: 1, status: {scores: [2, 4, 6]}, visits: 1} - * - * @example Targeting arrays: - * _.updatePathIn(user, "status.scores.0", inc) // => {id: 1, status: {scores: [3, 4, 6], visits: 0}} - * - * // you can use negative indexes as well - * _.updatePathIn(user, "status.scores.-1", inc) // => {id: 1, status: {scores: [2, 4, 7], visits: 0}} + * Transposes a matrix. Can also be used to reverse a {@link module:lamb.zip|zip} operation.
+ * Just like {@link module:lamb.zip|zip}, the received array-like objects will be truncated to the + * shortest length. + * @example Transposing a matrix: + * _.transpose([ + * [1, 2, 3], + * [4, 5, 6], + * [7, 8, 9] + * ]) // => + * // [ + * // [1, 4, 7], + * // [2, 5, 8], + * // [3, 6, 9] + * // ] * - * @example Arrays can also be part of the path and not necessarily its target: - * var user = {id: 1, scores: [ - * {value: 2, year: "2000"}, - * {value: 4, year: "2001"}, - * {value: 6, year: "2002"} - * ]}; + * @example Showing the relationship with zip: + * var zipped = _.zip(["a", "b", "c"], [1, 2, 3]); // => [["a", 1], ["b", 2], ["c", 3]] * - * var newUser = _.updatePathIn(user, "scores.0.value", inc); - * // "newUser" holds: - * // {id: 1, scores: [ - * // {value: 3, year: "2000"}, - * // {value: 4, year: "2001"}, - * // {value: 6, year: "2002"} - * // ]} + * _.transpose(zipped) // => [["a", "b", "c"], [1, 2, 3]] * * @memberof module:lamb - * @category Object - * @see {@link module:lamb.updatePath|updatePath} - * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} - * @since 0.24.0 - * @param {Object|Array} source - * @param {String} path - * @param {Function} updater - * @param {String} [separator="."] - * @returns {Object|Array} + * @category Array + * @see {@link module:lamb.zip|zip} + * @since 0.14.0 + * @param {ArrayLike} arrayLike + * @returns {Array} */ - function updatePathIn (source, path, updater, separator) { - var parts = _toPathParts(path, separator); - var pathInfo = _getPathInfo(source, parts, false); + function transpose (arrayLike) { + var minLen = MAX_ARRAY_LENGTH; + var len = _toArrayLength(arrayLike.length); - if (pathInfo.isValid) { - return _setPathIn(source, parts, updater(pathInfo.target)); - } else { - return Array.isArray(source) ? slice(source, 0, source.length) : _merge(enumerables, source, {}); + if (len === 0) { + return []; + } + + for (var j = 0, elementLen; j < len; j++) { + elementLen = _toArrayLength(arrayLike[j].length); + + if (elementLen < minLen) { + minLen = elementLen; + } + } + + var result = Array(minLen); + + for (var i = 0, el; i < minLen; i++) { + el = result[i] = Array(len); + + for (j = 0; j < len; j++) { + el[j] = arrayLike[j][i]; + } } + + return result; + } + + /** + * Builds a TypeError stating that it's not possible to convert the given value to the + * desired type. + * @private + * @param {*} value + * @param {String} desiredType + * @returns {TypeError} + */ + function _makeTypeErrorFor (value, desiredType) { + return new TypeError("Cannot convert " + type(value).toLowerCase() + " to " + desiredType); } - lamb.getAt = getAt; - lamb.getIn = getIn; - lamb.getIndex = getIndex; - lamb.getKey = getKey; - lamb.getPath = getPath; - lamb.getPathIn = getPathIn; - lamb.head = head; - lamb.last = last; - lamb.setAt = setAt; - lamb.setIn = setIn; - lamb.setIndex = setIndex; - lamb.setKey = setKey; - lamb.setPath = setPath; - lamb.setPathIn = setPathIn; - lamb.updateAt = updateAt; - lamb.updateIn = updateIn; - lamb.updateIndex = updateIndex; - lamb.updateKey = updateKey; - lamb.updatePath = updatePath; - lamb.updatePathIn = updatePathIn; - /** - * A curried version of {@link module:lamb.appendTo|appendTo} that uses the value to append - * to build a function expecting the array-like object to act upon. + * Creates a pipeline of functions, where each function consumes the result of the previous one. * @example - * var arr = [1, 2, 3, 4]; + * var __ = _.__; + * var square = _.partial(Math.pow, [__, 2]); + * var getMaxAndSquare = _.pipe([Math.max, square]); * - * _.append(5)(arr) // => [1, 2, 3, 4, 5] - * _.append([5])(arr) // => [1, 2, 3, 4, [5]] + * getMaxAndSquare(3, 5) // => 25 * * @memberof module:lamb - * @category Array + * @category Function * @function - * @see {@link module:lamb.appendTo|appendTo} - * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} - * @since 0.44.0 - * @param {*} value + * @see {@link module:lamb.compose|compose} + * @since 0.1.0 + * @param {Function[]} functions * @returns {Function} */ - var append = _curry2(appendTo, true); + function pipe (functions) { + if (!Array.isArray(functions)) { + throw _makeTypeErrorFor(functions, "array"); + } + + var len = functions.length; + + return len ? function () { + var result = functions[0].apply(this, arguments); + + for (var i = 1; i < len; i++) { + result = functions[i].call(this, result); + } + + return result; + } : identity; + } /** - * Appends the given value at the end of a copy of the provided array-like object. + * Using the provided iteratee to transform values, builds a function that will + * return an array of the unique elements in the two provided array-like objects.
+ * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} + * to test the equality of values.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.union|union} if you don't need to compare transformed values. * @example - * var arr = [1, 2, 3, 4]; + * var unionByFloor = _.unionBy(Math.floor); * - * _.appendTo(arr, 5) // => [1, 2, 3, 4, 5] - * _.appendTo(arr, [5]) // => [1, 2, 3, 4, [5]] + * unionByFloor([2.8, 3.2, 1.5], [3.5, 1.2, 4]) // => [2.8, 3.2, 1.5, 4] * * @memberof module:lamb * @category Array - * @see {@link module:lamb.append|append} - * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} - * @since 0.44.0 - * @param {ArrayLike} arrayLike - * @param {*} value - * @returns {Array} + * @see {@link module:lamb.union|union} + * @see {@link module:lamb.difference|difference} + * @see {@link module:lamb.intersection|intersection} + * @since 0.51.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} */ - function appendTo (arrayLike, value) { - return slice(arrayLike, 0, arrayLike.length).concat([value]); + function unionBy (iteratee) { + return pipe([binary(list), flatMapWith(drop(0)), uniquesBy(iteratee)]); } /** - * Returns an array of unique items present only in the first of the two given - * array-like objects. To determine uniqueness the function uses the - * ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * Returns a list of every unique element present in the two given array-like objects.
+ * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} + * to test the equality of values.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.unionBy|unionBy} if you need to transform the values before + * the comparison or if you have to extract them from complex ones. * @example - * var a1 = [1, 2, 1, 3, 4]; - * var a2 = [2, 4, 5, 6]; - * var a3 = [3, 4, 5, 2, 1]; - * - * _.difference(a1, a2) // => [1, 3] - * _.difference(a2, a3) // => [6] - * _.difference(a1, a3) // => [] + * _.union([1, 2, 3, 2], [2, 3, 4]) // => [1, 2, 3, 4] + * _.union("abc", "bcd") // => ["a", "b", "c", "d"] * * @memberof module:lamb * @category Array + * @function + * @see {@link module:lamb.unionBy|unionBy} + * @see {@link module:lamb.difference|difference} * @see {@link module:lamb.intersection|intersection} - * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} - * @see {@link module:lamb.pull|pull}, {@link module:lamb.pullFrom|pullFrom} - * @since 0.6.0 - * @param {ArrayLike} arrayLike - * @param {ArrayLike} other + * @since 0.5.0 + * @param {ArrayLike} a + * @param {ArrayLike} b * @returns {Array} */ - function difference (arrayLike, other) { - var isNotInOther = partial(not(isIn), [other]); - - return uniques(filter(arrayLike, isNotInOther)); - } + var union = unionBy(identity); /** - * A curried version of {@link module:lamb.dropFrom|dropFrom} that expects the number of elements - * to drop to build a function waiting for the list to take the elements from.
- * See the note and examples for {@link module:lamb.dropFrom|dropFrom} about passing a - * negative n. + * Builds a function that creates a copy of an array-like object with the given index + * changed by applying the provided function to its value.
+ * If the index is not an integer or if it's out of bounds, the function will return + * a copy of the original array.
+ * Negative indexes are allowed. * @example - * var drop2 = _.drop(2); + * var arr = ["a", "b", "c"]; + * var toUpperCase = _.invoker("toUpperCase"); * - * drop2([1, 2, 3, 4, 5]) // => [3, 4, 5] + * _.updateAt(1, toUpperCase)(arr) // => ["a", "B", "c"] + * _.updateAt(-1, toUpperCase)(arr) // => ["a", "b", "C"] + * _.updateAt(10, toUpperCase)(arr) // => ["a", "b", "c"] (not a reference to `arr`) * * @memberof module:lamb * @category Array - * @function - * @since 0.5.0 - * @see {@link module:lamb.dropFrom|dropFrom} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @param {Number} n + * @see {@link module:lamb.updateIndex|updateIndex} + * @since 0.22.0 + * @param {Number} index + * @param {Function} updater * @returns {Function} */ - var drop = _curry2(dropFrom, true); + function updateAt (index, updater) { + return function (arrayLike) { + return _setIndex(arrayLike, index, null, updater); + }; + } /** - * Builds an array without the first n elements of the given array or array-like object. - * Note that, being this only a shortcut for a specific use case of {@link module:lamb.slice|slice}, - * n can be a negative number. + * Creates a copy of an array-like object with the given index changed by applying the + * provided function to its value.
+ * If the index is not an integer or if it's out of bounds, the function will return + * a copy of the original array.
+ * Negative indexes are allowed. * @example - * var arr = [1, 2, 3, 4, 5]; + * var arr = ["a", "b", "c"]; + * var toUpperCase = _.invoker("toUpperCase"); * - * _.dropFrom(arr, 2) // => [3, 4, 5] - * _.dropFrom(arr, -1) // => [5] - * _.dropFrom(arr, -10) // => [1, 2, 3, 4, 5] + * _.updateIndex(arr, 1, toUpperCase) // => ["a", "B", "c"] + * _.updateIndex(arr, -1, toUpperCase) // => ["a", "b", "C"] + * _.updateIndex(arr, 10, toUpperCase) // => ["a", "b", "c"] (not a reference to `arr`) * * @memberof module:lamb * @category Array - * @see {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @since 0.51.0 + * @function + * @see {@link module:lamb.updateAt|updateAt} + * @since 0.23.0 * @param {ArrayLike} arrayLike - * @param {Number} n + * @param {Number} index + * @param {Function} updater * @returns {Array} */ - function dropFrom (arrayLike, n) { - return slice(arrayLike, n, arrayLike.length); - } + var updateIndex = partial(_setIndex, [__, __, null, __]); /** - * Builds a function that drops the first n elements satisfying a predicate - * from an array or array-like object. + * Builds a list of arrays out of the two given array-like objects by pairing items with + * the same index.
+ * The received array-like objects will be truncated to the shortest length. * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var dropWhileIsEven = _.dropWhile(isEven); + * _.zip( + * ["a", "b", "c"], + * [1, 2, 3] + * ) // => [["a", 1], ["b", 2], ["c", 3]] * - * dropWhileIsEven([2, 4, 6, 8]) // => [] - * dropWhileIsEven([2, 4, 7, 8]) // => [7, 8] + * _.zip([1, 2, 3, 4], [5, 6, 7]) // => [[1, 5], [2, 6], [3, 7]] * * @memberof module:lamb * @category Array - * @see {@link module:lamb.takeWhile|takeWhile} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @since 0.5.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} + * @see {@link module:lamb.transpose|transpose} for the reverse operation + * @see {@link module:lamb.zipWithIndex|zipWithIndex} + * @since 0.14.0 + * @param {ArrayLike} a + * @param {ArrayLike} b + * @returns {Array} */ - function dropWhile (predicate) { - return function (arrayLike) { - return slice(arrayLike, _getNumConsecutiveHits(arrayLike, predicate), arrayLike.length); - }; + function zip (a, b) { + return transpose([a, b]); } /** - * Similar to {@link module:lamb.map|map}, but if the mapping function returns an array this will - * be concatenated, rather than pushed, to the final result. - * @example Showing the difference with map: - * var words = ["foo", "bar"]; - * var toCharArray = function (s) { return s.split(""); }; - * - * _.map(words, toCharArray) // => [["f", "o", "o"], ["b", "a", "r"]] - * _.flatMap(words, toCharArray) // => ["f", "o", "o", "b", "a", "r"] + * "{@link module:lamb.zip|Zips}" an array-like object by pairing its values with their index. + * @example + * _.zipWithIndex(["a", "b", "c"]) // => [["a", 0], ["b", 1], ["c", 2]] * * @memberof module:lamb * @category Array - * @see {@link module:lamb.flatMapWith|flatMapWith} - * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} - * @since 0.1.0 - * @param {Array} array - * @param {ListIteratorCallback} iteratee - * @returns {Array} + * @function + * @see {@link module:lamb.zip|zip} + * @since 0.14.0 + * @param {ArrayLike} arrayLike + * @returns {Array>} */ - function flatMap (array, iteratee) { - return reduce(array, function (result, el, idx, arr) { - var v = iteratee(el, idx, arr); - - if (!Array.isArray(v)) { - v = [v]; - } - - for (var i = 0, len = v.length, rLen = result.length; i < len; i++) { - result[rLen + i] = v[i]; - } - - return result; - }, []); - } + var zipWithIndex = mapWith(binary(list)); /** - * A curried version of {@link module:lamb.flatMap|flatMap} that uses provided iteratee - * to build a function expecting the array to act upon. + * Applies the given function to a list of arguments. * @example - * var toCharArray = function (s) { return s.split(""); }; - * var wordsToCharArray = _.flatMapWith(toCharArray); - * - * wordsToCharArray(["foo", "bar"]) // => ["f", "o", "o", "b", "a", "r"] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.flatMap|flatMap} - * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} - * @since 0.11.0 - * @param {ListIteratorCallback} iteratee - * @returns {Function} + * _.application(_.sum, [3, 4]) // => 7 + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.apply|apply}, {@link module:lamb.applyTo|applyTo} + * @since 0.47.0 + * @param {Function} fn + * @param {ArrayLike} args + * @returns {*} */ - var flatMapWith = _curry2(flatMap, true); + function application (fn, args) { + return fn.apply(this, Object(args)); + } /** - * Flattens an array. - * @example Showing the difference with shallowFlatten: - * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; + * A left-curried version of {@link module:lamb.application|application}. Expects the function + * to apply and builds a function waiting for the arguments array. + * @example + * var arrayMax = _.apply(Math.max); * - * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] - * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] + * arrayMax([4, 5, 2, 6, 1]) // => 6 * * @memberof module:lamb - * @category Array + * @category Function * @function - * @see {@link module:lamb.shallowFlatten|shallowFlatten} + * @see {@link module:lamb.application|application}, {@link module:lamb.applyTo|applyTo} * @since 0.1.0 - * @param {Array} array - * @returns {Array} + * @param {Function} fn + * @returns {Function} */ - var flatten = _makeArrayFlattener(true); + var apply = _curry2(application); /** - * Returns a copy of the given array-like object without the last element. + * A right-curried version of {@link module:lamb.application|application}. Expects an array-like + * object to use as arguments and builds a function waiting for the target of the application. * @example - * _.init([1, 2, 3, 4]) // => [1, 2, 3] - * _.init([1]) // => [] - * _.init([]) // => [] + * var data = [3, 4]; + * var applyToData = _.applyTo(data); + * + * applyToData(_.sum) // => 7 + * applyToData(_.multiply) // => 12 * * @memberof module:lamb - * @category Array + * @category Function * @function - * @see {@link module:lamb.tail|tail} - * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {Array} + * @see {@link module:lamb.application|application}, {@link module:lamb.apply|apply} + * @since 0.47.0 + * @param {ArrayLike} args + * @returns {Function} */ - var init = partial(slice, [_, 0, -1]); + var applyTo = _curry2(application, true); /** - * Inserts the provided element in a copy of an array-like object at the - * specified index.
- * If the index is greater than the length of the array-like, the element - * will be appended at the end.
- * Negative indexes are allowed; when a negative index is out of bounds - * the element will be inserted at the start of the resulting array. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.insert(arr, 3, 99) // => [1, 2, 3, 99, 4, 5] - * _.insert(arr, -2, 99) // => [1, 2, 3, 99, 4, 5] - * _.insert(arr, 10, 99) // => [1, 2, 3, 4, 5, 99] - * _.insert(arr, -10, 99) // => [99, 1, 2, 3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.insertAt|insertAt} - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {Number} index - * @param {*} element - * @returns {Array} + * Keeps building a partial application of the received function as long + * as it's called with placeholders; applies the original function to + * the collected parameters otherwise.
+ * The function checks only the public placeholder to gain a little performance + * as no function in Lamb is built with {@link module:lamb.asPartial|asPartial}. + * @private + * @param {Function} fn + * @param {Array} argsHolder + * @returns {Function|*} */ - function insert (arrayLike, index, element) { - var result = slice(arrayLike, 0, arrayLike.length); + function _asPartial (fn, argsHolder) { + return function () { + var argsLen = arguments.length; + var lastIdx = 0; + var newArgs = []; - result.splice(index, 0, element); + for (var i = 0, len = argsHolder.length, boundArg; i < len; i++) { + boundArg = argsHolder[i]; + newArgs[i] = boundArg === __ && lastIdx < argsLen ? arguments[lastIdx++] : boundArg; + } - return result; + while (lastIdx < argsLen) { + newArgs[i++] = arguments[lastIdx++]; + } + + for (i = 0; i < argsLen; i++) { + if (arguments[i] === __) { + return _asPartial(fn, newArgs); + } + } + + for (i = 0, len = newArgs.length; i < len; i++) { + if (newArgs[i] === __) { + newArgs[i] = void 0; + } + } + + return fn.apply(this, newArgs); + }; } /** - * Builds a partial application of {@link module:lamb.insert|insert} - * expecting the array-like object to act upon. - * @example - * var arr = [1, 2, 3, 4, 5]; + * Decorates the received function so that it can be called with + * placeholders to build a partial application of it.
+ * The difference with {@link module:lamb.partial|partial} is that, as long as + * you call the generated function with placeholders, another partial application + * of the original function will be built.
+ * The final application will happen when one of the generated functions is + * invoked without placeholders, using the parameters collected so far.
+ * This function comes in handy when you need to build different specialized + * functions starting from a basic one, but it's also useful when dealing with + * optional parameters as you can decide to apply the function even if its arity + * hasn't been entirely consumed. + * @example Explaining the function's behaviour: + * var __ = _.__; + * var f = _.asPartial(function (a, b, c) { + * return a + b + c; + * }); * - * _.insertAt(3, 99)(arr) // => [1, 2, 3, 99, 4, 5] - * _.insertAt(-2, 99)(arr) // => [1, 2, 3, 99, 4, 5] - * _.insertAt(10, 99)(arr) // => [1, 2, 3, 4, 5, 99] - * _.insertAt(-10, 99)(arr) // => [99, 1, 2, 3, 4, 5] + * f(4, 3, 2) // => 9 + * f(4, __, 2)(3) // => 9 + * f(__, 3, __)(4, __)(2) // => 9 + * + * @example Exploiting optional parameters: + * var __ = _.__; + * var f = _.asPartial(function (a, b, c) { + * return a + b + (c || 0); + * }); + * + * var addFive = f(5, __); + * addFive(2) // => 7 + * + * var addNine = addFive(4, __); + * addNine(11) // => 20 * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.insert|insert} - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} - * @since 0.27.0 - * @param {Number} index - * @param {*} element + * @category Function + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.__|__} The placeholder object. + * @since 0.36.0 + * @param {Function} fn * @returns {Function} */ - var insertAt = _makePartial3(insert); + function asPartial (fn) { + return _asPartial(fn, []); + } /** - * Returns an array of every unique item that is included in all two given arrays - * or array-like objects.
- * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * Accepts a series of functions and builds a new function. The functions in the series + * will then be applied, in order, with the values received by the function built with + * collect.
+ * The collected results will be returned in an array. * @example - * var a1 = [1, 2, 3, 4]; - * var a2 = [2, 5, 4, 2, 6]; - * var a3 = [5, 6, 7]; + * var user = { + * id: "jdoe", + * name: "John", + * surname: "Doe", + * scores: [2, 4, 7] + * }; + * var getIDAndLastScore = _.collect([_.getKey("id"), _.getPath("scores.-1")]); * - * _.intersection(a1, a2) // => [2, 4] - * _.intersection(a2, a3) // => [5, 6] - * _.intersection(a1, a3) // => [] + * getIDAndLastScore(user) // => ["jdoe", 7] + * + * @example + * var minAndMax = _.collect([Math.min, Math.max]); + * + * minAndMax(3, 1, -2, 5, 4, -1) // => [-2, 5] * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.difference|difference} - * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} - * @since 0.5.0 - * @param {ArrayLike} a - * @param {ArrayLike} b - * @returns {Array} + * @category Function + * @since 0.35.0 + * @param {Function[]} functions + * @returns {Function} */ - function intersection (a, b) { - var result = []; - var lenA = a.length; + function collect (functions) { + if (!Array.isArray(functions)) { + throw _makeTypeErrorFor(functions, "array"); + } - if (lenA && b.length) { - for (var i = 0; i < lenA; i++) { - !isIn(result, a[i]) && isIn(b, a[i]) && result.push(a[i]); + return function () { + return map(functions, applyTo(arguments)); + }; + } + + /** + * Used by curry functions to collect arguments until the arity is consumed, + * then applies the original function. + * @private + * @param {Function} fn + * @param {Number} arity + * @param {Boolean} isRightCurry + * @param {Boolean} isAutoCurry + * @param {Array} argsHolder + * @returns {Function} + */ + function _currier (fn, arity, isRightCurry, isAutoCurry, argsHolder) { + return function () { + var holderLen = argsHolder.length; + var argsLen = arguments.length; + var newArgsLen = holderLen + (argsLen > 1 && isAutoCurry ? argsLen : 1); + var newArgs = Array(newArgsLen); + + for (var i = 0; i < holderLen; i++) { + newArgs[i] = argsHolder[i]; } - } - return result; + for (; i < newArgsLen; i++) { + newArgs[i] = arguments[i - holderLen]; + } + + if (newArgsLen >= arity) { + return fn.apply(this, isRightCurry ? newArgs.reverse() : newArgs); + } else { + return _currier(fn, arity, isRightCurry, isAutoCurry, newArgs); + } + }; + } + + /** + * Curries a function of arity 3. + * @private + * @param {Function} fn + * @param {Boolean} [isRightCurry=false] + * @returns {Function} + */ + function _curry3 (fn, isRightCurry) { + return function (a) { + return function (b) { + return function (c) { + return isRightCurry ? fn.call(this, c, b, a) : fn.call(this, a, b, c); + }; + }; + }; } /** - * Splits an array-like object in two lists: the first with the elements satisfying the given predicate, - * the others with the remaining elements. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - * - * _.partition(numbers, isEven) // => [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.partitionWith|partitionWith} - * @since 0.11.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Array} + * Prepares a function for currying. If it's not auto-currying and the arity + * is 2 or 3 returns optimized functions, otherwise delegates the currying + * to the _currier function.
+ * If the desumed arity isn't greater than one, it will return the received + * function itself, instead. + * @private + * @param {Function} fn + * @param {Number} [arity=fn.length] + * @param {Boolean} [isRightCurry=false] + * @param {Boolean} [isAutoCurry=false] + * @returns {Function} */ - function partition (arrayLike, predicate) { - var result = [[], []]; - var len = arrayLike.length; - - for (var i = 0, el; i < len; i++) { - el = arrayLike[i]; - result[predicate(el, i, arrayLike) ? 0 : 1].push(el); + function _curry (fn, arity, isRightCurry, isAutoCurry) { + if (arity >>> 0 !== arity) { + arity = fn.length; } - return result; + if (isAutoCurry && arity > 1 || arity > 3) { + return _currier(fn, arity, isRightCurry, isAutoCurry, []); + } else if (arity === 2) { + return _curry2(fn, isRightCurry); + } else if (arity === 3) { + return _curry3(fn, isRightCurry); + } else { + return fn; + } } /** - * A curried version of {@link module:lamb.partition|partition} that uses the provided - * predicate to build a function expecting the array-like object to act upon. + * Transforms the evaluation of the given function in the evaluation of a sequence of functions + * expecting only one argument. Each function of the sequence is a partial application of the + * original one, which will be applied when the specified (or derived) arity is consumed.
+ * Currying will start from the leftmost argument: use {@link module:lamb.curryRight|curryRight} + * for right currying. * @example - * var users = [ - * {"name": "Jane", "surname": "Doe", "active": false}, - * {"name": "John", "surname": "Doe", "active": true}, - * {"name": "Mario", "surname": "Rossi", "active": true}, - * {"name": "Paolo", "surname": "Bianchi", "active": false} - * ]; - * var isActive = _.hasKeyValue("active", true); - * var splitByActiveStatus = _.partitionWith(isActive); + * var makeWithKeys = _.curry(_.make); + * var makePerson = makeWithKeys(["name", "surname"]); * - * splitByActiveStatus(users) // => - * // [[ - * // {"name": "John", "surname": "Doe", "active": true}, - * // {"name": "Mario", "surname": "Rossi", "active": true} - * // ], [ - * // {"name": "Jane", "surname": "Doe", "active": false}, - * // {"name": "Paolo", "surname": "Bianchi", "active": false} - * // ]] + * makePerson(["John", "Doe"]) // => {name: "John", surname: "Doe"}; + * makePerson(["Mario", "Rossi"]) // => {name: "Mario", surname: "Rossi"}; * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.partition|partition} - * @since 0.11.0 - * @param {ListIteratorCallback} predicate + * @category Function + * @see {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.1.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] * @returns {Function} */ - var partitionWith = _curry2(partition, true); + function curry (fn, arity) { + return _curry(fn, arity, false); + } /** - * "Plucks" the values of the specified key from a list of objects. + * Builds an auto-curried function. The resulting function can be called multiple times with + * any number of arguments, and the original function will be applied only when the specified + * (or derived) arity is consumed.
+ * Currying will start from the leftmost argument: use {@link module:lamb.curryableRight|curryableRight} + * for right currying. * @example - * var persons = [ - * {"name": "Jane", "surname": "Doe", "age": 12}, - * {"name": "John", "surname": "Doe", "age": 40}, - * {"name": "Mario", "surname": "Rossi", "age": 18}, - * {"name": "Paolo", "surname": "Bianchi", "age": 15} - * ]; - * - * _.pluck(persons, "age") // => [12, 40, 18, 15] - * - * var lists = [ - * [1, 2], - * [3, 4, 5], - * [6] - * ]; + * var collectFourElements = _.curryable(_.list, 4); * - * _.pluck(lists, "length") // => [2, 3, 1] + * collectFourElements(2)(3)(4)(5) // => [2, 3, 4, 5] + * collectFourElements(2)(3, 4)(5) // => [2, 3, 4, 5] + * collectFourElements(2, 3, 4, 5) // => [2, 3, 4, 5] + * collectFourElements(2, 3)(4, 5) // => [2, 3, 4, 5] * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.pluckKey|pluckKey} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {String} key - * @returns {Array} + * @category Function + * @see {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.6.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] + * @returns {Function} */ - function pluck (arrayLike, key) { - return map(arrayLike, getKey(key)); + function curryable (fn, arity) { + return _curry(fn, arity, false, true); } /** - * A curried version of {@link module:lamb.pluck|pluck} expecting the key to retrieve to - * build a function waiting for the array-like object to act upon. + * Same as {@link module:lamb.curryable|curryable}, but currying starts from the rightmost argument. * @example - * var persons = [ - * {"name": "Jane", "surname": "Doe", "age": 12}, - * {"name": "John", "surname": "Doe", "age": 40}, - * {"name": "Mario", "surname": "Rossi", "age": 18}, - * {"name": "Paolo", "surname": "Bianchi", "age": 15} - * ]; - * var getAges = _.pluckKey("age"); + * var collectFourElements = _.curryableRight(_.list, 4); * - * getAges(persons) // => [12, 40, 18, 15] + * collectFourElements(2)(3)(4)(5) // => [5, 4, 3, 2] + * collectFourElements(2)(3, 4)(5) // => [5, 4, 3, 2] + * collectFourElements(2, 3, 4, 5) // => [5, 4, 3, 2] + * collectFourElements(2, 3)(4, 5) // => [5, 4, 3, 2] * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.pluck|pluck} - * @since 0.12.0 - * @param {String} key + * @category Function + * @see {@link module:lamb.curryable|curryable} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.9.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] * @returns {Function} */ - var pluckKey = compose(mapWith, getKey); + function curryableRight (fn, arity) { + return _curry(fn, arity, true, true); + } /** - * A curried version of {@link module:lamb.pullFrom|pullFrom} expecting - * a list of values to build a function waiting for an array-like object.
- * The new function will create an array copy of the array-like without - * the specified values.
- * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
- * See examples in {@link module:lamb.pullFrom|pullFrom} about the - * relationship with {@link module:lamb.difference|difference}. + * Same as {@link module:lamb.curry|curry}, but currying starts from the rightmost argument. * @example - * var scores = [40, 20, 30, 10]; - * var newScores = [30, 10]; - * var pullNewScores = _.pull(newScores); + * var makeWithValues = _.curryRight(_.make); + * var makeJohnDoe = makeWithValues(["John", "Doe"]); * - * pullNewScores(scores) // => [40, 20] + * makeJohnDoe(["name", "surname"]) // => {name: "John", surname: "Doe"}; + * makeJohnDoe(["firstName", "lastName"]) // => {firstName: "John", lastName: "Doe"}; * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.pullFrom|pullFrom} - * @see {@link module:lamb.difference|difference} - * @since 0.45.0 - * @param {ArrayLike} values + * @category Function + * @see {@link module:lamb.curry|curry} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.9.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] * @returns {Function} */ - var pull = _curry2(pullFrom, true); + function curryRight (fn, arity) { + return _curry(fn, arity, true); + } /** - * Creates an array copy of the given array-like object without the - * specified values.
- * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.pullFrom(arr, [2, 5]) // => [1, 3, 4] - * - * @example It's not the same as {@link module:lamb.difference|difference}: + * Returns a function that will execute the given function only if it stops being called for the + * specified timespan.
+ * See also {@link module:lamb.throttle|throttle} for a different behaviour where the first call + * happens immediately. + * @example A common use case of debounce in a browser environment: + * var updateLayout = function () { + * // some heavy DOM operations here + * }; * - * var arr = [1,1,2,3,4,4,5]; + * window.addEventListener("resize", _.debounce(updateLayout, 200), false); * - * _.pullFrom(arr, [1, 2]) // => [3, 4, 4, 5] - * _.difference(arr, [1, 2]) // => [3, 4, 5] + * // The resize event is fired repeteadly until the user stops resizing the + * // window, while the `updateLayout` function is called only once: 200 ms + * // after he stopped. * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.pull|pull} - * @see {@link module:lamb.difference|difference} - * @since 0.45.0 - * @param {ArrayLike} arrayLike - * @param {ArrayLike} values - * @returns {Array} - */ - function pullFrom (arrayLike, values) { - return values ? filter(arrayLike, function (element) { - return !isIn(values, element); - }) : slice(arrayLike, 0, arrayLike.length); + * @category Function + * @see {@link module:lamb.throttle|throttle} + * @since 0.1.0 + * @param {Function} fn + * @param {Number} timespan - Expressed in milliseconds + * @returns {Function} + */ + function debounce (fn, timespan) { + var timeoutID; + + return function () { + var args = arguments; + var debounced = function () { + timeoutID = null; + fn.apply(this, args); + }.bind(this); + + clearTimeout(timeoutID); + timeoutID = setTimeout(debounced, timespan); + }; } /** - * Returns a copy of the given array-like with the element rotated by the desired amount. - * Negative indexes are allowed. + * Returns a function that applies the original function with the arguments in reverse order. * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.rotate(arr, 3) // => [3, 4, 5, 1, 2] - * _.rotate(arr, -3) // => [4, 5, 1, 2, 3] - * _.rotate(arr, 11) // => [5, 1, 2, 3, 4] + * _.list(1, 2, 3) // => [1, 2, 3] + * _.flip(_.list)(1, 2, 3) // => [3, 2, 1] * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.rotateBy|rotateBy} - * @since 0.55.0 - * @param {ArrayLike} arrayLike - * @param {Number} amount - * @returns {Array} + * @category Function + * @since 0.1.0 + * @param {Function} fn + * @returns {Function} */ - function rotate (arrayLike, amount) { - var len = arrayLike.length; - var shift = amount % len; + function flip (fn) { + return function () { + var args = list.apply(null, arguments).reverse(); - return slice(arrayLike, -shift, len).concat(slice(arrayLike, 0, -shift)); + return fn.apply(this, args); + }; } /** - * A curried version of {@link module:lamb.rotate|rotate}.
- * Uses the given amount to build a function expecting the array to rotate by that amount. + * Builds a function that returns the argument received at the given index.
+ * As with {@link module:lamb.getAt|getAt} negative indexes are allowed.
+ * The resulting function will return undefined if no arguments are + * passed or if the index is out of bounds. * @example - * var arr = [1, 2, 3, 4, 5]; - * var rotateByTwo = _.rotateBy(2); + * var getFirstArg = _.getArgAt(0); + * var getLastArg = _.getArgAt(-1); * - * rotateByTwo(arr) // => [4, 5, 1, 2, 3] + * getFirstArg(1, 2, 3) // => 1 + * getLastArg(1, 2, 3) // => 3 + * + * _.getArgAt()(1, 2, 3) // => undefined + * _.getArgAt(6)(1, 2, 3) // => undefined + * _.getArgAt(1)() // => undefined * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.rotate|rotate} - * @since 0.55.0 - * @param {Number} amount + * @category Function + * @since 0.17.0 + * @param {Number} idx * @returns {Function} */ - var rotateBy = _curry2(rotate, true); + function getArgAt (idx) { + return function () { + return arguments[_toNaturalIndex(idx, arguments.length)]; + }; + } /** - * Flattens the "first level" of an array. - * @example Showing the difference with flatten: - * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; - * - * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] - * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] - * - * @memberof module:lamb - * @category Array + * Builds an array with the received arguments excluding the first one.
+ * To be used with the arguments object, which needs to be passed to the apply + * method of this function. + * @private * @function - * @see {@link module:lamb.flatten|flatten} - * @since 0.9.0 - * @param {Array} array + * @param {...*} value * @returns {Array} */ - var shallowFlatten = _makeArrayFlattener(false); + var _argsTail = _argsToArrayFrom(1); /** - * Returns a copy of the given array-like object without the first element. - * @example - * _.tail([1, 2, 3, 4]) // => [2, 3, 4] - * _.tail([1]) // => [] - * _.tail([]) // => [] + * If a method with the given name exists on the target, applies it to the provided + * arguments and returns the result. Returns undefined otherwise.
+ * The arguments for the method are built by concatenating the array of bound arguments, + * optionally received by {@link module:lamb.invoker|invoker}, with the final set of, also + * optional, args. + * @private + * @param {Array} boundArgs + * @param {String} methodName + * @param {Object} target + * @param {...*} [args] + * @returns {*} + */ + function _invoker (boundArgs, methodName, target) { + var method = target[methodName]; + + if (typeof method !== "function") { + return void 0; + } + + var boundArgsLen = boundArgs.length; + var ofs = 3 - boundArgsLen; + var len = arguments.length - ofs; + var args = Array(len); + + for (var i = 0; i < boundArgsLen; i++) { + args[i] = boundArgs[i]; + } + + for (; i < len; i++) { + args[i] = arguments[i + ofs]; + } + + return method.apply(target, args); + } + + /** + * Builds a function that will invoke the given method name on any received object and + * return the result. If no method with such name is found the function will return + * undefined.
+ * Along with the method name it's possible to supply some arguments that will be bound to the + * method call. Further arguments can also be passed when the function is actually called, and + * they will be concatenated to the bound ones.
+ * Returning undefined is a behaviour meant to quickly create a case for + * {@link module:lamb.adapter|adapter} without the need to check for the existence of the + * desired method.
+ * See also {@link module:lamb.generic|generic} to create functions out of object methods. + * @example Basic polymorphism with invoker: + * var polySlice = _.invoker("slice"); + * + * polySlice([1, 2, 3, 4, 5], 1, 3) // => [2, 3] + * polySlice("Hello world", 1, 3) // => "el" + * + * @example With bound arguments: + * var substrFrom2 = _.invoker("substr", 2); + * substrFrom2("Hello world") // => "llo world" + * substrFrom2("Hello world", 5) // => "llo w" * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.init|init} - * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {Array} + * @category Function + * @see {@link module:lamb.invokerOn|invokerOn} + * @since 0.1.0 + * @param {String} methodName + * @param {...*} [boundArg] + * @returns {Function} */ - var tail = drop(1); + function invoker (methodName) { + return partial(_invoker, [_argsTail.apply(null, arguments), methodName]); + } /** - * A curried version of {@link module:lamb.takeFrom|takeFrom} that expects the number of elements - * to retrieve to build a function waiting for the list to take the elements from.
- * See the note and examples for {@link module:lamb.takeFrom|takeFrom} about passing a - * negative n. + * Accepts an object and builds a function expecting a method name, and optionally arguments, + * to call on such object. + * Like {@link module:lamb.invoker|invoker}, if no method with the given name is found the + * function will return undefined. * @example - * var take2 = _.take(2); + * var isEven = function (n) { return n % 2 === 0; }; + * var arr = [1, 2, 3, 4, 5]; + * var invokerOnArr = _.invokerOn(arr); * - * take2([1, 2, 3, 4, 5]) // => [1, 2] + * invokerOnArr("filter", isEven) // => [2, 4] + * invokerOnArr("slice", 1, 3) // => [2, 3] * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.takeFrom|takeFrom} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @since 0.5.0 - * @param {Number} n + * @category Function + * @see {@link module:lamb.invoker|invoker} + * @since 0.15.0 + * @param {Object} target * @returns {Function} */ - var take = _curry2(takeFrom, true); + function invokerOn (target) { + return partial(_invoker, [[], __, target]); + } /** - * Retrieves the first n elements from an array or array-like object.
- * Note that, being this a shortcut for a common use case of {@link module:lamb.slice|slice}, - * n can be a negative number. + * Builds a function that allows to map over the received arguments before applying them + * to the original one. * @example - * var arr = [1, 2, 3, 4, 5]; + * var __ = _.__; + * var sumArray = _.reduceWith(_.sum); + * var sumArgs = _.compose(sumArray, _.list); * - * _.takeFrom(arr, 3) // => [1, 2, 3] - * _.takeFrom(arr, -1) // => [1, 2, 3, 4] - * _.takeFrom(arr, -10) // => [] + * sumArgs(1, 2, 3, 4, 5) // => 15 + * + * var square = _.partial(Math.pow, [__, 2]); + * var sumSquares = _.mapArgs(sumArgs, square); + * + * sumSquares(1, 2, 3, 4, 5) // => 55 * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.take|take} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @since 0.51.0 - * @param {ArrayLike} arrayLike - * @param {Number} n - * @returns {Array} + * @category Function + * @see {@link module:lamb.tapArgs|tapArgs} + * @since 0.3.0 + * @param {Function} fn + * @param {ListIteratorCallback} mapper + * @returns {Function} */ - function takeFrom (arrayLike, n) { - return slice(arrayLike, 0, n); + function mapArgs (fn, mapper) { + return pipe([list, mapWith(mapper), apply(fn)]); } /** - * Builds a function that takes the first n elements satisfying a predicate from - * an array or array-like object. + * Builds a function that allows to "tap" into the arguments of the original one. + * This allows to extract simple values from complex ones, transform arguments or simply intercept them. + * If a "tapper" isn't found the argument is passed as it is. * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var takeWhileIsEven = _.takeWhile(isEven); + * var someObject = {count: 5}; + * var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0]; + * var getDataAmount = _.tapArgs(_.sum, [_.getKey("count"), _.getKey("length")]); * - * takeWhileIsEven([1, 2, 4, 6, 8]) // => [] - * takeWhileIsEven([2, 4, 7, 8]) // => [2, 4] + * getDataAmount(someObject, someArrayData); // => 15 * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.dropWhile|dropWhile} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @since 0.5.0 - * @param {ListIteratorCallback} predicate + * @category Function + * @see {@link module:lamb.mapArgs|mapArgs} + * @since 0.3.0 + * @param {Function} fn + * @param {Function[]} tappers * @returns {Function} */ - function takeWhile (predicate) { - return function (arrayLike) { - return slice(arrayLike, 0, _getNumConsecutiveHits(arrayLike, predicate)); + function tapArgs (fn, tappers) { + return function () { + var len = arguments.length; + var tappersLen = tappers.length; + var args = []; + + for (var i = 0; i < len; i++) { + args.push(i < tappersLen ? tappers[i](arguments[i]) : arguments[i]); + } + + return fn.apply(this, args); }; } /** - * Transposes a matrix. Can also be used to reverse a {@link module:lamb.zip|zip} operation.
- * Just like {@link module:lamb.zip|zip}, the received array-like objects will be truncated to the - * shortest length. - * @example Transposing a matrix: - * _.transpose([ - * [1, 2, 3], - * [4, 5, 6], - * [7, 8, 9] - * ]) // => - * // [ - * // [1, 4, 7], - * // [2, 5, 8], - * // [3, 6, 9] - * // ] - * - * @example Showing the relationship with zip: - * var zipped = _.zip(["a", "b", "c"], [1, 2, 3]); // => [["a", 1], ["b", 2], ["c", 3]] + * Returns a function that will invoke the passed function at most once in the given timespan.
+ * The first call in this case happens as soon as the function is invoked; see also + * {@link module:lamb.debounce|debounce} for a different behaviour where the first call is delayed. + * @example + * var log = _.throttle(console.log.bind(console), 5000); * - * _.transpose(zipped) // => [["a", "b", "c"], [1, 2, 3]] + * log("Hi"); // console logs "Hi" + * log("Hi again"); // nothing happens + * // after five seconds + * log("Hello world"); // console logs "Hello world" * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.zip|zip} - * @since 0.14.0 - * @param {ArrayLike} arrayLike - * @returns {Array} + * @category Function + * @see {@link module:lamb.debounce|debounce} + * @since 0.1.0 + * @param {Function} fn + * @param {Number} timespan - Expressed in milliseconds. + * @returns {Function} */ - function transpose (arrayLike) { - var minLen = MAX_ARRAY_LENGTH; - var len = _toArrayLength(arrayLike.length); - - if (len === 0) { - return []; - } - - for (var j = 0, elementLen; j < len; j++) { - elementLen = _toArrayLength(arrayLike[j].length); - - if (elementLen < minLen) { - minLen = elementLen; - } - } - - var result = Array(minLen); + function throttle (fn, timespan) { + var result; + var lastCall = 0; - for (var i = 0, el; i < minLen; i++) { - el = result[i] = Array(len); + return function () { + var now = Date.now(); - for (j = 0; j < len; j++) { - el[j] = arrayLike[j][i]; + if (now - lastCall >= timespan) { + lastCall = now; + result = fn.apply(this, arguments); } - } - return result; + return result; + }; } /** - * Returns a list of every unique element present in the two given array-like objects.
- * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} - * to test the equality of values.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.unionBy|unionBy} if you need to transform the values before - * the comparison or if you have to extract them from complex ones. - * @example - * _.union([1, 2, 3, 2], [2, 3, 4]) // => [1, 2, 3, 4] - * _.union("abc", "bcd") // => ["a", "b", "c", "d"] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.unionBy|unionBy} - * @see {@link module:lamb.difference|difference} - * @see {@link module:lamb.intersection|intersection} - * @since 0.5.0 - * @param {ArrayLike} a - * @param {ArrayLike} b - * @returns {Array} - */ - var union = unionBy(identity); - - /** - * Using the provided iteratee to transform values, builds a function that will - * return an array of the unique elements in the two provided array-like objects.
- * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} - * to test the equality of values.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.union|union} if you don't need to compare transformed values. + * Builds a function that passes only one argument to the given function.
+ * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, + * exposed for convenience. * @example - * var unionByFloor = _.unionBy(Math.floor); + * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; * - * unionByFloor([2.8, 3.2, 1.5], [3.5, 1.2, 4]) // => [2.8, 3.2, 1.5, 4] + * _.map(weights, _.unary(parseInt)) // => [2, 10, 1, 7] * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.union|union} - * @see {@link module:lamb.difference|difference} - * @see {@link module:lamb.intersection|intersection} - * @since 0.51.0 - * @param {ListIteratorCallback} iteratee + * @category Function + * @see {@link module:lamb.aritize|aritize} + * @see {@link module:lamb.binary|binary} + * @since 0.10.0 + * @param {Function} fn * @returns {Function} */ - function unionBy (iteratee) { - return pipe([binary(list), flatMapWith(drop(0)), uniquesBy(iteratee)]); + function unary (fn) { + return function (a) { + return fn.call(this, a); + }; } /** - * Returns an array comprised of the unique elements of the given array-like object.
- * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} - * to test the equality of values.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.uniquesBy|uniquesBy} if you need to transform your values before - * the comparison or if you have to extract them from complex ones. + * Accepts a series of functions and builds a function that applies the received + * arguments to each one and returns the first non-undefined value.
+ * Meant to work in synergy with {@link module:lamb.case|case} and + * {@link module:lamb.invoker|invoker}, can be useful as a strategy pattern for functions, + * to mimic conditional logic or pattern matching, and also to build polymorphic functions. * @example - * _.uniques([-0, 1, 2, 0, 2, 3, 4, 3, 5, 1]) // => [-0, 1, 2, 3, 4, 5] + * var isEven = function (n) { return n % 2 === 0; }; + * var filterString = _.compose(_.invoker("join", ""), _.filter); + * var filterAdapter = _.adapter([ + * _.invoker("filter"), + * _.case(_.isType("String"), filterString) + * ]); + * + * filterAdapter([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] + * filterAdapter("123456", isEven) // => "246" + * filterAdapter({}, isEven) // => undefined + * + * // by its nature is composable + * var filterWithDefault = _.adapter([filterAdapter, _.always("Not implemented")]); + * + * filterWithDefault([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] + * filterWithDefault("123456", isEven) // => "246" + * filterWithDefault({}, isEven) // => "Not implemented" * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.uniquesBy|uniquesBy} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @returns {Array} + * @category Logic + * @see {@link module:lamb.case|case} + * @see {@link module:lamb.invoker|invoker} + * @since 0.6.0 + * @param {Function[]} functions + * @returns {Function} */ - var uniques = uniquesBy(identity); + function adapter (functions) { + if (!Array.isArray(functions)) { + throw _makeTypeErrorFor(functions, "array"); + } + + return function () { + var len = functions.length; + var result; + + for (var i = 0; i < len; i++) { + result = functions[i].apply(this, arguments); + + if (!isUndefined(result)) { + break; + } + } + + return result; + }; + } /** - * Using the provided iteratee, builds a function that will return an array comprised of the - * unique elements of an array-like object. The values being compared are the ones returned by - * the iteratee.
- * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.uniques|uniques} if you don't need to transform your values before the - * comparison. - * @example - * var data = [ - * {id: "1", name: "John"}, - * {id: "4", name: "Jane"}, - * {id: "5", name: "Joe"}, - * {id: "1", name: "Mario"}, - * {id: "5", name: "Paolo"}, - * ]; - * var uniquesById = _.uniquesBy(_.getKey("id")); - * - * uniquesById(data) // => [{id: "1", name: "John"}, {id: "4", name: "Jane"}, {id: "5", name: "Joe"}] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.uniques|uniques} - * @since 0.51.0 - * @param {ListIteratorCallback} iteratee + * Creates a function to check the given predicates.
+ * Used to build the {@link module:lamb.allOf|allOf} and the + * {@link module:lamb.anyOf|anyOf} functions. + * @private + * @param {Boolean} checkAll * @returns {Function} */ - function uniquesBy (iteratee) { - return function (arrayLike) { - var result = []; + function _checkPredicates (checkAll) { + return function (predicates) { + if (!Array.isArray(predicates)) { + throw _makeTypeErrorFor(predicates, "array"); + } - for (var i = 0, len = arrayLike.length, seen = [], value; i < len; i++) { - value = iteratee(arrayLike[i], i, arrayLike); + return function () { + for (var i = 0, len = predicates.length, result; i < len; i++) { + result = predicates[i].apply(this, arguments); - if (!isIn(seen, value)) { - seen.push(value); - result.push(arrayLike[i]); + if (checkAll && !result) { + return false; + } else if (!checkAll && result) { + return true; + } } - } - return result; + return checkAll; + }; }; } /** - * Builds a list of arrays out of the two given array-like objects by pairing items with - * the same index.
- * The received array-like objects will be truncated to the shortest length. + * Accepts an array of predicates and builds a new one that returns true if they are all satisfied + * by the same arguments. The functions in the array will be applied one at a time until a + * false value is produced, which is returned immediately. * @example - * _.zip( - * ["a", "b", "c"], - * [1, 2, 3] - * ) // => [["a", 1], ["b", 2], ["c", 3]] + * var isEven = function (n) { return n % 2 === 0; }; + * var isPositiveEven = _.allOf([isEven, _.isGT(0)]); * - * _.zip([1, 2, 3, 4], [5, 6, 7]) // => [[1, 5], [2, 6], [3, 7]] + * isPositiveEven(-2) // => false + * isPositiveEven(11) // => false + * isPositiveEven(6) // => true * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.transpose|transpose} for the reverse operation - * @see {@link module:lamb.zipWithIndex|zipWithIndex} - * @since 0.14.0 - * @param {ArrayLike} a - * @param {ArrayLike} b - * @returns {Array} + * @category Logic + * @function + * @see {@link module:lamb.anyOf|anyOf} + * @since 0.1.0 + * @param {Function[]} predicates + * @returns {Function} */ - function zip (a, b) { - return transpose([a, b]); - } + var allOf = _checkPredicates(true); /** - * "{@link module:lamb.zip|Zips}" an array-like object by pairing its values with their index. + * Accepts an array of predicates and builds a new one that returns true if at least one of them is + * satisfied by the received arguments. The functions in the array will be applied one at a time + * until a true value is produced, which is returned immediately. * @example - * _.zipWithIndex(["a", "b", "c"]) // => [["a", 0], ["b", 1], ["c", 2]] + * var users = [ + * {id: 1, name: "John", group: "guest"}, + * {id: 2, name: "Jane", group: "root"}, + * {id: 3, name: "Mario", group: "admin"} + * ]; + * var isInGroup = _.partial(_.hasKeyValue, ["group"]); + * var isSuperUser = _.anyOf([isInGroup("admin"), isInGroup("root")]); + * + * isSuperUser(users[0]) // => false + * isSuperUser(users[1]) // => true + * isSuperUser(users[2]) // => true * * @memberof module:lamb - * @category Array + * @category Logic * @function - * @see {@link module:lamb.zip|zip} - * @since 0.14.0 - * @param {ArrayLike} arrayLike - * @returns {Array>} + * @see {@link module:lamb.allOf|allOf} + * @since 0.1.0 + * @param {Function[]} predicates + * @returns {Function} */ - var zipWithIndex = mapWith(binary(list)); - - lamb.append = append; - lamb.appendTo = appendTo; - lamb.difference = difference; - lamb.drop = drop; - lamb.dropFrom = dropFrom; - lamb.dropWhile = dropWhile; - lamb.flatMap = flatMap; - lamb.flatMapWith = flatMapWith; - lamb.flatten = flatten; - lamb.init = init; - lamb.insert = insert; - lamb.insertAt = insertAt; - lamb.intersection = intersection; - lamb.partition = partition; - lamb.partitionWith = partitionWith; - lamb.pluck = pluck; - lamb.pluckKey = pluckKey; - lamb.pull = pull; - lamb.pullFrom = pullFrom; - lamb.rotate = rotate; - lamb.rotateBy = rotateBy; - lamb.shallowFlatten = shallowFlatten; - lamb.tail = tail; - lamb.take = take; - lamb.takeFrom = takeFrom; - lamb.takeWhile = takeWhile; - lamb.transpose = transpose; - lamb.union = union; - lamb.unionBy = unionBy; - lamb.uniques = uniques; - lamb.uniquesBy = uniquesBy; - lamb.zip = zip; - lamb.zipWithIndex = zipWithIndex; + var anyOf = _checkPredicates(false); /** - * Transforms an array-like object in a lookup table with the keys generated by the provided - * iteratee, having as values the count of matches for the key. + * Verifies that the two supplied values are the same value using the "SameValue" comparison.
+ * Note that this doesn't behave as the strict equality operator, but rather as a shim of ES6's + * [Object.is]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is}. + * Differences are that 0 and -0 aren't the same value and, finally, + * NaN is equal to itself.
+ * See also {@link module:lamb.is|is} for a curried version building a predicate and + * {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} to perform a "SameValueZero" + * comparison. * @example - * var persons = [ - * {"name": "Jane", "age": 12}, - * {"name": "John", "age": 40}, - * {"name": "Mario", "age": 17}, - * {"name": "Paolo", "age": 15} - * ]; - * var getAgeStatus = function (person) { return person.age >= 18 ? "adult" : "minor"; }; + * var testObject = {}; * - * _.count(persons, getAgeStatus) // => {"adult": 1, "minor": 3} + * _.areSame({}, testObject) // => false + * _.areSame(testObject, testObject) // => true + * _.areSame("foo", "foo") // => true + * _.areSame(0, -0) // => false + * _.areSame(0 / 0, NaN) // => true * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.21.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Object} + * @category Logic + * @see {@link module:lamb.is|is} + * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.50.0 + * @param {*} a + * @param {*} b + * @returns {Boolean} */ - var count = _groupWith(function (a) { - return a ? ++a : 1; - }); + function areSame (a, b) { + return a === 0 && b === 0 ? 1 / a === 1 / b : areSVZ(a, b); + } /** - * A curried version of {@link module:lamb.count|count} that uses the provided iteratee to - * build a function expecting the array-like object to act upon. + * Builds a case for {@link module:lamb.adapter|adapter}.
+ * The function will apply the received arguments to fn if the predicate is satisfied + * with the same arguments, otherwise will return undefined.
+ * See also {@link module:lamb.condition|condition} to build a condition with two branching functions + * and {@link module:lamb.unless|unless} and {@link module:lamb.when|when} where one of the branches + * is the identity function. * @example - * var persons = [ - * {"name": "Jane", "city": "New York"}, - * {"name": "John", "city": "New York"}, - * {"name": "Mario", "city": "Rome"}, - * {"name": "Paolo"} - * ]; - * var getCityOrUnknown = _.adapter([_.getKey("city"), _.always("Unknown")]); - * var countByCity = _.countBy(getCityOrUnknown); + * var halveIfNumber = _.case(_.isType("Number"), _.divideBy(2)); * - * countByCity(persons) // => {"New York": 2, "Rome": 1, "Unknown": 1} + * halveIfNumber(2) // => 1 + * halveIfNumber("2") // => undefined * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.count|count} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.21.0 - * @param {ListIteratorCallback} iteratee + * @alias module:lamb.case + * @category Logic + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.condition|condition} + * @see {@link module:lamb.unless|unless} + * @see {@link module:lamb.when|when} + * @since 0.51.0 + * @param {Function} predicate + * @param {Function} fn * @returns {Function} */ - var countBy = _curry2(count, true); + function case_ (predicate, fn) { + return function () { + return predicate.apply(this, arguments) ? fn.apply(this, arguments) : void 0; + }; + } /** - * Transforms an array-like object into a lookup table using the provided iteratee as a grouping - * criterion to generate keys and values. + * Builds a function that will apply the received arguments to trueFn, + * if the predicate is satisfied with the same arguments, or to falseFn otherwise.
+ * Although you can use other conditions as trueFn or falseFn, + * it's probably better to use {@link module:lamb.adapter|adapter} to build more complex behaviours.
+ * See also {@link module:lamb.unless|unless} and {@link module:lamb.when|when} as they are + * shortcuts to common use cases. * @example - * var persons = [ - * {"name": "Jane", "city": "New York"}, - * {"name": "John", "city": "New York"}, - * {"name": "Mario", "city": "Rome"}, - * {"name": "Paolo"} - * ]; - * var getCity = _.getKey("city"); - * var personsByCity = _.group(persons, getCity); - * - * // "personsByCity" holds: - * // { - * // "New York": [ - * // {"name": "Jane", "city": "New York"}, - * // {"name": "John", "city": "New York"} - * // ], - * // "Rome": [ - * // {"name": "Mario", "city": "Rome"} - * // ], - * // "undefined": [ - * // {"name": "Paolo"} - * // ] - * // } + * var isEven = function (n) { return n % 2 === 0}; + * var halveEvenAndDoubleOdd = _.condition(isEven, _.divideBy(2), _.multiplyBy(2)); * - * @example Adding a custom value for missing keys: + * halveEvenAndDoubleOdd(5) // => 10 + * halveEvenAndDoubleOdd(6) // => 3 * - * var getCityOrUnknown = _.adapter([getCity, _.always("Unknown")]); + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.unless|unless} + * @see {@link module:lamb.when|when} + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.case|case} + * @since 0.2.0 + * @param {Function} predicate + * @param {Function} trueFn + * @param {Function} falseFn + * @returns {Function} + */ + function condition (predicate, trueFn, falseFn) { + return function () { + return (predicate.apply(this, arguments) ? trueFn : falseFn).apply(this, arguments); + }; + } + + /** + * Verifies that the first given value is greater than the second.
+ * Wraps the native > operator within a function. + * @example + * var pastDate = new Date(2010, 2, 12); + * var today = new Date(); * - * var personsByCity = _.group(persons, getCityOrUnknown); + * _.gt(today, pastDate) // => true + * _.gt(pastDate, today) // => false + * _.gt(3, 4) // => false + * _.gt(3, 3) // => false + * _.gt(3, 2) // => true + * _.gt(0, -0) // => false + * _.gt(-0, 0) // => false + * _.gt("a", "A") // => true + * _.gt("b", "a") // => true * - * // "personsByCity" holds: - * // { - * // "New York": [ - * // {"name": "Jane", "city": "New York"}, - * // {"name": "John", "city": "New York"} - * // ], - * // "Rome": [ - * // {"name": "Mario", "city": "Rome"} - * // ], - * // "Unknown": [ - * // {"name": "Paolo"} - * // ] - * // } + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.gte|gte} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} + */ + function gt (a, b) { + return a > b; + } + + /** + * Verifies that the first given value is greater than or equal to the second. + * Regarding equality, beware that this is simply a wrapper for the native + * >= operator, so -0 === 0. + * @example + * _.gte(3, 4) // => false + * _.gte(3, 3) // => true + * _.gte(3, 2) // => true + * _.gte(0, -0) // => true + * _.gte(-0, 0) // => true * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.groupBy|groupBy} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.7.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Object} + * @category Logic + * @see {@link module:lamb.gt|gt} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} */ - var group = _groupWith(function (a, b) { - if (!a) { - return [b]; - } - - a[a.length] = b; - - return a; - }); + function gte (a, b) { + return a >= b; + } /** - * A curried version of {@link module:lamb.group|group} that uses the provided iteratee - * to build a function expecting the array-like object to act upon. + * A curried version of {@link module:lamb.areSame|areSame}.
+ * Accepts a value and builds a predicate that checks whether the value + * and the one received by the predicate are the same using the "SameValue" + * comparison.
+ * See also {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} + * to perform a "SameValueZero" comparison. * @example - * var persons = [ - * {"name": "Jane", "age": 12}, - * {"name": "John", "age": 40}, - * {"name": "Mario", "age": 18}, - * {"name": "Paolo", "age": 15} - * ]; + * var john = {name: "John", surname: "Doe"}; + * var isJohn = _.is(john); + * var isNegativeZero = _.is(-0); + * var isReallyNaN = _.is(NaN); * - * var getAgeStatus = function (person) { return person.age > 20 ? "over 20" : "under 20"; }; - * var groupByAgeStatus = _.groupBy(getAgeStatus); + * isJohn(john) // => true + * isJohn({name: "John", surname: "Doe"}) // => false * - * var personsByAgeStatus = groupByAgeStatus(persons); + * isNegativeZero(0) // => false + * isNegativeZero(-0) // => true * - * // "personsByAgeStatus" holds: - * // { - * // "under 20": [ - * // {"name": "Jane", "age": 12}, - * // {"name": "Mario", "age": 18}, - * // {"name": "Paolo", "age": 15} - * // ], - * // "over 20": [ - * // {"name": "John", "age": 40} - * // ] - * // } + * isNaN(NaN) // => true + * isNaN("foo") // => true + * + * isReallyNaN(NaN) // => true + * isReallyNaN("foo") // => false * * @memberof module:lamb - * @category Array + * @category Logic * @function - * @see {@link module:lamb.group|group} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.7.0 - * @param {ListIteratorCallback} iteratee + * @see {@link module:lamb.areSame|areSame} + * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.1.0 + * @param {*} value * @returns {Function} */ - var groupBy = _curry2(group, true); + var is = _curry2(areSame); /** - * Similar to {@link module:lamb.group|group}, but the generated lookup table will have - * only one element of the original array-like object for each value.
- * Should be used only when you're sure that your iteratee won't produce - * duplicate keys, otherwise only the last evaluated element will be in the result. + * A right curried version of {@link module:lamb.gt|gt}.
+ * Accepts a value and builds a predicate that checks whether the value + * is greater than the one received by the predicate. * @example - * var users = [ - * {id: 1, name: "John"}, - * {id: 2, name: "Jane"}, - * {id: 3, name: "Mario"}, - * {id: 4, name: "John"} - * ]; - * - * var indexedUsers = _.index(users, _.getKey("id")); - * - * // "indexedUsers" holds: - * // { - * // "1": {id: 1, name: "John"}, - * // "2": {id: 2, name: "Jane"}, - * // "3": {id: 3, name: "Mario"}, - * // "4": {id: 4, name: "John"} - * // } - * - * @example Result of an iteratee producing a duplicate key: - * var users = [ - * {id: 1, name: "John"}, - * {id: 2, name: "Jane"}, - * {id: 3, name: "Mario"}, - * {id: 4, name: "John"} - * ]; - * - * var indexedUsers = _.index(users, _.getKey("name")); + * var isGreaterThan5 = _.isGT(5); * - * // "indexedUsers" holds: - * // { - * // "John": {"id": 4, "name": "John"}, - * // "Jane": {"id": 2, "name": "Jane"}, - * // "Mario": {"id": 3, "name": "Mario"} - * // } + * isGreaterThan5(3) // => false + * isGreaterThan5(5) // => false + * isGreaterThan5(7) // => true * * @memberof module:lamb - * @category Array + * @category Logic * @function - * @see {@link module:lamb.indexBy|indexBy} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @since 0.21.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Object} + * @see {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @since 0.1.0 + * @param {Number|String|Date|Boolean} value + * @returns {Function} */ - var index = _groupWith(function (a, b) { - return b; - }); + var isGT = _curry2(gt, true); /** - * A curried version of {@link module:lamb.index|index} that uses the provided iteratee - * to build a function expecting the array-like object to act upon. + * A right curried version of {@link module:lamb.gte|gte}.
+ * Accepts a value and builds a predicate that checks whether the value + * is greater than or equal to the one received by the predicate. * @example - * var users = [ - * {id: 1, name: "John"}, - * {id: 2, name: "Jane"}, - * {id: 3, name: "Mario"} - * ]; - * var indexByID = _.indexBy(_.getKey("id")); - * - * var indexedUsers = indexByID(users); + * var isPositiveOrZero = _.isGTE(0); * - * // "indexedUsers" holds: - * // { - * // "1": {id: 1, name: "John"}, - * // "2": {id: 2, name: "Jane"}, - * // "3": {id: 3, name: "Mario"} - * // } + * isPositiveOrZero(-3) // => false + * isPositiveOrZero(-0) // => true + * isPositiveOrZero(0) // => true + * isPositiveOrZero(5) // => true * * @memberof module:lamb - * @category Array + * @category Logic * @function - * @see {@link module:lamb.index|index} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @since 0.21.0 - * @param {ListIteratorCallback} iteratee + * @see {@link module:lamb.isGT|isGT} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @since 0.1.0 + * @param {Number|String|Date|Boolean} value * @returns {Function} */ - var indexBy = _curry2(index, true); - - lamb.count = count; - lamb.countBy = countBy; - lamb.group = group; - lamb.groupBy = groupBy; - lamb.index = index; - lamb.indexBy = indexBy; + var isGTE = _curry2(gte, true); /** - * Returns a [stably]{@link https://en.wikipedia.org/wiki/Sorting_algorithm#Stability} sorted - * copy of an array-like object using the given criteria.
- * Sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, but you - * can also pass simple "reader" functions and default ascending sorters will be built for you.
- * A "reader" is a function that evaluates the array element and supplies the value to be used - * in the comparison.
- * Please note that if the arguments received by the default comparer aren't of the same type, - * they will be compared as strings. - * - * @example Stable sort: - * var persons = [ - * {"name": "John", "surname" :"Doe"}, - * {"name": "Mario", "surname": "Rossi"}, - * {"name": "John", "surname" :"Moe"}, - * {"name": "Jane", "surname": "Foe"} - * ]; - * - * var personsByName = _.sort(persons, [_.getKey("name")]); - * - * // personsByName holds: - * // [ - * // {"name": "Jane", "surname": "Foe"}, - * // {"name": "John", "surname" :"Doe"}, - * // {"name": "John", "surname" :"Moe"}, - * // {"name": "Mario", "surname": "Rossi"} - * // ] - * - * @example Stable multi-sort: - * var personsByNameAscSurnameDesc = _.sort(persons, [ - * _.getKey("name"), - * _.sorterDesc(_.getKey("surname")) - * ]); - * - * // personsByNameAscSurnameDesc holds: - * // [ - * // {"name": "Jane", "surname": "Foe"}, - * // {"name": "John", "surname" :"Moe"}, - * // {"name": "John", "surname" :"Doe"}, - * // {"name": "Mario", "surname": "Rossi"} - * // ] - * - * @example Using custom comparers: - * var localeSorter = new Intl.Collator("it"); - * var chars = ["a", "è", "à", "é", "c", "b", "e"]; - * - * _.sort(chars, [localeSorter]) // => ["a", "à", "b", "c", "e", "é", "è"] - * - * var localeSorterDesc = _.sorterDesc(_.identity, localeSorter.compare); + * Verifies that the first given value is less than the second.
+ * Wraps the native < operator within a function. + * @example + * var pastDate = new Date(2010, 2, 12); + * var today = new Date(); * - * _.sort(chars, [localeSorterDesc]) // => ["è", "é", "e", "c", "b", "à", "a"] + * _.lt(today, pastDate) // => false + * _.lt(pastDate, today) // => true + * _.lt(3, 4) // => true + * _.lt(3, 3) // => false + * _.lt(3, 2) // => false + * _.lt(0, -0) // => false + * _.lt(-0, 0) // => false + * _.lt("a", "A") // => false + * _.lt("a", "b") // => true * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} - * @since 0.15.0 - * @param {ArrayLike} arrayLike - * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - * @returns {Array} + * @category Logic + * @see {@link module:lamb.lte|lte} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} */ - function sort (arrayLike, sorters) { - var criteria = _makeCriteria(sorters); - var len = _toArrayLength(arrayLike.length); - var result = Array(len); - - for (var i = 0; i < len; i++) { - result[i] = {value: arrayLike[i], index: i}; - } - - result.sort(_compareWith(criteria)); - - for (i = 0; i < len; i++) { - result[i] = result[i].value; - } - - return result; + function lt (a, b) { + return a < b; } /** - * Inserts an element in a copy of a sorted array respecting the sort order. - * @example With simple values: - * _.sortedInsert([], 1) // => [1] - * _.sortedInsert([2, 4, 6], 5) // => [2, 4, 5, 6] - * _.sortedInsert([4, 2, 1], 3, _.sorterDesc()) // => [4, 3, 2, 1] - * - * @example With complex values: - * var persons = [ - * {"name": "jane", "surname": "doe"}, - * {"name": "John", "surname": "Doe"}, - * {"name": "Mario", "surname": "Rossi"} - * ]; - * - * var getLowerCaseName = _.compose( - * _.invoker("toLowerCase"), - * _.getKey("name") - * ); - * - * var result = _.sortedInsert( - * persons, - * {"name": "marco", "surname": "Rossi"}, - * getLowerCaseName - * ); + * A right curried version of {@link module:lamb.lt|lt}.
+ * Accepts a value and builds a predicate that checks whether the value + * is less than the one received by the predicate. + * @example + * var isLessThan5 = _.isLT(5); * - * // `result` holds: - * // [ - * // {"name": "jane", "surname": "doe"}, - * // {"name": "John", "surname": "Doe"}, - * // {"name": "marco", "surname": "Rossi"}, - * // {"name": "Mario", "surname": "Rossi"} - * // ] + * isLessThan5(7) // => false + * isLessThan5(5) // => false + * isLessThan5(3) // => true * * @memberof module:lamb - * @category Array - * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} - * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} to insert the element - * at a specific index - * @since 0.27.0 - * @param {ArrayLike} arrayLike - * @param {*} element - * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - The sorting criteria - * used to sort the array. - * @returns {Array} + * @category Logic + * @function + * @see {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @since 0.1.0 + * @param {Number|String|Date|Boolean} value + * @returns {Function} */ - function sortedInsert (arrayLike, element, sorters) { - var result = slice(arrayLike, 0, arrayLike.length); - - if (arguments.length === 1) { - return result; - } - - var criteria = _makeCriteria(sorters); - var idx = _getInsertionIndex(result, element, _compareWith(criteria), 0, result.length); - - result.splice(idx, 0, element); + var isLT = _curry2(lt, true); - return result; + /** + * Verifies that the first given value is less than or equal to the second. + * Regarding equality, beware that this is simply a wrapper for the native + * <= operator, so -0 === 0. + * @example + * _.lte(3, 4) // => true + * _.lte(3, 3) // => true + * _.lte(3, 2) // => false + * _.lte(0, -0) // => true + * _.lte(-0, 0) // => true + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.lt|lt} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} + */ + function lte (a, b) { + return a <= b; } /** - * Creates an ascending sort criterion with the provided reader and - * comparer.
- * See {@link module:lamb.sort|sort} for various examples. + * A right curried version of {@link module:lamb.lte|lte}.
+ * Accepts a value and builds a predicate that checks whether the value + * is less than or equal to the one received by the predicate. + * @example + * var isNegativeOrZero = _.isLTE(0); + * + * isNegativeOrZero(5) // => false + * isNegativeOrZero(-0) // => true + * isNegativeOrZero(0) // => true + * isNegativeOrZero(-3) // => true * * @memberof module:lamb - * @category Array + * @category Logic * @function - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorterDesc|sorterDesc} + * @see {@link module:lamb.isLT|isLT} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} * @since 0.1.0 - * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a - * simple value from a complex one. The function should evaluate the array element and supply the - * value to be passed to the comparer. - * @param {Function} [comparer] An optional custom comparer function. - * @returns {Sorter} + * @param {Number|String|Date|Boolean} value + * @returns {Function} */ - var sorter = partial(_sorter, [_, false, _]); + var isLTE = _curry2(lte, true); /** - * Creates a descending sort criterion with the provided reader and - * comparer.
- * See {@link module:lamb.sort|sort} for various examples. + * Builds a unary function that will check its argument against the given predicate. + * If the predicate isn't satisfied, the provided fn function will be + * applied to the same value. The received argument is returned as it is otherwise.
+ * See {@link module:lamb.when|when} for the opposite behaviour.
+ * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, + * where its trueFn parameter is the [identity function]{@link module:lamb.identity}. + * @example + * var isEven = function (n) { return n % 2 === 0}; + * var halveUnlessIsEven = _.unless(isEven, _.divideBy(2)); + * + * halveUnlessIsEven(5) // => 2.5 + * halveUnlessIsEven(6) // => 6 * * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorter|sorter} - * @since 0.15.0 - * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a - * simple value from a complex one. The function should evaluate the array element and supply the - * value to be passed to the comparer. - * @param {Function} [comparer] An optional custom comparer function. - * @returns {Sorter} + * @category Logic + * @see {@link module:lamb.condition|condition} + * @see {@link module:lamb.when|when} + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.case|case} + * @since 0.42.0 + * @param {Function} predicate + * @param {Function} fn + * @returns {Function} */ - var sorterDesc = partial(_sorter, [_, true, _]); + function unless (predicate, fn) { + return function (value) { + return predicate.call(this, value) ? value : fn.call(this, value); + }; + } /** - * Builds a partial application of {@link module:lamb.sort|sort} using the provided criteria. - * The returned function expects the array-like object to sort. - * As usual, sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, - * but you can also pass simple "reader" functions and default ascending sorters will be built.
- * A "reader" is a function that evaluates the array element and supplies the value to be used in - * the comparison.
- * See {@link module:lamb.sort|sort} for more examples. + * Builds a unary function that will check its argument against the given predicate. + * If the predicate is satisfied, the provided fn function will be + * applied to the same value. The received argument is returned as it is otherwise.
+ * See {@link module:lamb.unless|unless} for the opposite behaviour.
+ * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, + * where its falseFn parameter is the [identity function]{@link module:lamb.identity}. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var halveIfEven = _.when(isEven, _.divideBy(2)); + * + * halveIfEven(5) // => 5 + * halveIfEven(6) // => 3 * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.condition|condition} + * @see {@link module:lamb.unless|unless} + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.case|case} + * @since 0.42.0 + * @param {Function} predicate + * @param {Function} fn + * @returns {Function} + */ + function when (predicate, fn) { + return function (value) { + return predicate.call(this, value) ? fn.call(this, value) : value; + }; + } + + /** + * Sums two numbers. + * @example + * _.sum(4, 5) // => 9 + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.add|add} + * @since 0.50.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ + function sum (a, b) { + return a + b; + } + + /** + * A curried version of {@link module:lamb.sum|sum}. * @example - * var sortAsNumbers = _.sortWith([parseFloat]); - * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; + * var add5 = _.add(5); * - * sortAsNumbers(weights) // => ["1 Kg", "2 Kg", "7 Kg", "10 Kg"] + * _.add5(4) // => 9 + * _.add5(-2) // => 3 * * @memberof module:lamb - * @category Array + * @category Math * @function - * @see {@link module:lamb.sort|sort} - * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} - * @since 0.15.0 - * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] + * @see {@link module:lamb.sum|sum} + * @since 0.1.0 + * @param {Number} a * @returns {Function} */ - var sortWith = _curry2(sort, true); - - lamb.sort = sort; - lamb.sortedInsert = sortedInsert; - lamb.sorter = sorter; - lamb.sorterDesc = sorterDesc; - lamb.sortWith = sortWith; + var add = _curry2(sum, true); /** - * Applies the given function to a list of arguments. + * Subtracts two numbers. * @example - * _.application(_.sum, [3, 4]) // => 7 + * _.subtract(5, 3) // => 2 * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.apply|apply}, {@link module:lamb.applyTo|applyTo} - * @since 0.47.0 - * @param {Function} fn - * @param {ArrayLike} args - * @returns {*} + * @category Math + * @see {@link module:lamb.deduct|deduct} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} */ - function application (fn, args) { - return fn.apply(this, Object(args)); + function subtract (a, b) { + return a - b; } /** - * A left-curried version of {@link module:lamb.application|application}. Expects the function - * to apply and builds a function waiting for the arguments array. + * A curried version of {@link module:lamb.subtract|subtract} that expects the + * subtrahend to build a function waiting for the minuend. * @example - * var arrayMax = _.apply(Math.max); + * var deduct5 = _.deduct(5); * - * arrayMax([4, 5, 2, 6, 1]) // => 6 + * deduct5(12) // => 7 + * deduct5(3) // => -2 * * @memberof module:lamb - * @category Function + * @category Math * @function - * @see {@link module:lamb.application|application}, {@link module:lamb.applyTo|applyTo} - * @since 0.1.0 - * @param {Function} fn + * @see {@link module:lamb.subtract|subtract} + * @since 0.50.0 + * @param {Number} a * @returns {Function} */ - var apply = _curry2(application); + var deduct = _curry2(subtract, true); /** - * A right-curried version of {@link module:lamb.application|application}. Expects an array-like - * object to use as arguments and builds a function waiting for the target of the application. + * Divides two numbers. * @example - * var data = [3, 4]; - * var applyToData = _.applyTo(data); + * _.divide(5, 2) // => 2.5 * - * applyToData(_.sum) // => 7 - * applyToData(_.multiply) // => 12 + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.divideBy|divideBy} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ + function divide (a, b) { + return a / b; + } + + /** + * A curried version of {@link module:lamb.divide|divide} that expects a divisor to + * build a function waiting for the dividend. + * @example + * var halve = divideBy(2); + * + * halve(10) // => 5 + * halve(5) // => 2.5 * * @memberof module:lamb - * @category Function + * @category Math * @function - * @see {@link module:lamb.application|application}, {@link module:lamb.apply|apply} - * @since 0.47.0 - * @param {ArrayLike} args + * @see {@link module:lamb.divide|divide} + * @since 0.50.0 + * @param {Number} a * @returns {Function} */ - var applyTo = _curry2(application, true); + var divideBy = _curry2(divide, true); /** - * Builds a new function that passes only the specified amount of arguments to the original one.
- * As {@link module:lamb.slice|slice} is used to extract the arguments, you can also - * pass a negative arity. + * Generates a sequence of values of the desired length with the provided iteratee. + * The values being iterated, and received by the iteratee, are the results generated so far. * @example - * Math.max(10, 11, 45, 99) // => 99 - * _.aritize(Math.max, 2)(10, 11, 45, 99) // => 11 + * var fibonacci = function (n, idx, results) { + * return n + (results[idx - 1] || 0); + * }; * - * @example Using a negative arity: - * _.aritize(Math.max, -1)(10, 11, 45, 99) // => 45 + * _.generate(1, 10, fibonacci) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.binary|binary}, {@link module:lamb.unary|unary} for common use cases shortcuts - * @since 0.1.0 - * @param {Function} fn - * @param {Number} arity - * @returns {Function} + * @category Math + * @see {@link module:lamb.range|range} + * @since 0.21.0 + * @param {*} start - The starting value + * @param {Number} len - The desired length for the sequence + * @param {ListIteratorCallback} iteratee + * @returns {Array} */ - function aritize (fn, arity) { - return function () { - var n = _toInteger(arity); - var args = list.apply(null, arguments).slice(0, n); + function generate (start, len, iteratee) { + var result = [start]; - for (var i = args.length; i < n; i++) { - args[i] = void 0; - } + for (var i = 0, limit = len - 1; i < limit; i++) { + result.push(iteratee(result[i], i, result)); + } - return fn.apply(this, args); - }; + return result; } /** - * Decorates the received function so that it can be called with - * placeholders to build a partial application of it.
- * The difference with {@link module:lamb.partial|partial} is that, as long as - * you call the generated function with placeholders, another partial application - * of the original function will be built.
- * The final application will happen when one of the generated functions is - * invoked without placeholders, using the parameters collected so far.
- * This function comes in handy when you need to build different specialized - * functions starting from a basic one, but it's also useful when dealing with - * optional parameters as you can decide to apply the function even if its arity - * hasn't been entirely consumed. - * @example Explaining the function's behaviour: - * var f = _.asPartial(function (a, b, c) { - * return a + b + c; - * }); + * Verifies whether the received value is a finite number.
+ * Behaves almost as a shim of ES6's [Number.isFinite]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite}, + * but with a difference: it will return true even for Number object's instances. + * @example + * _.isFinite(5) // => true + * _.isFinite(new Number(5)) // => true + * _.isFinite(Infinity) // => false + * _.isFinite(-Infinity) // => false + * _.isFinite("5") // => false + * _.isFinite(NaN) // => false + * _.isFinite(null) // => false * - * f(4, 3, 2) // => 9 - * f(4, _, 2)(3) // => 9 - * f(_, 3, _)(4, _)(2) // => 9 + * @alias module:lamb.isFinite + * @category Math + * @since 0.46.0 + * @param {*} value + * @returns {Boolean} + */ + function isFinite_ (value) { + return type(value) === "Number" && isFinite(value); + } + + /** + * Verifies whether the received value is a number and an integer. + * Behaves almost as a shim of ES6's [Number.isInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger}, + * but with a difference: it will return true even for Number object's instances. + * @example + * _.isInteger(5) // => true + * _.isInteger(new Number(5)) // => true + * _.isInteger(2.5) // => false + * _.isInteger(Infinity) // => false + * _.isInteger(-Infinity) // => false + * _.isInteger("5") // => false + * _.isInteger(NaN) // => false * - * @example Exploiting optional parameters: - * var f = _.asPartial(function (a, b, c) { - * return a + b + (c || 0); - * }); + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.isSafeInteger|isSafeInteger} + * @since 0.46.0 + * @param {*} value + * @returns {Boolean} + */ + function isInteger (value) { + return type(value) === "Number" && value % 1 === 0; + } + + /** + * Verifies whether the received value is a "safe integer", meaning that is a number and that + * can be exactly represented as an IEEE-754 double precision number. + * The safe integers consist of all integers from -(253 - 1) inclusive to + * 253 - 1 inclusive.
+ * Behaves almost as a shim of ES6's [Number.isSafeInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger}, + * but with a difference: it will return true even for Number object's instances. + * @example + * _.isSafeInteger(5) // => true + * _.isSafeInteger(new Number(5)) // => true + * _.isSafeInteger(Math.pow(2, 53) - 1) // => true + * _.isSafeInteger(Math.pow(2, 53)) // => false + * _.isSafeInteger(2e32) // => false + * _.isSafeInteger(2.5) // => false + * _.isSafeInteger(Infinity) // => false + * _.isSafeInteger(-Infinity) // => false + * _.isSafeInteger("5") // => false + * _.isSafeInteger(NaN) // => false * - * var addFive = f(5, _); - * addFive(2) // => 7 + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.isInteger|isInteger} + * @since 0.46.0 + * @param {*} value + * @returns {Boolean} + */ + function isSafeInteger (value) { + return isInteger(value) && Math.abs(value) <= MAX_SAFE_INTEGER; + } + + /** + * Performs the modulo operation and should not be confused with the + * {@link module:lamb.remainder|remainder}. + * The function performs a floored division to calculate the result and not + * a truncated one, hence the sign of the dividend is not kept, unlike the + * {@link module:lamb.remainder|remainder}. + * @example + * _.modulo(5, 3) // => 2 + * _.remainder(5, 3) // => 2 * - * var addNine = addFive(4, _); - * addNine(11) // => 20 + * _.modulo(-5, 3) // => 1 + * _.remainder(-5, 3) // => -2 * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} - * @since 0.36.0 - * @param {Function} fn - * @returns {Function} + * @category Math + * @see {@link module:lamb.remainder|remainder} + * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} */ - function asPartial (fn) { - return _asPartial(fn, []); + function modulo (a, b) { + return a - (b * Math.floor(a / b)); } /** - * Builds a function that passes only two arguments to the given function.
- * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, - * exposed for convenience. + * Multiplies two numbers. * @example - * _.list(1, 2, 3, 4, 5) // => [1, 2, 3, 4, 5] - * _.binary(_.list)(1, 2, 3, 4, 5) // => [1, 2] + * _.multiply(5, 3) // => 15 * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.aritize|aritize} - * @see {@link module:lamb.unary|unary} - * @since 0.10.0 - * @param {Function} fn - * @returns {Function} + * @category Math + * @see {@link module:lamb.multiplyBy|multiplyBy} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} */ - function binary (fn) { - return function (a, b) { - return fn.call(this, a, b); - }; + function multiply (a, b) { + return a * b; } /** - * Accepts a series of functions and builds a new function. The functions in the series - * will then be applied, in order, with the values received by the function built with - * collect.
- * The collected results will be returned in an array. + * A curried version of {@link module:lamb.multiply|multiply}. * @example - * var user = { - * id: "jdoe", - * name: "John", - * surname: "Doe", - * scores: [2, 4, 7] - * }; - * var getIDAndLastScore = _.collect([_.getKey("id"), _.getPath("scores.-1")]); + * var double = _.multiplyBy(2); * - * getIDAndLastScore(user) // => ["jdoe", 7] + * double(5) // => 10 * + * @memberof module:lamb + * @category Math + * @function + * @see {@link module:lamb.multiply|multiply} + * @since 0.50.0 + * @param {Number} a + * @returns {Function} + */ + var multiplyBy = _curry2(multiply, true); + + /** + * Generates a random integer between two given integers, both included. + * Note that no safety measure is taken if the provided arguments aren't integers, so + * you may end up with unexpected (not really) results. + * For example randomInt(0.1, 1.2) could be 2. * @example - * var minAndMax = _.collect([Math.min, Math.max]); * - * minAndMax(3, 1, -2, 5, 4, -1) // => [-2, 5] + * _.randomInt(1, 10) // => an integer >=1 && <= 10 * * @memberof module:lamb - * @category Function - * @since 0.35.0 - * @param {Function[]} functions - * @returns {Function} + * @category Math + * @since 0.1.0 + * @param {Number} min + * @param {Number} max + * @returns {Number} */ - function collect (functions) { - if (!Array.isArray(functions)) { - throw _makeTypeErrorFor(functions, "array"); - } + function randomInt (min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); + } - return function () { - return map(functions, applyTo(arguments)); - }; + /** + * Converts a value to a number and returns it if it's not NaN, otherwise + * returns zero. + * @private + * @param {*} value + * @returns {Number} + */ + function _forceToNumber (value) { + var n = +value; + + return n === n ? n : 0; // eslint-disable-line no-self-compare } /** - * Transforms the evaluation of the given function in the evaluation of a sequence of functions - * expecting only one argument. Each function of the sequence is a partial application of the - * original one, which will be applied when the specified (or derived) arity is consumed.
- * Currying will start from the leftmost argument: use {@link module:lamb.curryRight|curryRight} - * for right currying. + * Generates an arithmetic progression of numbers starting from start up to, + * but not including, limit, using the given step. * @example - * var makeWithKeys = _.curry(_.make); - * var makePerson = makeWithKeys(["name", "surname"]); + * _.range(2, 10) // => [2, 3, 4, 5, 6, 7, 8, 9] + * _.range(1, -10, -2) // => [1, -1, -3, -5, -7, -9] + * _.range(0, 3, 1) // => [0, 1, 2] + * _.range(-0, 3, 1) // => [-0, 1, 2] + * _.range(1, -10, 2) // => [] + * _.range(3, 5, -1) // => [] * - * makePerson(["John", "Doe"]) // => {name: "John", surname: "Doe"}; - * makePerson(["Mario", "Rossi"]) // => {name: "Mario", surname: "Rossi"}; + * @example Behaviour if step happens to be zero: + * _.range(2, 10, 0) // => [2] + * _.range(2, -10, 0) // => [2] + * _.range(2, 2, 0) // => [] * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} + * @category Math + * @see {@link module:lamb.generate|generate} * @since 0.1.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} + * @param {Number} start + * @param {Number} limit + * @param {Number} [step=1] + * @returns {Number[]} */ - function curry (fn, arity) { - return _curry(fn, arity, false); + function range (start, limit, step) { + start = _forceToNumber(start); + limit = _forceToNumber(limit); + step = arguments.length === 3 ? _forceToNumber(step) : 1; + + if (step === 0) { + return limit === start ? [] : [start]; + } + + var len = Math.max(Math.ceil((limit - start) / step), 0); + var result = Array(len); + + for (var i = 0, last = start; i < len; i++) { + result[i] = last; + last += step; + } + + return result; } /** - * Builds an auto-curried function. The resulting function can be called multiple times with - * any number of arguments, and the original function will be applied only when the specified - * (or derived) arity is consumed.
- * Currying will start from the leftmost argument: use {@link module:lamb.curryableRight|curryableRight} - * for right currying. + * Gets the remainder of the division of two numbers. + * Not to be confused with the {@link module:lamb.modulo|modulo} as the remainder + * keeps the sign of the dividend and may lead to some unexpected results. * @example - * var collectFourElements = _.curryable(_.list, 4); - * - * collectFourElements(2)(3)(4)(5) // => [2, 3, 4, 5] - * collectFourElements(2)(3, 4)(5) // => [2, 3, 4, 5] - * collectFourElements(2, 3, 4, 5) // => [2, 3, 4, 5] - * collectFourElements(2, 3)(4, 5) // => [2, 3, 4, 5] + * // example of wrong usage of the remainder + * // (in this case the modulo operation should be used) + * var isOdd = function (n) { return _.remainder(n, 2) === 1; }; + * isOdd(-3) // => false as -3 % 2 === -1 * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.6.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} + * @category Math + * @see {@link module:lamb.modulo|modulo} + * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} */ - function curryable (fn, arity) { - return _curry(fn, arity, false, true); + function remainder (a, b) { + return a % b; + } + + /** + * Checks whether the specified key is a own enumerable property of the given object or not. + * @private + * @function + * @param {Object} obj + * @param {String} key + * @returns {Boolean} + */ + var _isOwnEnumerable = generic(Object.prototype.propertyIsEnumerable); + + /** + * Builds a list of the enumerable properties of an object. + * The function is null-safe, unlike the public one. + * @private + * @param {Object} obj + * @returns {String[]} + */ + function _safeEnumerables (obj) { + var result = []; + + for (var key in obj) { + result.push(key); + } + + return result; + } + + /** + * Checks whether the specified key is an enumerable property of the given object or not. + * @private + * @param {Object} obj + * @param {String} key + * @returns {Boolean} + */ + function _isEnumerable (obj, key) { + return key in Object(obj) && (_isOwnEnumerable(obj, key) || ~_safeEnumerables(obj).indexOf(key)); + } + + /** + * Helper to retrieve the correct key while evaluating a path. + * @private + * @param {Object} target + * @param {String} key + * @param {Boolean} includeNonEnumerables + * @returns {String|Number|Undefined} + */ + function _getPathKey (target, key, includeNonEnumerables) { + if (includeNonEnumerables && key in Object(target) || _isEnumerable(target, key)) { + return key; + } + + var n = +key; + var len = target && target.length; + + return n >= -len && n < len ? n < 0 ? n + len : n : void 0; + } + + /** + * Checks if a path is valid in the given object and retrieves the path target. + * @private + * @param {Object} obj + * @param {String[]} parts + * @param {Boolean} walkNonEnumerables + * @returns {Object} + */ + function _getPathInfo (obj, parts, walkNonEnumerables) { + if (isNil(obj)) { + throw _makeTypeErrorFor(obj, "object"); + } + + var target = obj; + var i = -1; + var len = parts.length; + var key; + + while (++i < len) { + key = _getPathKey(target, parts[i], walkNonEnumerables); + + if (isUndefined(key)) { + break; + } + + target = target[key]; + } + + return i === len ? { isValid: true, target: target } : { isValid: false, target: void 0 }; } /** - * Same as {@link module:lamb.curryable|curryable}, but currying starts from the rightmost argument. - * @example - * var collectFourElements = _.curryableRight(_.list, 4); - * - * collectFourElements(2)(3)(4)(5) // => [5, 4, 3, 2] - * collectFourElements(2)(3, 4)(5) // => [5, 4, 3, 2] - * collectFourElements(2, 3, 4, 5) // => [5, 4, 3, 2] - * collectFourElements(2, 3)(4, 5) // => [5, 4, 3, 2] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curryable|curryable} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.9.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} + * Splits a sting path using the provided separator and returns an array + * of path parts. + * @private + * @param {String} path + * @param {String} separator + * @returns {String[]} */ - function curryableRight (fn, arity) { - return _curry(fn, arity, true, true); + function _toPathParts (path, separator) { + return String(path).split(separator || "."); } /** - * Same as {@link module:lamb.curry|curry}, but currying starts from the rightmost argument. + * Gets a nested property value from an object using the given path.
+ * The path is a string with property names separated by dots by default, but + * it can be customised with the optional third parameter.
+ * You can use integers in the path, even negative ones, to refer to array-like + * object indexes, but the priority will be given to existing object keys: + * the last example explains this particular case. * @example - * var makeWithValues = _.curryRight(_.make); - * var makeJohnDoe = makeWithValues(["John", "Doe"]); + * var user = { + * name: "John", + * surname: "Doe", + * login: { + * "user.name": "jdoe", + * password: "abc123" + * }, + * scores: [ + * {id: 1, value: 10}, + * {id: 2, value: 20}, + * {id: 3, value: 30} + * ] + * }; * - * makeJohnDoe(["name", "surname"]) // => {name: "John", surname: "Doe"}; - * makeJohnDoe(["firstName", "lastName"]) // => {firstName: "John", lastName: "Doe"}; + * _.getPathIn(user, "name") // => "John" + * _.getPathIn(user, "login.password") // => "abc123"; + * _.getPathIn(user, "login/user.name", "/") // => "jdoe" + * _.getPathIn(user, "name.foo") // => undefined + * _.getPathIn(user, "name.foo.bar") // => undefined + * + * @example Accessing array-like objects indexes: + * _.getPathIn(user, "login.password.1") // => "b" + * _.getPathIn(user, "scores.0") // => {id: 1, value: 10} + * _.getPathIn(user, "scores.-1.value") // => 30 + * + * @example Priority will be given to existing object keys over indexes: + * _.getPathIn(user, "scores.-1") // => {id: 3, value: 30} + * + * // let's do something funny + * user.scores["-1"] = "foo bar"; + * + * _.getPathIn(user, "scores.-1") // => "foo bar"; * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curry|curry} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.9.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} + * @category Object + * @see {@link module:lamb.getPath|getPath} + * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} + * @since 0.19.0 + * @param {Object|ArrayLike} obj + * @param {String} path + * @param {String} [separator="."] + * @returns {*} */ - function curryRight (fn, arity) { - return _curry(fn, arity, true); + function getPathIn (obj, path, separator) { + return _getPathInfo(obj, _toPathParts(path, separator), true).target; } /** - * Returns a function that will execute the given function only if it stops being called for the - * specified timespan.
- * See also {@link module:lamb.throttle|throttle} for a different behaviour where the first call - * happens immediately. - * @example A common use case of debounce in a browser environment: - * var updateLayout = function () { - * // some heavy DOM operations here + * Builds a checker function meant to be used with + * {@link module:lamb.validate|validate}.
+ * Note that the function accepts multiple keyPaths as a means to + * compare their values. In other words all the received keyPaths will be + * passed as arguments to the predicate to run the test.
+ * If you want to run the same single property check with multiple properties, you should build + * multiple checkers and combine them with {@link module:lamb.validate|validate}. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * login: { + * username: "jdoe", + * password: "abc123", + * passwordConfirm: "abc123" + * } * }; + * var pwdMatch = _.checker( + * _.areSame, + * "Passwords don't match", + * ["login.password", "login.passwordConfirm"] + * ); * - * window.addEventListener("resize", _.debounce(updateLayout, 200), false); + * pwdMatch(user) // => [] * - * // The resize event is fired repeteadly until the user stops resizing the - * // window, while the `updateLayout` function is called only once: 200 ms - * // after he stopped. + * var newUser = _.setPathIn(user, "login.passwordConfirm", "avc123"); + * + * pwdMatch(newUser) // => ["Passwords don't match", ["login.password", "login.passwordConfirm"]] * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.throttle|throttle} + * @category Object + * @see {@link module:lamb.validate|validate}, {@link module:lamb.validateWith|validateWith} * @since 0.1.0 - * @param {Function} fn - * @param {Number} timespan - Expressed in milliseconds - * @returns {Function} + * @param {Function} predicate - The predicate to test the object properties + * @param {String} message - The error message + * @param {String[]} keyPaths - The array of keys, or {@link module:lamb.getPathIn|paths}, to test. + * @param {String} [pathSeparator="."] + * @returns {Function} A checker function which returns an error in the form + * ["message", ["propertyA", "propertyB"]] or an empty array. */ - function debounce (fn, timespan) { - var timeoutID; - - return function () { - var args = arguments; - var debounced = function () { - timeoutID = null; - fn.apply(this, args); - }.bind(this); + function checker (predicate, message, keyPaths, pathSeparator) { + return function (obj) { + var getValues = partial(getPathIn, [obj, __, pathSeparator]); - clearTimeout(timeoutID); - timeoutID = setTimeout(debounced, timespan); + return predicate.apply(obj, map(keyPaths, getValues)) ? [] : [message, keyPaths]; }; } /** - * Returns a function that applies the original function with the arguments in reverse order. - * @example - * _.list(1, 2, 3) // => [1, 2, 3] - * _.flip(_.list)(1, 2, 3) // => [3, 2, 1] - * - * @memberof module:lamb - * @category Function - * @since 0.1.0 - * @param {Function} fn + * Creates a non-null-safe version of the provided "getKeys" function. + * @private + * @function + * @param {Function} getKeys * @returns {Function} */ - function flip (fn) { - return function () { - var args = list.apply(null, arguments).reverse(); + var _unsafeKeyListFrom = _curry2(function (getKeys, obj) { + if (isNil(obj)) { + throw _makeTypeErrorFor(obj, "object"); + } - return fn.apply(this, args); - }; - } + return getKeys(obj); + }); /** - * Builds a function that returns the argument received at the given index.
- * As with {@link module:lamb.getAt|getAt} negative indexes are allowed.
- * The resulting function will return undefined if no arguments are - * passed or if the index is out of bounds. - * @example - * var getFirstArg = _.getArgAt(0); - * var getLastArg = _.getArgAt(-1); - * - * getFirstArg(1, 2, 3) // => 1 - * getLastArg(1, 2, 3) // => 3 + * Creates an array with all the enumerable properties of the given object. + * @example Showing the difference with {@link module:lamb.keys|keys}: + * var baseFoo = Object.create({a: 1}, {b: {value: 2}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3}, + * d: {value: 4, enumerable: true} + * }); * - * _.getArgAt()(1, 2, 3) // => undefined - * _.getArgAt(6)(1, 2, 3) // => undefined - * _.getArgAt(1)() // => undefined + * _.keys(foo) // => ["d"] + * _.enumerables(foo) // => ["d", "a"] * * @memberof module:lamb - * @category Function - * @since 0.17.0 - * @param {Number} idx - * @returns {Function} + * @category Object + * @function + * @see {@link module:lamb.keys|keys} + * @since 0.12.0 + * @param {Object} obj + * @returns {String[]} */ - function getArgAt (idx) { - return function () { - return arguments[_toNaturalIndex(idx, arguments.length)]; - }; - } + var enumerables = _unsafeKeyListFrom(_safeEnumerables); /** - * Builds a function that will invoke the given method name on any received object and - * return the result. If no method with such name is found the function will return - * undefined.
- * Along with the method name it's possible to supply some arguments that will be bound to the - * method call. Further arguments can also be passed when the function is actually called, and - * they will be concatenated to the bound ones.
- * Returning undefined is a behaviour meant to quickly create a case for - * {@link module:lamb.adapter|adapter} without the need to check for the existence of the - * desired method.
- * See also {@link module:lamb.generic|generic} to create functions out of object methods. - * @example Basic polymorphism with invoker: - * var polySlice = _.invoker("slice"); - * - * polySlice([1, 2, 3, 4, 5], 1, 3) // => [2, 3] - * polySlice("Hello world", 1, 3) // => "el" - * - * @example With bound arguments: - * var substrFrom2 = _.invoker("substr", 2); - * substrFrom2("Hello world") // => "llo world" - * substrFrom2("Hello world", 5) // => "llo w" + * Builds an object from a list of key / value pairs like the one + * returned by {@link module:lamb.pairs|pairs} or {@link module:lamb.ownPairs|ownPairs}.
+ * In case of duplicate keys the last key / value pair is used. + * @example + * _.fromPairs([["a", 1], ["b", 2], ["c", 3]]) // => {"a": 1, "b": 2, "c": 3} + * _.fromPairs([["a", 1], ["b", 2], ["a", 3]]) // => {"a": 3, "b": 2} + * _.fromPairs([[1], [void 0, 2], [null, 3]]) // => {"1": undefined, "undefined": 2, "null": 3} * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.invokerOn|invokerOn} - * @since 0.1.0 - * @param {String} methodName - * @param {...*} [boundArg] - * @returns {Function} + * @category Object + * @see {@link module:lamb.ownPairs|ownPairs}, {@link module:lamb.pairs|pairs} + * @since 0.8.0 + * @param {Array>} pairsList + * @returns {Object} */ - function invoker (methodName) { - return partial(_invoker, [_argsTail.apply(null, arguments), methodName]); + function fromPairs (pairsList) { + var result = {}; + + forEach(pairsList, function (pair) { + result[pair[0]] = pair[1]; + }); + + return result; } /** - * Accepts an object and builds a function expecting a method name, and optionally arguments, - * to call on such object. - * Like {@link module:lamb.invoker|invoker}, if no method with the given name is found the - * function will return undefined. + * Builds a partial application of {@link module:lamb.getPathIn|getPathIn} with the given + * path and separator, expecting the object to act upon.
* @example - * var isEven = function (n) { return n % 2 === 0; }; - * var arr = [1, 2, 3, 4, 5]; - * var invokerOnArr = _.invokerOn(arr); + * var user = { + * name: "John", + * surname: "Doe", + * login: { + * "user.name": "jdoe", + * password: "abc123" + * } + * }; * - * invokerOnArr("filter", isEven) // => [2, 4] - * invokerOnArr("slice", 1, 3) // => [2, 3] + * var getPwd = _.getPath("login.password"); + * var getUsername = _.getPath("login/user.name", "/"); + * + * getPwd(user) // => "abc123"; + * getUsername(user) // => "jdoe" * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.invoker|invoker} - * @since 0.15.0 - * @param {Object} target + * @category Object + * @function + * @see {@link module:lamb.getPathIn|getPathIn} + * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} + * @since 0.19.0 + * @param {String} path + * @param {String} [separator="."] * @returns {Function} */ - function invokerOn (target) { - return partial(_invoker, [[], _, target]); - } + var getPath = _makePartial3(getPathIn); /** - * Builds a function that allows to map over the received arguments before applying them - * to the original one. + * Verifies the existence of a property in an object. * @example - * var sumArray = _.reduceWith(_.sum); - * var sumArgs = _.compose(sumArray, _.list); + * var user1 = {name: "john"}; * - * sumArgs(1, 2, 3, 4, 5) // => 15 + * _.has(user1, "name") // => true + * _.has(user1, "surname") // => false + * _.has(user1, "toString") // => true * - * var square = _.partial(Math.pow, [_, 2]); - * var sumSquares = _.mapArgs(sumArgs, square); + * var user2 = Object.create(null); * - * sumSquares(1, 2, 3, 4, 5) // => 55 + * // not inherited through the prototype chain + * _.has(user2, "toString") // => false * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.tapArgs|tapArgs} - * @since 0.3.0 - * @param {Function} fn - * @param {ListIteratorCallback} mapper - * @returns {Function} + * @category Object + * @see {@link module:lamb.hasKey|hasKey} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @since 0.1.0 + * @param {Object} obj + * @param {String} key + * @returns {Boolean} */ - function mapArgs (fn, mapper) { - return pipe([list, mapWith(mapper), apply(fn)]); + function has (obj, key) { + if (typeof obj !== "object" && !isUndefined(obj)) { + obj = Object(obj); + } + + return key in obj; } /** - * Creates a pipeline of functions, where each function consumes the result of the previous one. + * Curried version of {@link module:lamb.has|has}.
+ * Returns a function expecting the object to check against the given key. * @example - * var square = _.partial(Math.pow, [_, 2]); - * var getMaxAndSquare = _.pipe([Math.max, square]); + * var user1 = {name: "john"}; + * var user2 = {}; + * var hasName = _.hasKey("name"); * - * getMaxAndSquare(3, 5) // => 25 + * hasName(user1) // => true + * hasName(user2) // => false * * @memberof module:lamb - * @category Function + * @category Object * @function - * @see {@link module:lamb.compose|compose} + * @see {@link module:lamb.has|has} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} * @since 0.1.0 - * @param {Function[]} functions + * @param {String} key * @returns {Function} */ - function pipe (functions) { - if (!Array.isArray(functions)) { - throw _makeTypeErrorFor(functions, "array"); - } - - var len = functions.length; - - return len ? function () { - var result = functions[0].apply(this, arguments); - - for (var i = 1; i < len; i++) { - result = functions[i].call(this, result); - } + var hasKey = _curry2(has, true); - return result; - } : identity; - } + /** + * Verifies if an object has the specified property and that the property isn't inherited through + * the prototype chain.
+ * @example Comparison with has: + * var user = {name: "john"}; + * + * _.has(user, "name") // => true + * _.has(user, "surname") // => false + * _.has(user, "toString") // => true + * + * _.hasOwn(user, "name") // => true + * _.hasOwn(user, "surname") // => false + * _.hasOwn(user, "toString") // => false + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @since 0.1.0 + * @param {Object} obj + * @param {String} key + * @returns {Boolean} + */ + var hasOwn = generic(Object.prototype.hasOwnProperty); /** - * Builds a function that allows to "tap" into the arguments of the original one. - * This allows to extract simple values from complex ones, transform arguments or simply intercept them. - * If a "tapper" isn't found the argument is passed as it is. + * Curried version of {@link module:lamb.hasOwn|hasOwn}.
+ * Returns a function expecting the object to check against the given key. * @example - * var someObject = {count: 5}; - * var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0]; - * var getDataAmount = _.tapArgs(_.sum, [_.getKey("count"), _.getKey("length")]); + * var user = {name: "john"}; + * var hasOwnName = _.hasOwnKey("name"); + * var hasOwnToString = _.hasOwnToString("toString"); * - * getDataAmount(someObject, someArrayData); // => 15 + * hasOwnName(user) // => true + * hasOwnToString(user) // => false * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.mapArgs|mapArgs} - * @since 0.3.0 - * @param {Function} fn - * @param {Function[]} tappers + * @category Object + * @function + * @see {@link module:lamb.hasOwn|hasOwn} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @since 0.1.0 + * @param {String} key * @returns {Function} */ - function tapArgs (fn, tappers) { - return function () { - var len = arguments.length; - var tappersLen = tappers.length; - var args = []; - - for (var i = 0; i < len; i++) { - args.push(i < tappersLen ? tappers[i](arguments[i]) : arguments[i]); - } - - return fn.apply(this, args); - }; - } + var hasOwnKey = _curry2(hasOwn, true); /** - * Returns a function that will invoke the passed function at most once in the given timespan.
- * The first call in this case happens as soon as the function is invoked; see also - * {@link module:lamb.debounce|debounce} for a different behaviour where the first call is delayed. + * Builds a predicate expecting an object to check against the given key / value pair.
+ * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. * @example - * var log = _.throttle(console.log.bind(console), 5000); + * var hasTheCorrectAnswer = _.hasKeyValue("answer", 42); * - * log("Hi"); // console logs "Hi" - * log("Hi again"); // nothing happens - * // after five seconds - * log("Hello world"); // console logs "Hello world" + * hasTheCorrectAnswer({answer: 2}) // false + * hasTheCorrectAnswer({answer: 42}) // true * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.debounce|debounce} + * @category Object + * @see {@link module:lamb.hasPathValue|hasPathValue} * @since 0.1.0 - * @param {Function} fn - * @param {Number} timespan - Expressed in milliseconds. + * @param {String} key + * @param {*} value * @returns {Function} */ - function throttle (fn, timespan) { - var result; - var lastCall = 0; - - return function () { - var now = Date.now(); - - if (now - lastCall >= timespan) { - lastCall = now; - result = fn.apply(this, arguments); - } - - return result; + function hasKeyValue (key, value) { + return function (obj) { + return isUndefined(value) ? has(obj, key) && obj[key] === value : areSVZ(value, obj[key]); }; } /** - * Builds a function that passes only one argument to the given function.
- * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, - * exposed for convenience. + * Builds a predicate to check if the given path exists in an object and holds the desired value.
+ * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
+ * Note that the function will check even non-enumerable properties. * @example - * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; + * var user = { + * name: "John", + * surname: "Doe", + * personal: { + * age: 25, + * gender: "M" + * }, + * scores: [ + * {id: 1, value: 10, passed: false}, + * {id: 2, value: 20, passed: false}, + * {id: 3, value: 30, passed: true} + * ] + * }; * - * _.map(weights, _.unary(parseInt)) // => [2, 10, 1, 7] + * var isMale = _.hasPathValue("personal.gender", "M"); + * var hasPassedFirstTest = _.hasPathValue("scores.0.passed", true); + * var hasPassedLastTest = _.hasPathValue("scores.-1.passed", true); + * + * isMale(user) // => true + * hasPassedFirstTest(user) // => false + * hasPassedLastTest(user) // => true * * @memberof module:lamb - * @category Function - * @see {@link module:lamb.aritize|aritize} - * @see {@link module:lamb.binary|binary} - * @since 0.10.0 - * @param {Function} fn + * @category Object + * @see {@link module:lamb.hasKeyValue|hasKeyValue} + * @since 0.41.0 + * @param {String} path + * @param {*} value + * @param {String} [separator="."] * @returns {Function} */ - function unary (fn) { - return function (a) { - return fn.call(this, a); + function hasPathValue (path, value, separator) { + return function (obj) { + var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); + + return pathInfo.isValid && areSVZ(pathInfo.target, value); }; } - lamb.application = application; - lamb.apply = apply; - lamb.applyTo = applyTo; - lamb.aritize = aritize; - lamb.asPartial = asPartial; - lamb.binary = binary; - lamb.collect = collect; - lamb.curry = curry; - lamb.curryRight = curryRight; - lamb.curryable = curryable; - lamb.curryableRight = curryableRight; - lamb.debounce = debounce; - lamb.flip = flip; - lamb.getArgAt = getArgAt; - lamb.invoker = invoker; - lamb.invokerOn = invokerOn; - lamb.mapArgs = mapArgs; - lamb.pipe = pipe; - lamb.tapArgs = tapArgs; - lamb.throttle = throttle; - lamb.unary = unary; - /** - * Creates an array with all the enumerable properties of the given object. - * @example Showing the difference with {@link module:lamb.keys|keys}: - * var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3}, - * d: {value: 4, enumerable: true} - * }); - * - * _.keys(foo) // => ["d"] - * _.enumerables(foo) // => ["d", "a"] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.keys|keys} - * @since 0.12.0 + * Makes an object immutable by recursively calling Object.freeze + * on its members. + * @private * @param {Object} obj - * @returns {String[]} + * @param {Array} seen + * @returns {Object} The obj parameter itself, not a copy. */ - var enumerables = _unsafeKeyListFrom(_safeEnumerables); + function _immutable (obj, seen) { + if (seen.indexOf(obj) === -1) { + seen.push(Object.freeze(obj)); - /** - * Builds an object from a list of key / value pairs like the one - * returned by {@link module:lamb.pairs|pairs} or {@link module:lamb.ownPairs|ownPairs}.
- * In case of duplicate keys the last key / value pair is used. - * @example - * _.fromPairs([["a", 1], ["b", 2], ["c", 3]]) // => {"a": 1, "b": 2, "c": 3} - * _.fromPairs([["a", 1], ["b", 2], ["a", 3]]) // => {"a": 3, "b": 2} - * _.fromPairs([[1], [void 0, 2], [null, 3]]) // => {"1": undefined, "undefined": 2, "null": 3} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.ownPairs|ownPairs}, {@link module:lamb.pairs|pairs} - * @since 0.8.0 - * @param {Array>} pairsList - * @returns {Object} - */ - function fromPairs (pairsList) { - var result = {}; + forEach(Object.getOwnPropertyNames(obj), function (key) { + var value = obj[key]; - forEach(pairsList, function (pair) { - result[pair[0]] = pair[1]; - }); + if (typeof value === "object" && !isNull(value)) { + _immutable(value, seen); + } + }); + } - return result; + return obj; } /** @@ -5801,6 +5276,15 @@ return _immutable(obj, []); } + /** + * A null-safe version of Object.keys. + * @private + * @function + * @param {Object} obj + * @returns {String[]} + */ + var _safeKeys = compose(Object.keys, Object); + /** * Retrieves the list of the own enumerable properties of an object.
* Although [Object.keys]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys} @@ -5828,6 +5312,33 @@ */ var keys = _unsafeKeyListFrom(_safeKeys); + /** + * Builds a predicate to check if the given key satisfies the desired condition + * on an object. + * @example + * var users = [ + * {name: "John", age: 25}, + * {name: "Jane", age: 15}, + * ]; + * var isAdult = _.keySatisfies(_.isGTE(18), "age"); + * + * isAdult(users[0]) // => true + * isAdult(users[1]) // => false + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.pathSatisfies|pathSatisfies} + * @since 0.45.0 + * @param {Function} predicate + * @param {String} key + * @returns {Function} + */ + function keySatisfies (predicate, key) { + return function (obj) { + return predicate.call(this, obj[key]); + }; + } + /** * Builds an object from the two given lists, using the first one as keys and the last * one as values.
@@ -5916,6 +5427,24 @@ */ var mapValuesWith = _curry2(mapValues, true); + /** + * Merges the received objects using the provided function to retrieve their keys. + * @private + * @param {Function} getKeys + * @param {Object} a + * @param {Object} b + * @returns {Function} + */ + function _merge (getKeys, a, b) { + return reduce([a, b], function (result, source) { + forEach(getKeys(source), function (key) { + result[key] = source[key]; + }); + + return result; + }, {}); + } + /** * Merges the enumerable properties of the provided sources into a new object.
* In case of key homonymy the last source has precedence over the first. @@ -5974,6 +5503,30 @@ */ var mergeOwn = partial(_merge, [keys]); + /** + * Accepts an object and build a function expecting a key to create a "pair" with the key + * and its value. + * @private + * @function + * @param {Object} obj + * @returns {Function} + */ + var _keyToPairIn = _curry2(function (obj, key) { + return [key, obj[key]]; + }); + + /** + * Using the provided function to retrieve the keys, builds a new function + * expecting an object to create a list of key / value pairs. + * @private + * @function + * @param {Function} getKeys + * @returns {Function} + */ + var _pairsFrom = _curry2(function (getKeys, obj) { + return map(getKeys(obj), _keyToPairIn(obj)); + }); + /** * Same as {@link module:lamb.pairs|pairs}, but only the own enumerable properties of the object are * taken into account.
@@ -5998,6 +5551,20 @@ */ var ownPairs = _pairsFrom(keys); + /** + * Using the provided function to retrieve the keys of an object, builds + * a function expecting an object to create the list of values for such keys. + * @private + * @function + * @param {Function} getKeys + * @returns {Function} + */ + var _valuesFrom = _curry2(function (getKeys, obj) { + return map(getKeys(obj), function (key) { + return obj[key]; + }); + }); + /** * Same as {@link module:lamb.values|values}, but only the own enumerable properties of the object are * taken into account.
@@ -6018,25 +5585,129 @@ * @param {Object} obj * @returns {Array} */ - var ownValues = _valuesFrom(keys); + var ownValues = _valuesFrom(keys); + + /** + * Converts an object into an array of key / value pairs of its enumerable properties.
+ * See also {@link module:lamb.ownPairs|ownPairs} for picking only the own enumerable + * properties and {@link module:lamb.fromPairs|fromPairs} for the reverse operation. + * @example + * _.pairs({a: 1, b: 2, c: 3}) // => [["a", 1], ["b", 2], ["c", 3]] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.ownPairs|ownPairs} + * @see {@link module:lamb.fromPairs|fromPairs} + * @since 0.8.0 + * @param {Object} obj + * @returns {Array>} + */ + var pairs = _pairsFrom(enumerables); + + /** + * Checks if the provided path exists in the given object.
+ * Note that the function will check even non-enumerable properties. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * address: { + * city: "New York" + * }, + * scores: [10, 20, 15] + * }; + * + * _.pathExistsIn(user, "address.city") // => true + * _.pathExistsIn(user, "address.country") // => false + * _.pathExistsIn(user, "scores.1") // => true + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.pathExists|pathExists} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @since 0.43.0 + * @param {Object} obj + * @param {String} path + * @param {String} [separator="."] + * @returns {Boolean} + */ + function pathExistsIn (obj, path, separator) { + return _getPathInfo(obj, _toPathParts(path, separator), true).isValid; + } + + /** + * Builds a partial application of {@link module:lamb.pathExistsIn|pathExistsIn} using the given + * path and the optional separator. The resulting function expects the object to check.
+ * Note that the function will check even non-enumerable properties. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * address: { + * city: "New York" + * }, + * scores: [10, 20, 15] + * }; + * + * var hasCity = _.pathExists("address.city"); + * var hasCountry = _.pathExists("address.country"); + * var hasAtLeastThreeScores = _.pathExists("scores.2"); + * + * hasCity(user) // => true + * hasCountry(user) // => false + * hasAtLeastThreeScores(user) // => true + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.pathExistsIn|pathExistsIn} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @since 0.43.0 + * @param {String} path + * @param {String} [separator="."] + * @returns {Function} + */ + var pathExists = _makePartial3(pathExistsIn); /** - * Converts an object into an array of key / value pairs of its enumerable properties.
- * See also {@link module:lamb.ownPairs|ownPairs} for picking only the own enumerable - * properties and {@link module:lamb.fromPairs|fromPairs} for the reverse operation. + * Builds a predicate that verifies if a condition is satisfied for the given + * path in an object.
+ * Like the other "path functions" you can use integers in the path, even + * negative ones, to refer to array-like object indexes, but the priority will + * be given to existing object keys. * @example - * _.pairs({a: 1, b: 2, c: 3}) // => [["a", 1], ["b", 2], ["c", 3]] + * var user = { + * name: "John", + * performance: { + * scores: [1, 5, 10] + * } + * }; + * + * var gotAnHighScore = _.pathSatisfies(_.contains(10), "performance.scores"); + * var hadAGoodStart = _.pathSatisfies(_.isGT(6), "performance.scores.0"); + * + * gotAnHighScore(user) // => true + * hadAGoodStart(user) // => false * * @memberof module:lamb * @category Object - * @function - * @see {@link module:lamb.ownPairs|ownPairs} - * @see {@link module:lamb.fromPairs|fromPairs} - * @since 0.8.0 - * @param {Object} obj - * @returns {Array>} + * @see {@link module:lamb.keySatisfies|keySatisfies} + * @since 0.45.0 + * @param {Function} predicate + * @param {String} path + * @param {String} [separator="."] + * @returns {Function} */ - var pairs = _pairsFrom(enumerables); + function pathSatisfies (predicate, path, separator) { + return function (obj) { + var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); + + return predicate.call(this, pathInfo.target); + }; + } /** * Returns an object containing only the specified properties of the given object.
@@ -6213,39 +5884,252 @@ * * @memberof module:lamb * @category Object - * @function - * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameWith|renameWith} - * @since 0.26.0 - * @param {Object} keysMap - * @returns {Function} + * @function + * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameWith|renameWith} + * @since 0.26.0 + * @param {Object} keysMap + * @returns {Function} + */ + var renameKeys = _curry2(rename, true); + + /** + * Uses the provided function as a keysMap generator and returns + * a function expecting the object whose keys we want to {@link module:lamb.rename|rename}. + * @example + * var person = {"NAME": "John", "SURNAME": "Doe"}; + * var arrayToLower = _.mapWith(_.invoker("toLowerCase")); + * var makeLowerKeysMap = function (source) { + * var sourceKeys = _.keys(source); + * + * return _.make(sourceKeys, arrayToLower(sourceKeys)); + * }; + * var lowerKeysFor = _.renameWith(makeLowerKeysMap); + * + * lowerKeysFor(person) // => {"name": "John", "surname": "doe"}; + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameKeys|renameKeys} + * @since 0.26.0 + * @param {Function} fn + * @returns {Function} + */ + function renameWith (fn) { + return function (source) { + return rename(source, fn(source)); + }; + } + + /** + * Sets, or creates, a property in a copy of the provided object to the desired value. + * @private + * @param {Object} source + * @param {String} key + * @param {*} value + * @returns {Object} + */ + function _setIn (source, key, value) { + var result = {}; + + for (var prop in source) { + result[prop] = source[prop]; + } + + result[key] = value; + + return result; + } + + /** + * Sets the specified key to the given value in a copy of the provided object.
+ * All the remaining enumerable keys of the source object will be simply copied in the + * result object without breaking references.
+ * If the specified key is not part of the source object, it will be added to the + * result.
+ * The main purpose of the function is to work on simple plain objects used as + * data structures, such as JSON objects, and makes no effort to play nice with + * objects created from an OOP perspective (it's not worth it).
+ * For example the prototype of the result will be Object's regardless + * of the source's one. + * @example + * var user = {name: "John", surname: "Doe", age: 30}; + * + * _.setIn(user, "name", "Jane") // => {name: "Jane", surname: "Doe", age: 30} + * _.setIn(user, "gender", "male") // => {name: "John", surname: "Doe", age: 30, gender: "male"} + * + * // `user` still is {name: "John", surname: "Doe", age: 30} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.setKey|setKey} + * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} + * @since 0.18.0 + * @param {Object} source + * @param {String} key + * @param {*} value + * @returns {Object} + */ + function setIn (source, key, value) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "object"); + } + + return _setIn(source, key, value); + } + + /** + * Builds a partial application of {@link module:lamb.setIn|setIn} with the provided + * key and value.
+ * The resulting function expects the object to act upon.
+ * Please refer to {@link module:lamb.setIn|setIn}'s description for explanations about + * how the copy of the source object is made. + * @example + * var user = {name: "John", surname: "Doe", age: 30}; + * var setAgeTo40 = _.setKey("age", 40); + * + * setAgeTo40(user) // => {name: "john", surname: "doe", age: 40} + * + * // `user` still is {name: "John", surname: "Doe", age: 30} + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.setIn|setIn} + * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} + * @since 0.18.0 + * @param {String} key + * @param {*} value + * @returns {Function} + */ + var setKey = _makePartial3(setIn); + + /** + * Accepts a target object and a key name and verifies that the target is an array and that + * the key is an existing index. + * @private + * @param {Object} target + * @param {String|Number} key + * @returns {Boolean} + */ + function _isArrayIndex (target, key) { + var n = +key; + + return Array.isArray(target) && n % 1 === 0 && !(n < 0 && _isEnumerable(target, key)); + } + + /** + * Sets the object's property targeted by the given path to the desired value.
+ * Works with arrays and is able to set their indexes, even negative ones. + * @private + * @param {Object|Array} obj + * @param {String[]} parts + * @param {*} value + * @returns {Object|Array} + */ + function _setPathIn (obj, parts, value) { + var key = parts[0]; + var partsLen = parts.length; + var v; + + if (partsLen === 1) { + v = value; + } else { + var targetKey = _getPathKey(obj, key, false); + + v = _setPathIn( + isUndefined(targetKey) ? targetKey : obj[targetKey], + slice(parts, 1, partsLen), + value + ); + } + + return _isArrayIndex(obj, key) ? _setIndex(obj, key, v) : _setIn(obj, key, v); + } + + /** + * Allows to change a nested value in a copy of the provided object.
+ * The function will delegate the "set action" to {@link module:lamb.setIn|setIn} or + * {@link module:lamb.setAt|setAt} depending on the value encountered in the path, + * so please refer to the documentation of those functions for specifics about the + * implementation.
+ * Note anyway that the distinction will be between Arrays, delegated + * to {@link module:lamb.setAt|setAt}, and everything else (including array-like objects), + * which will be delegated to {@link module:lamb.setIn|setIn}.
+ * As a result of that, array-like objects will be converted to objects having numbers as keys + * and paths targeting non-object values will be converted to empty objects.
+ * You can anyway target array elements using integers in the path, even negative ones, but + * the priority will be given to existing, and enumerable, object keys.
+ * Non-enumerable properties encountered in the path will be considered as non-existent properties.
+ * Like {@link module:lamb.getPathIn|getPathIn} or {@link module:lamb.getPath|getPath} you can + * use custom path separators. + * @example + * var user = {id: 1, status: {active : false, scores: [2, 4, 6]}}; + * + * _.setPathIn(user, "status.active", true) // => {id: 1, status: {active : true, scores: [2, 4, 6]}} + * + * @example Targeting arrays: + * _.setPathIn(user, "status.scores.0", 8) // => {id: 1, status: {active : false, scores: [8, 4, 6]}} + * + * // you can use negative indexes as well + * _.setPathIn(user, "status.scores.-1", 8) // => {id: 1, status: {active : false, scores: [2, 4, 8]}} + * + * @example Arrays can also be part of the path and not necessarily its target: + * var user = {id: 1, scores: [ + * {value: 2, year: "2000"}, + * {value: 4, year: "2001"}, + * {value: 6, year: "2002"} + * ]}; + * + * var newUser = _.setPathIn(user, "scores.0.value", 8); + * // "newUser" holds: + * // {id: 1, scores: [ + * // {value: 8, year: "2000"}, + * // {value: 4, year: "2001"}, + * // {value: 6, year: "2002"} + * // ]} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.setPath|setPath} + * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} + * @since 0.20.0 + * @param {Object|Array} source + * @param {String} path + * @param {*} value + * @param {String} [separator="."] + * @returns {Object|Array} */ - var renameKeys = _curry2(rename, true); + function setPathIn (source, path, value, separator) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "object"); + } + + return _setPathIn(source, _toPathParts(path, separator), value); + } /** - * Uses the provided function as a keysMap generator and returns - * a function expecting the object whose keys we want to {@link module:lamb.rename|rename}. + * Builds a partial application of {@link module:lamb.setPathIn|setPathIn} expecting the + * object to act upon.
+ * See {@link module:lamb.setPathIn|setPathIn} for more details and examples. * @example - * var person = {"NAME": "John", "SURNAME": "Doe"}; - * var arrayToLower = _.mapWith(_.invoker("toLowerCase")); - * var makeLowerKeysMap = function (source) { - * var sourceKeys = _.keys(source); - * - * return _.make(sourceKeys, arrayToLower(sourceKeys)); - * }; - * var lowerKeysFor = _.renameWith(makeLowerKeysMap); + * var user = {id: 1, status: {active: false}}; + * var activate = _.setPath("status.active", true); * - * lowerKeysFor(person) // => {"name": "John", "surname": "doe"}; + * activate(user) // => {id: 1, status: {active: true}} * * @memberof module:lamb * @category Object - * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameKeys|renameKeys} - * @since 0.26.0 - * @param {Function} fn + * @see {@link module:lamb.setPathIn|setPathIn} + * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} + * @since 0.20.0 + * @param {String} path + * @param {*} value + * @param {String} [separator="."] * @returns {Function} */ - function renameWith (fn) { + function setPath (path, value, separator) { return function (source) { - return rename(source, fn(source)); + return setPathIn(source, path, value, separator); }; } @@ -6284,501 +6168,270 @@ return result; } - /** - * Builds a function expecting an object whose enumerable properties will be checked - * against the given predicate.
- * The properties satisfying the predicate will be omitted in the resulting object. - * @example - * var user = {name: "john", surname: "doe", age: 30}; - * var skipIfIstring = _.skipIf(_.isType("String")); - * - * skipIfIstring(user) // => {age: 30} - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys} - * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, - * {@link module:lamb.pickIf|pickIf} - * @since 0.1.0 - * @param {ObjectIteratorCallback} predicate - * @returns {Function} - */ - var skipIf = compose(pickIf, not); - - /** - * A curried version of {@link module:lamb.skip|skip}, expecting a blacklist of keys to build - * a function waiting for the object to act upon. - * @example - * var user = {id: 1, name: "Jane", surname: "Doe", active: false}; - * var getUserInfo = _.skipKeys(["name", "surname"]); - * - * getUserInfo(user) // => {id: 1, active: false} - * - * @example A useful composition with mapWith: - * var users = [ - * {id: 1, name: "Jane", surname: "Doe", active: false}, - * {id: 2, name: "John", surname: "Doe", active: true}, - * {id: 3, name: "Mario", surname: "Rossi", active: true}, - * {id: 4, name: "Paolo", surname: "Bianchi", active: false} - * ]; - * var discard = _.compose(_.mapWith, _.skipKeys); - * var discardNames = discard(["name", "surname"]); - * - * discardNames(users) // => - * // [ - * // {id: 1, active: false}, - * // {id: 2, active: true}, - * // {id: 3, active: true}, - * // {id: 4, active: false} - * // ] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf} - * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, - * {@link module:lamb.pickIf|pickIf} - * @since 0.35.0 - * @param {String[]} blacklist - * @returns {Function} - */ - var skipKeys = _curry2(skip, true); - - /** - * Tears an object apart by transforming it in an array of two lists: one containing - * its enumerable keys, the other containing the corresponding values.
- * Although this "tearing apart" may sound as a rather violent process, the source - * object will be unharmed. - * @example - * _.tear({a: 1, b: 2, c: 3}) // => [["a", "b", "c"], [1, 2, 3]] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.tearOwn|tearOwn} - * @see {@link module:lamb.make|make} for the reverse operation - * @since 0.8.0 - * @param {Object} obj - * @returns {Array} - */ - var tear = _tearFrom(enumerables); - - /** - * Same as {@link module:lamb.tear|tear}, but only the own properties of the object are - * taken into account. - * @example Showing the difference with tear: - * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3, enumerable: true} - * }); - * - * _.tear(foo) // => [["c", "b", "a"], [3, 2, 1]] - * _.tearOwn(foo) // => [["c"], [3]] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.tear|tear} - * @see {@link module:lamb.make|make} for the reverse operation - * @since 0.12.0 - * @param {Object} obj - * @returns {Array} - */ - var tearOwn = _tearFrom(keys); - - /** - * Generates an array with the values of the enumerable properties of the given object.
- * See also {@link module:lamb.ownValues|ownValues} to pick only from the own properties of the object. - * @example - * var user = {name: "john", surname: "doe", age: 30}; - * - * _.values(user) // => ["john", "doe", 30] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.ownValues|ownValues} - * @since 0.1.0 - * @param {Object} obj - * @returns {Array} - */ - var values = _valuesFrom(enumerables); - - lamb.enumerables = enumerables; - lamb.fromPairs = fromPairs; - lamb.immutable = immutable; - lamb.keys = keys; - lamb.make = make; - lamb.mapValues = mapValues; - lamb.mapValuesWith = mapValuesWith; - lamb.merge = merge; - lamb.mergeOwn = mergeOwn; - lamb.ownPairs = ownPairs; - lamb.ownValues = ownValues; - lamb.pairs = pairs; - lamb.pick = pick; - lamb.pickIf = pickIf; - lamb.pickKeys = pickKeys; - lamb.rename = rename; - lamb.renameKeys = renameKeys; - lamb.renameWith = renameWith; - lamb.skip = skip; - lamb.skipIf = skipIf; - lamb.skipKeys = skipKeys; - lamb.tear = tear; - lamb.tearOwn = tearOwn; - lamb.values = values; - - /** - * Builds a checker function meant to be used with - * {@link module:lamb.validate|validate}.
- * Note that the function accepts multiple keyPaths as a means to - * compare their values. In other words all the received keyPaths will be - * passed as arguments to the predicate to run the test.
- * If you want to run the same single property check with multiple properties, you should build - * multiple checkers and combine them with {@link module:lamb.validate|validate}. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * login: { - * username: "jdoe", - * password: "abc123", - * passwordConfirm: "abc123" - * } - * }; - * var pwdMatch = _.checker( - * _.areSame, - * "Passwords don't match", - * ["login.password", "login.passwordConfirm"] - * ); - * - * pwdMatch(user) // => [] - * - * var newUser = _.setPathIn(user, "login.passwordConfirm", "avc123"); - * - * pwdMatch(newUser) // => ["Passwords don't match", ["login.password", "login.passwordConfirm"]] - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.validate|validate}, {@link module:lamb.validateWith|validateWith} - * @since 0.1.0 - * @param {Function} predicate - The predicate to test the object properties - * @param {String} message - The error message - * @param {String[]} keyPaths - The array of keys, or {@link module:lamb.getPathIn|paths}, to test. - * @param {String} [pathSeparator="."] - * @returns {Function} A checker function which returns an error in the form - * ["message", ["propertyA", "propertyB"]] or an empty array. - */ - function checker (predicate, message, keyPaths, pathSeparator) { - return function (obj) { - var getValues = partial(getPathIn, [obj, _, pathSeparator]); - - return predicate.apply(obj, map(keyPaths, getValues)) ? [] : [message, keyPaths]; - }; - } - - /** - * Verifies the existence of a property in an object. - * @example - * var user1 = {name: "john"}; - * - * _.has(user1, "name") // => true - * _.has(user1, "surname") // => false - * _.has(user1, "toString") // => true - * - * var user2 = Object.create(null); - * - * // not inherited through the prototype chain - * _.has(user2, "toString") // => false - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.hasKey|hasKey} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} - * @since 0.1.0 - * @param {Object} obj - * @param {String} key - * @returns {Boolean} - */ - function has (obj, key) { - if (typeof obj !== "object" && !isUndefined(obj)) { - obj = Object(obj); - } - - return key in obj; - } - - /** - * Curried version of {@link module:lamb.has|has}.
- * Returns a function expecting the object to check against the given key. + /** + * Builds a function expecting an object whose enumerable properties will be checked + * against the given predicate.
+ * The properties satisfying the predicate will be omitted in the resulting object. * @example - * var user1 = {name: "john"}; - * var user2 = {}; - * var hasName = _.hasKey("name"); + * var user = {name: "john", surname: "doe", age: 30}; + * var skipIfIstring = _.skipIf(_.isType("String")); * - * hasName(user1) // => true - * hasName(user2) // => false + * skipIfIstring(user) // => {age: 30} * * @memberof module:lamb * @category Object * @function - * @see {@link module:lamb.has|has} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys} + * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, + * {@link module:lamb.pickIf|pickIf} * @since 0.1.0 - * @param {String} key + * @param {ObjectIteratorCallback} predicate * @returns {Function} */ - var hasKey = _curry2(has, true); + var skipIf = compose(pickIf, not); /** - * Builds a predicate expecting an object to check against the given key / value pair.
- * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * A curried version of {@link module:lamb.skip|skip}, expecting a blacklist of keys to build + * a function waiting for the object to act upon. * @example - * var hasTheCorrectAnswer = _.hasKeyValue("answer", 42); + * var user = {id: 1, name: "Jane", surname: "Doe", active: false}; + * var getUserInfo = _.skipKeys(["name", "surname"]); * - * hasTheCorrectAnswer({answer: 2}) // false - * hasTheCorrectAnswer({answer: 42}) // true + * getUserInfo(user) // => {id: 1, active: false} + * + * @example A useful composition with mapWith: + * var users = [ + * {id: 1, name: "Jane", surname: "Doe", active: false}, + * {id: 2, name: "John", surname: "Doe", active: true}, + * {id: 3, name: "Mario", surname: "Rossi", active: true}, + * {id: 4, name: "Paolo", surname: "Bianchi", active: false} + * ]; + * var discard = _.compose(_.mapWith, _.skipKeys); + * var discardNames = discard(["name", "surname"]); + * + * discardNames(users) // => + * // [ + * // {id: 1, active: false}, + * // {id: 2, active: true}, + * // {id: 3, active: true}, + * // {id: 4, active: false} + * // ] * * @memberof module:lamb * @category Object - * @see {@link module:lamb.hasPathValue|hasPathValue} - * @since 0.1.0 - * @param {String} key - * @param {*} value + * @function + * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf} + * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, + * {@link module:lamb.pickIf|pickIf} + * @since 0.35.0 + * @param {String[]} blacklist * @returns {Function} */ - function hasKeyValue (key, value) { - return function (obj) { - return isUndefined(value) ? has(obj, key) && obj[key] === value : areSVZ(value, obj[key]); - }; - } + var skipKeys = _curry2(skip, true); /** - * Verifies if an object has the specified property and that the property isn't inherited through - * the prototype chain.
- * @example Comparison with has: - * var user = {name: "john"}; - * - * _.has(user, "name") // => true - * _.has(user, "surname") // => false - * _.has(user, "toString") // => true - * - * _.hasOwn(user, "name") // => true - * _.hasOwn(user, "surname") // => false - * _.hasOwn(user, "toString") // => false - * - * @memberof module:lamb - * @category Object + * Using the provided function to retrieve the keys of an object, builds + * a function expecting an object to create an array containing a list + * of the keys in its first index and the corresponding list of values + * in the second one. + * @private * @function - * @see {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} - * @since 0.1.0 - * @param {Object} obj - * @param {String} key - * @returns {Boolean} + * @param {Function} getKeys + * @returns {Function} */ - var hasOwn = generic(_objectProto.hasOwnProperty); + var _tearFrom = _curry2(function (getKeys, obj) { + return reduce(getKeys(obj), function (result, key) { + result[0].push(key); + result[1].push(obj[key]); + + return result; + }, [[], []]); + }); /** - * Curried version of {@link module:lamb.hasOwn|hasOwn}.
- * Returns a function expecting the object to check against the given key. + * Tears an object apart by transforming it in an array of two lists: one containing + * its enumerable keys, the other containing the corresponding values.
+ * Although this "tearing apart" may sound as a rather violent process, the source + * object will be unharmed. * @example - * var user = {name: "john"}; - * var hasOwnName = _.hasOwnKey("name"); - * var hasOwnToString = _.hasOwnToString("toString"); - * - * hasOwnName(user) // => true - * hasOwnToString(user) // => false + * _.tear({a: 1, b: 2, c: 3}) // => [["a", "b", "c"], [1, 2, 3]] * * @memberof module:lamb * @category Object * @function - * @see {@link module:lamb.hasOwn|hasOwn} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} - * @since 0.1.0 - * @param {String} key - * @returns {Function} + * @see {@link module:lamb.tearOwn|tearOwn} + * @see {@link module:lamb.make|make} for the reverse operation + * @since 0.8.0 + * @param {Object} obj + * @returns {Array} */ - var hasOwnKey = _curry2(hasOwn, true); + var tear = _tearFrom(enumerables); /** - * Builds a predicate to check if the given path exists in an object and holds the desired value.
- * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
- * Note that the function will check even non-enumerable properties. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * personal: { - * age: 25, - * gender: "M" - * }, - * scores: [ - * {id: 1, value: 10, passed: false}, - * {id: 2, value: 20, passed: false}, - * {id: 3, value: 30, passed: true} - * ] - * }; - * - * var isMale = _.hasPathValue("personal.gender", "M"); - * var hasPassedFirstTest = _.hasPathValue("scores.0.passed", true); - * var hasPassedLastTest = _.hasPathValue("scores.-1.passed", true); + * Same as {@link module:lamb.tear|tear}, but only the own properties of the object are + * taken into account. + * @example Showing the difference with tear: + * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3, enumerable: true} + * }); * - * isMale(user) // => true - * hasPassedFirstTest(user) // => false - * hasPassedLastTest(user) // => true + * _.tear(foo) // => [["c", "b", "a"], [3, 2, 1]] + * _.tearOwn(foo) // => [["c"], [3]] * * @memberof module:lamb * @category Object - * @see {@link module:lamb.hasKeyValue|hasKeyValue} - * @since 0.41.0 - * @param {String} path - * @param {*} value - * @param {String} [separator="."] - * @returns {Function} + * @function + * @see {@link module:lamb.tear|tear} + * @see {@link module:lamb.make|make} for the reverse operation + * @since 0.12.0 + * @param {Object} obj + * @returns {Array} */ - function hasPathValue (path, value, separator) { - return function (obj) { - var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); - - return pathInfo.isValid && areSVZ(pathInfo.target, value); - }; - } + var tearOwn = _tearFrom(keys); /** - * Builds a predicate to check if the given key satisfies the desired condition - * on an object. + * Creates a copy of the given object having the desired key value updated by applying + * the provided function to it.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the + * source is returned otherwise. * @example - * var users = [ - * {name: "John", age: 25}, - * {name: "Jane", age: 15}, - * ]; - * var isAdult = _.keySatisfies(_.isGTE(18), "age"); + * var user = {name: "John", visits: 2}; + * var toUpperCase = _.invoker("toUpperCase"); * - * isAdult(users[0]) // => true - * isAdult(users[1]) // => false + * _.updateIn(user, "name", toUpperCase) // => {name: "JOHN", visits: 2} + * _.updateIn(user, "surname", toUpperCase) // => {name: "John", visits: 2} + * + * @example Non-enumerable properties will be treated as non-existent: + * var user = Object.create({name: "John"}, {visits: {value: 2}}); + * + * _.updateIn(user, "visits", _.add(1)) // => {name: "John", visits: 2} * * @memberof module:lamb * @category Object - * @see {@link module:lamb.pathSatisfies|pathSatisfies} - * @since 0.45.0 - * @param {Function} predicate + * @see {@link module:lamb.updateKey|updateKey} + * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} + * @since 0.22.0 + * @param {Object} source * @param {String} key - * @returns {Function} + * @param {Function} updater + * @returns {Object} */ - function keySatisfies (predicate, key) { - return function (obj) { - return predicate.call(this, obj[key]); - }; + function updateIn (source, key, updater) { + return _isEnumerable(source, key) ? + _setIn(source, key, updater(source[key])) : + _merge(enumerables, source, {}); } /** - * Builds a partial application of {@link module:lamb.pathExistsIn|pathExistsIn} using the given - * path and the optional separator. The resulting function expects the object to check.
- * Note that the function will check even non-enumerable properties. + * Builds a partial application of {@link module:lamb.updateIn|updateIn} with the provided + * key and updater, expecting the object to act upon.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the + * source is returned otherwise. * @example - * var user = { - * name: "John", - * surname: "Doe", - * address: { - * city: "New York" - * }, - * scores: [10, 20, 15] - * }; - * - * var hasCity = _.pathExists("address.city"); - * var hasCountry = _.pathExists("address.country"); - * var hasAtLeastThreeScores = _.pathExists("scores.2"); + * var user = {name: "John", visits: 2}; + * var incrementVisits = _.updateKey("visits", _.add(1)); * - * hasCity(user) // => true - * hasCountry(user) // => false - * hasAtLeastThreeScores(user) // => true + * incrementVisits(user) // => {name: "John", visits: 3} * * @memberof module:lamb * @category Object * @function - * @see {@link module:lamb.pathExistsIn|pathExistsIn} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @since 0.43.0 - * @param {String} path - * @param {String} [separator="."] + * @see {@link module:lamb.updateIn|updateIn} + * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} + * @since 0.22.0 + * @param {String} key + * @param {Function} updater * @returns {Function} */ - var pathExists = _makePartial3(pathExistsIn); + var updateKey = _makePartial3(updateIn); /** - * Checks if the provided path exists in the given object.
- * Note that the function will check even non-enumerable properties. + * Allows to change a nested value in a copy of the given object by applying the provided + * function to it.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the + * source is returned otherwise.
+ * Like the other "path" functions, negative indexes can be used to access array elements, but + * the priority will be given to existing, and enumerable, object keys. * @example - * var user = { - * name: "John", - * surname: "Doe", - * address: { - * city: "New York" - * }, - * scores: [10, 20, 15] - * }; + * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; + * var inc = _.add(1); * - * _.pathExistsIn(user, "address.city") // => true - * _.pathExistsIn(user, "address.country") // => false - * _.pathExistsIn(user, "scores.1") // => true + * _.updatePathIn(user, "status.visits", inc) // => {id: 1, status: {scores: [2, 4, 6]}, visits: 1} + * + * @example Targeting arrays: + * _.updatePathIn(user, "status.scores.0", inc) // => {id: 1, status: {scores: [3, 4, 6], visits: 0}} + * + * // you can use negative indexes as well + * _.updatePathIn(user, "status.scores.-1", inc) // => {id: 1, status: {scores: [2, 4, 7], visits: 0}} + * + * @example Arrays can also be part of the path and not necessarily its target: + * var user = {id: 1, scores: [ + * {value: 2, year: "2000"}, + * {value: 4, year: "2001"}, + * {value: 6, year: "2002"} + * ]}; + * + * var newUser = _.updatePathIn(user, "scores.0.value", inc); + * // "newUser" holds: + * // {id: 1, scores: [ + * // {value: 3, year: "2000"}, + * // {value: 4, year: "2001"}, + * // {value: 6, year: "2002"} + * // ]} * * @memberof module:lamb * @category Object - * @see {@link module:lamb.pathExists|pathExists} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @since 0.43.0 - * @param {Object} obj + * @see {@link module:lamb.updatePath|updatePath} + * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} + * @since 0.24.0 + * @param {Object|Array} source * @param {String} path + * @param {Function} updater * @param {String} [separator="."] - * @returns {Boolean} + * @returns {Object|Array} */ - function pathExistsIn (obj, path, separator) { - return _getPathInfo(obj, _toPathParts(path, separator), true).isValid; + function updatePathIn (source, path, updater, separator) { + var parts = _toPathParts(path, separator); + var pathInfo = _getPathInfo(source, parts, false); + + if (pathInfo.isValid) { + return _setPathIn(source, parts, updater(pathInfo.target)); + } else { + return Array.isArray(source) ? slice(source, 0, source.length) : _merge(enumerables, source, {}); + } } /** - * Builds a predicate that verifies if a condition is satisfied for the given - * path in an object.
- * Like the other "path functions" you can use integers in the path, even - * negative ones, to refer to array-like object indexes, but the priority will - * be given to existing object keys. + * Builds a partial application of {@link module:lamb.updatePathIn|updatePathIn} + * expecting the object to act upon.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the + * source is returned otherwise.
+ * Like the other "path" functions, negative indexes can be used to access array elements, but + * the priority will be given to existing, and enumerable, object keys. * @example - * var user = { - * name: "John", - * performance: { - * scores: [1, 5, 10] - * } - * }; - * - * var gotAnHighScore = _.pathSatisfies(_.contains(10), "performance.scores"); - * var hadAGoodStart = _.pathSatisfies(_.isGT(6), "performance.scores.0"); + * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; + * var incrementScores = _.updatePath("status.scores", _.mapWith(_.add(1))) * - * gotAnHighScore(user) // => true - * hadAGoodStart(user) // => false + * incrementScores(user) // => {id: 1, status: {scores: [3, 5, 7], visits: 0}} * * @memberof module:lamb * @category Object - * @see {@link module:lamb.keySatisfies|keySatisfies} - * @since 0.45.0 - * @param {Function} predicate + * @see {@link module:lamb.updatePathIn|updatePathIn} + * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} + * @since 0.24.0 * @param {String} path + * @param {Function} updater * @param {String} [separator="."] * @returns {Function} */ - function pathSatisfies (predicate, path, separator) { - return function (obj) { - var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); - - return predicate.call(this, pathInfo.target); + function updatePath (path, updater, separator) { + return function (source) { + return updatePathIn(source, path, updater, separator); }; } @@ -6855,19 +6508,56 @@ */ var validateWith = _curry2(validate, true); - lamb.checker = checker; - lamb.has = has; - lamb.hasKey = hasKey; - lamb.hasKeyValue = hasKeyValue; - lamb.hasOwn = hasOwn; - lamb.hasOwnKey = hasOwnKey; - lamb.hasPathValue = hasPathValue; - lamb.keySatisfies = keySatisfies; - lamb.pathExists = pathExists; - lamb.pathExistsIn = pathExistsIn; - lamb.pathSatisfies = pathSatisfies; - lamb.validate = validate; - lamb.validateWith = validateWith; + /** + * Generates an array with the values of the enumerable properties of the given object.
+ * See also {@link module:lamb.ownValues|ownValues} to pick only from the own properties of the object. + * @example + * var user = {name: "john", surname: "doe", age: 30}; + * + * _.values(user) // => ["john", "doe", 30] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.ownValues|ownValues} + * @since 0.1.0 + * @param {Object} obj + * @returns {Array} + */ + var values = _valuesFrom(enumerables); + + /** + * A null-safe function to repeat the source string the desired amount of times. + * @private + * @param {String} source + * @param {Number} times + * @returns {String} + */ + function _repeat (source, times) { + var result = ""; + + for (var i = 0; i < times; i++) { + result += source; + } + + return result; + } + + /** + * Builds the prefix or suffix to be used when padding a string. + * @private + * @param {String} source + * @param {String} char + * @param {Number} len + * @returns {String} + */ + function _getPadding (source, char, len) { + if (!isNil(source) && type(source) !== "String") { + source = String(source); + } + + return _repeat(String(char)[0] || "", Math.ceil(len - source.length)); + } /** * Pads a string to the desired length with the given char starting from the beginning of the string. @@ -6943,6 +6633,16 @@ return _repeat(source, Math.floor(times)); } + /** + * A generic version of String.prototype.search + * @private + * @function + * @param {String} s + * @param {RegExp} pattern + * @return {Number} + */ + var _search = generic(String.prototype.search); + /** * Builds a predicate expecting a string to test against the given regular expression pattern. * @example @@ -6963,132 +6663,263 @@ }; } - lamb.padLeft = padLeft; - lamb.padRight = padRight; - lamb.repeat = repeat; - lamb.testWith = testWith; - - /* istanbul ignore next */ - if (typeof exports === "object") { - module.exports = lamb; - } else if (typeof define === "function" && define.amd) { - define(function () { - return lamb; - }); - } else { - host.lamb = lamb; + /** + * Accepts a constructor and builds a predicate expecting an object, + * which will be tested to verify whether the prototype of the constructor + * is in its prototype chain.
+ * Wraps in a convenient way the native + * [instanceof]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof} operator. + * @example + * function SomeObjA () {} + * + * var a = new SomeObjA(); + * var sObj = new String("foo"); + * var s = "foo"; + * + * _.isInstanceOf(Object)(a) // => true + * _.isInstanceOf(SomeObjA)(a) // => true + * + * _.isInstanceOf(Object)(sObj) // => true + * _.isInstanceOf(String)(sObj) // => true + * + * _.isInstanceOf(Object)(s) // => false + * _.isInstanceOf(String)(s) // => false + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isType|isType} + * @since 0.47.0 + * @param {*} constructor + * @returns {Function} + */ + function isInstanceOf (constructor) { + return function (obj) { + return obj instanceof constructor; + }; } -})(this); - -/** - * @callback AccumulatorCallback - * @global - * @param {*} previousValue - The value returned it the last execution of the accumulator or, in the first - * iteration, the {@link module:lamb.reduce|initialValue} if supplied. - * @param {*} currentValue - The value being processed in the current iteration. - * @param {Number} idx - The index of the element being processed. - * @param {ArrayLike} arrayLike - The list being traversed. - */ - -/** - * The built-in arguments object. - * @typedef {arguments} arguments - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments|arguments} in Mozilla documentation. - */ - -/** - * The built-in Array object. - * @typedef {Array} Array - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array|Array} in Mozilla documentation. - */ - -/** - * Any array-like object. - * @typedef {Array|String|arguments|?} ArrayLike - * @global - */ - -/** - * The built-in Boolean object. - * @typedef {Boolean} Boolean - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean|Boolean} in Mozilla documentation. - */ - -/** - * The built-in Date object. - * @typedef {Date} Date - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date|Date} in Mozilla documentation. - */ - -/** - * The built-in Function object. - * @typedef {Function} function - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function|Function} and - * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions|Functions} in Mozilla documentation. - */ - -/** - * @callback ListIteratorCallback - * @global - * @param {*} element - The element being evaluated. - * @param {Number} idx - The index of the element within the list. - * @param {ArrayLike} arrayLike - The list being traversed. - */ -/** - * The built-in Number object. - * @typedef {Number} Number - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number|Number} in Mozilla documentation. - */ - -/** - * The built-in Object object. - * @typedef {Object} Object - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object|Object} in Mozilla documentation. - */ - -/** - * @callback ObjectIteratorCallback - * @global - * @param {*} value - The value of the current property. - * @param {String} key - The property name. - * @param {Object} source - The object being traversed. - */ - -/** - * The built-in RegExp object. - * @typedef {RegExp} RegExp - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp|RegExp} in Mozilla documentation. - */ - -/** - * Represents a sorting criteria used by {@link module:lamb.sortedInsert|sortedInsert}, - * {@link module:lamb.sort|sort} and {@link module:lamb.sortWith|sortWith}, and it's - * usually built using {@link module:lamb.sorter|sorter} and {@link module:lamb.sorterDesc|sorterDesc}. - * @typedef {Sorter} Sorter - * @global - * @property {Boolean} isDescending - * @property {Function} compare - */ - -/** - * The built-in String object. - * @typedef {String} String - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String|String} in Mozilla documentation. - */ + /** + * Builds a predicate that expects a value to check against the specified type. + * @example + * var isString = _.isType("String"); + * + * isString("Hello") // => true + * isString(new String("Hi")) // => true + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.type|type} + * @since 0.1.0 + * @param {String} typeName + * @returns {Function} + */ + function isType (typeName) { + return function (value) { + return type(value) === typeName; + }; + } -/** - * The built-in primitive value undefined - * @typedef {Undefined} Undefined - * @global - * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined|undefined} in Mozilla documentation. - */ + exports.__ = __; + exports.always = always; + exports.areSVZ = areSVZ; + exports.binary = binary; + exports.clamp = clamp; + exports.clampWithin = clampWithin; + exports.compose = compose; + exports.forEach = forEach; + exports.generic = generic; + exports.identity = identity; + exports.isNil = isNil; + exports.isNull = isNull; + exports.isSVZ = isSVZ; + exports.isUndefined = isUndefined; + exports.map = map; + exports.mapWith = mapWith; + exports.partial = partial; + exports.partialRight = partialRight; + exports.reduce = reduce; + exports.reduceWith = reduceWith; + exports.slice = slice; + exports.sliceAt = sliceAt; + exports.type = type; + exports.append = append; + exports.appendTo = appendTo; + exports.contains = contains; + exports.count = count; + exports.countBy = countBy; + exports.difference = difference; + exports.drop = drop; + exports.dropFrom = dropFrom; + exports.dropWhile = dropWhile; + exports.every = every; + exports.everyIn = everyIn; + exports.filter = filter; + exports.filterWith = filterWith; + exports.find = find; + exports.findIndex = findIndex; + exports.findWhere = findWhere; + exports.findIndexWhere = findIndexWhere; + exports.flatMap = flatMap; + exports.flatMapWith = flatMapWith; + exports.flatten = flatten; + exports.getAt = getAt; + exports.getIndex = getIndex; + exports.group = group; + exports.groupBy = groupBy; + exports.head = head; + exports.index = index; + exports.indexBy = indexBy; + exports.init = init; + exports.insert = insert; + exports.insertAt = insertAt; + exports.intersection = intersection; + exports.isIn = isIn; + exports.last = last; + exports.list = list; + exports.partition = partition; + exports.partitionWith = partitionWith; + exports.pluck = pluck; + exports.pluckKey = pluckKey; + exports.pull = pull; + exports.pullFrom = pullFrom; + exports.reduceRight = reduceRight; + exports.reduceRightWith = reduceRightWith; + exports.reverse = reverse; + exports.rotate = rotate; + exports.rotateBy = rotateBy; + exports.setAt = setAt; + exports.setIndex = setIndex; + exports.shallowFlatten = shallowFlatten; + exports.some = some; + exports.someIn = someIn; + exports.sort = sort; + exports.sortedInsert = sortedInsert; + exports.sorter = sorter; + exports.sorterDesc = sorterDesc; + exports.sortWith = sortWith; + exports.tail = tail; + exports.take = take; + exports.takeFrom = takeFrom; + exports.takeWhile = takeWhile; + exports.transpose = transpose; + exports.union = union; + exports.unionBy = unionBy; + exports.uniques = uniques; + exports.uniquesBy = uniquesBy; + exports.updateAt = updateAt; + exports.updateIndex = updateIndex; + exports.zip = zip; + exports.zipWithIndex = zipWithIndex; + exports.application = application; + exports.apply = apply; + exports.applyTo = applyTo; + exports.asPartial = asPartial; + exports.aritize = aritize; + exports.collect = collect; + exports.curry = curry; + exports.curryable = curryable; + exports.curryableRight = curryableRight; + exports.curryRight = curryRight; + exports.debounce = debounce; + exports.flip = flip; + exports.getArgAt = getArgAt; + exports.invoker = invoker; + exports.invokerOn = invokerOn; + exports.mapArgs = mapArgs; + exports.pipe = pipe; + exports.tapArgs = tapArgs; + exports.throttle = throttle; + exports.unary = unary; + exports.adapter = adapter; + exports.allOf = allOf; + exports.anyOf = anyOf; + exports.areSame = areSame; + exports.case = case_; + exports.condition = condition; + exports.gt = gt; + exports.gte = gte; + exports.is = is; + exports.isGT = isGT; + exports.isGTE = isGTE; + exports.isLT = isLT; + exports.isLTE = isLTE; + exports.lt = lt; + exports.lte = lte; + exports.not = not; + exports.unless = unless; + exports.when = when; + exports.add = add; + exports.deduct = deduct; + exports.divide = divide; + exports.divideBy = divideBy; + exports.generate = generate; + exports.isFinite = isFinite_; + exports.isInteger = isInteger; + exports.isSafeInteger = isSafeInteger; + exports.modulo = modulo; + exports.multiply = multiply; + exports.multiplyBy = multiplyBy; + exports.randomInt = randomInt; + exports.range = range; + exports.remainder = remainder; + exports.subtract = subtract; + exports.sum = sum; + exports.checker = checker; + exports.enumerables = enumerables; + exports.fromPairs = fromPairs; + exports.getIn = getIn; + exports.getKey = getKey; + exports.getPath = getPath; + exports.getPathIn = getPathIn; + exports.has = has; + exports.hasKey = hasKey; + exports.hasOwn = hasOwn; + exports.hasOwnKey = hasOwnKey; + exports.hasKeyValue = hasKeyValue; + exports.hasPathValue = hasPathValue; + exports.immutable = immutable; + exports.keys = keys; + exports.keySatisfies = keySatisfies; + exports.make = make; + exports.mapValues = mapValues; + exports.mapValuesWith = mapValuesWith; + exports.merge = merge; + exports.mergeOwn = mergeOwn; + exports.ownPairs = ownPairs; + exports.ownValues = ownValues; + exports.pairs = pairs; + exports.pathExists = pathExists; + exports.pathExistsIn = pathExistsIn; + exports.pathSatisfies = pathSatisfies; + exports.pick = pick; + exports.pickIf = pickIf; + exports.pickKeys = pickKeys; + exports.rename = rename; + exports.renameKeys = renameKeys; + exports.renameWith = renameWith; + exports.setIn = setIn; + exports.setKey = setKey; + exports.setPath = setPath; + exports.setPathIn = setPathIn; + exports.skip = skip; + exports.skipIf = skipIf; + exports.skipKeys = skipKeys; + exports.tear = tear; + exports.tearOwn = tearOwn; + exports.updateIn = updateIn; + exports.updateKey = updateKey; + exports.updatePath = updatePath; + exports.updatePathIn = updatePathIn; + exports.validate = validate; + exports.validateWith = validateWith; + exports.values = values; + exports.padLeft = padLeft; + exports.padRight = padRight; + exports.repeat = repeat; + exports.testWith = testWith; + exports.isInstanceOf = isInstanceOf; + exports.isType = isType; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); diff --git a/dist/lamb.min.js b/dist/lamb.min.js index a8eb057..9ff5d49 100644 --- a/dist/lamb.min.js +++ b/dist/lamb.min.js @@ -1,10 +1,10 @@ /** - * @overview lamb - A lightweight, and docile, JavaScript library to help embracing functional programming. - * @author Andrea Scartabelli - * @version 0.57.0-alpha.3 - * @module lamb - * @license MIT - * @preserve - */ -!function(n){"use strict";var r=Object.create(null),o={},c=r;Object.defineProperties(r,{"@@lamb/placeholder":{get:function(){return c},set:function(n){c=n}},"@@lamb/version":{value:"0.57.0-alpha.3"}});var t=Object.prototype,e=String.prototype,f=4294967295;function u(n,r){return arguments.length?function(){return n.call(this,r.apply(this,arguments))}:a}var i=Function.bind.bind(Function.call);function a(n){return n}function l(o,a){return function(){if(!Array.isArray(a))return o.apply(this,arguments);for(var n,r=0,t=[],e=a.length,u=0;u>>0!==r&&(r=n.length),e&&1>>0}function J(n){var r=+n;return r!=r?0:r%1==0?r:Math.floor(Math.abs(r))*(r<0?-1:1)}function Q(n,r){return-r<=(n=J(n))&&n>1,a=e({value:t,index:o},{value:r[o],index:o});return i-u<=1?a<0?o:o+1:a<0?n(r,t,e,u,o):0===a?o+1:n(r,t,e,o,i)}(e,r,g(V(t)),0,e.length);return e.splice(u,0,r),e},r.sorter=Qr,r.sorterDesc=Xr,r.sortWith=Yr;var _r=d($r),nt=d($r,!0);function rt(e,u){return function(){for(var n=J(u),r=hn.apply(null,arguments).slice(0,n),t=r.length;t +* @version 0.57.0-rc.2 +* @module lamb +* @license MIT +* @preserve +*/ +!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((n=n||self).lamb={})}(this,function(n){"use strict";var c={};function i(n,r){return n!=n?r!=r:n===r}function u(t){return function(n,r){return t.call(this,n,r)}}function r(n,r,t){return n=+n,(t=+t)<(r=+r)?NaN:n>>0}function s(n,r){for(var t=0,e=h(n.length);t>>0!==r&&(r=n.length),e&&1>1,a=e({value:t,index:o},{value:r[o],index:o});return i-u<=1?a<0?o:o+1:a<0?n(r,t,e,u,o):0===a?o+1:n(r,t,e,o,i)}(e,r,Nn(Rn(t)),0,e.length);return e.splice(u,0,r),e},n.sorter=Dn,n.sorterDesc=Ln,n.sortWith=_n,n.tail=qn,n.take=Gn,n.takeFrom=Cn,n.takeWhile=function(r){return function(n){return x(n,0,_(n,r))}},n.transpose=Zn,n.union=Qn,n.unionBy=Jn,n.uniques=z,n.uniquesBy=R,n.updateAt=function(r,t){return function(n){return Wn(n,r,null,t)}},n.updateIndex=Xn,n.zip=function(n,r){return Zn([n,r])},n.zipWithIndex=Yn,n.application=$n,n.apply=nr,n.applyTo=rr,n.asPartial=function(n){return function o(a,f){return function(){for(var n,r=arguments.length,t=0,e=[],u=0,i=f.length;u\n * @version 0.57.0-alpha.3\n * @module lamb\n * @license MIT\n * @preserve\n */\n(function (host) {\n \"use strict\";\n\n var lamb = Object.create(null);\n var _ = {}; // internal placeholder for partial application\n var _placeholder = lamb; // default value for public placeholder\n\n Object.defineProperties(lamb, {\n /**\n * The object used as a placeholder in partial application. Its default value is\n * the lamb object itself.
\n * The property is public so that you can make Lamb use your own placeholder, however\n * you can't change it at will or the partially applied functions you defined before the\n * change won't recognize the former placeholder.\n * @alias module:lamb.@@lamb/placeholder\n * @category Special properties\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.53.0\n * @type Object\n */\n \"@@lamb/placeholder\": {\n get: function () {\n return _placeholder;\n },\n set: function (value) {\n _placeholder = value;\n }\n },\n\n /**\n * The current library version.\n * @alias module:lamb.@@lamb/version\n * @category Special properties\n * @readonly\n * @since 0.53.0\n * @type String\n */\n \"@@lamb/version\": {value: \"0.57.0-alpha.3\"}\n });\n\n // prototype shortcuts\n var _objectProto = Object.prototype;\n var _stringProto = String.prototype;\n\n // constants\n var MAX_ARRAY_LENGTH = 4294967295;\n\n /**\n * Builds a function that returns a constant value.\n * It's actually the simplest form of the K combinator or Kestrel.\n * @example\n * var truth = _.always(true);\n *\n * truth() // => true\n * truth(false) // => true\n * truth(1, 2) // => true\n *\n * // the value being returned is actually the\n * // very same value passed to the function\n * var foo = {bar: \"baz\"};\n * var alwaysFoo = _.always(foo);\n *\n * alwaysFoo() === foo // => true\n *\n * @memberof module:lamb\n * @category Function\n * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus}\n * @since 0.1.0\n * @param {*} value\n * @returns {Function}\n */\n function always (value) {\n return function () {\n return value;\n };\n }\n\n /**\n * Returns a function that is the composition of the functions given as parameters.\n * The first function consumes the result of the function that follows.\n * @example\n * var sayHi = function (name) { return \"Hi, \" + name; };\n * var capitalize = function (s) {\n * return s[0].toUpperCase() + s.substr(1).toLowerCase();\n * };\n * var fixNameAndSayHi = _.compose(sayHi, capitalize);\n *\n * sayHi(\"bOb\") // => \"Hi, bOb\"\n * fixNameAndSayHi(\"bOb\") // \"Hi, Bob\"\n *\n * var users = [{name: \"fred\"}, {name: \"bOb\"}];\n * var sayHiToUser = _.compose(fixNameAndSayHi, _.getKey(\"name\"));\n *\n * _.map(users, sayHiToUser) // [\"Hi, Fred\", \"Hi, Bob\"]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.pipe|pipe}\n * @since 0.1.0\n * @param {Function} a\n * @param {Function} b\n * @returns {Function}\n */\n function compose (a, b) {\n return arguments.length ? function () {\n return a.call(this, b.apply(this, arguments));\n } : identity;\n }\n\n /**\n * Creates generic functions out of methods.\n * @author A very little change on a great idea by [Irakli Gozalishvili]{@link https://github.com/Gozala/}.\n * Thanks for this *beautiful* one-liner (never liked your \"unbind\" naming choice, though).\n * @memberof module:lamb\n * @category Function\n * @function\n * @example\n * var join = _.generic(Array.prototype.join);\n *\n * join([1, 2, 3, 4, 5], \"-\") // => \"1-2-3-4-5\"\n *\n * // the function will work with any array-like object\n * join(\"hello\", \"-\") // => \"h-e-l-l-o\"\n *\n * @since 0.1.0\n * @param {Function} method\n * @returns {Function}\n */\n var generic = Function.bind.bind(Function.call);\n\n /**\n * The I combinator. Any value passed to the function is simply returned as it is.\n * @example\n * var foo = {bar: \"baz\"};\n *\n * _.identity(foo) === foo // true\n *\n * @memberof module:lamb\n * @category Function\n * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus}\n * @since 0.1.0\n * @param {*} value\n * @returns {*} The value passed as parameter.\n */\n function identity (value) {\n return value;\n }\n\n /**\n * Builds a partially applied function. The lamb object itself can be\n * used as a placeholder argument and it's useful to alias it with a short symbol\n * such as _.
\n * You can use a custom placeholder by setting the\n * {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} property.\n * @example\n * var users = [\n * {id: 1, name: \"John\", active: true, confirmedMail: true},\n * {id: 2, name: \"Jane\", active: true, confirmedMail: false},\n * {id: 3, name: \"Mario\", active: false, confirmedMail: false}\n * ];\n * var isKeyTrue = _.partial(_.hasKeyValue, [_, true]);\n * var isActive = isKeyTrue(\"active\");\n * var hasConfirmedMail = isKeyTrue(\"confirmedMail\");\n *\n * _.map(users, isActive) // => [true, true, false]\n * _.map(users, hasConfirmedMail) // => [true, false, false]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder}\n * @since 0.1.0\n * @param {Function} fn\n * @param {Array} args\n * @returns {Function}\n */\n function partial (fn, args) {\n return function () {\n if (!Array.isArray(args)) {\n return fn.apply(this, arguments);\n }\n\n var lastIdx = 0;\n var newArgs = [];\n var argsLen = args.length;\n\n for (var i = 0, boundArg; i < argsLen; i++) {\n boundArg = args[i];\n newArgs[i] = _isPlaceholder(boundArg) ? arguments[lastIdx++] : boundArg;\n }\n\n for (var len = arguments.length; lastIdx < len; lastIdx++) {\n newArgs[i++] = arguments[lastIdx];\n }\n\n return fn.apply(this, newArgs);\n };\n }\n\n /**\n * Like {@link module:lamb.partial|partial} will build a partially applied function and\n * it will accept placeholders.
\n * The difference is that the bound arguments will be appended to the ones received by\n * the resulting function.\n * @example\n * Explaining the difference with partial:\n * var f1 = _.partial(_.list, [\"a\", \"b\", \"c\"]);\n * var f2 = _.partialRight(_.list, [\"a\", \"b\", \"c\"]);\n *\n * f1(\"d\", \"e\") // => [\"a\", \"b\", \"c\", \"d\", \"e\"]\n * f2(\"d\", \"e\") // => [\"d\", \"e\", \"a\", \"b\", \"c\"]\n *\n * @example\n * Explaining placeholder substitutions:\n * var f1 = _.partial(_.list, [\"a\", _, _, \"d\"]);\n * var f2 = _.partialRight(_.list, [\"a\", _, _, \"d\"]);\n *\n * f1(\"b\", \"c\", \"e\") // => [\"a\", \"b\", \"c\", \"d\", \"e\"]\n * f2(\"b\", \"c\", \"e\") // => [\"b\", \"a\", \"c\", \"e\", \"d\"]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.partial|partial}\n * @see {@link module:lamb.asPartial|asPartial}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder}\n * @param {Function} fn\n * @param {Array} args\n * @since 0.52.0\n * @returns {Function}\n */\n function partialRight (fn, args) {\n return function () {\n if (!Array.isArray(args)) {\n return fn.apply(this, arguments);\n }\n\n var lastIdx = arguments.length - 1;\n var argsLen = args.length;\n var boundArgs = Array(argsLen);\n var newArgs = [];\n\n for (var i = argsLen - 1, boundArg; i > -1; i--) {\n boundArg = args[i];\n boundArgs[i] = _isPlaceholder(boundArg) ? arguments[lastIdx--] : boundArg;\n }\n\n for (i = 0; i <= lastIdx; i++) {\n newArgs[i] = arguments[i];\n }\n\n for (var j = 0; j < argsLen; j++) {\n newArgs[i++] = boundArgs[j];\n }\n\n return fn.apply(this, newArgs);\n };\n }\n\n lamb.always = always;\n lamb.compose = compose;\n lamb.generic = generic;\n lamb.identity = identity;\n lamb.partial = partial;\n lamb.partialRight = partialRight;\n\n /**\n * Builds an array with the received arguments excluding the first one.
\n * To be used with the arguments object, which needs to be passed to the apply\n * method of this function.\n * @private\n * @function\n * @param {...*} value\n * @returns {Array}\n */\n var _argsTail = _argsToArrayFrom(1);\n\n /**\n * Builds helper functions to extract portions of the arguments\n * object rather efficiently without having to write for loops\n * manually for each case.
\n * The arguments object needs to be passed to the apply method\n * of the generated function.\n * @private\n * @param {Number} idx\n * @returns {Function}\n */\n function _argsToArrayFrom (idx) {\n return function () {\n var argsLen = arguments.length || idx;\n var len = argsLen - idx;\n var result = Array(len);\n\n for (var i = 0; i < len; i++) {\n result[i] = arguments[i + idx];\n }\n\n return result;\n };\n }\n\n /**\n * Keeps building a partial application of the received function as long\n * as it's called with placeholders; applies the original function to\n * the collected parameters otherwise.
\n * The function checks only the public placeholder to gain a little performance\n * as no function in Lamb is built with {@link module:lamb.asPartial|asPartial}.\n * @private\n * @param {Function} fn\n * @param {Array} argsHolder\n * @returns {Function|*}\n */\n function _asPartial (fn, argsHolder) {\n return function () {\n var argsLen = arguments.length;\n var lastIdx = 0;\n var newArgs = [];\n\n for (var i = 0, len = argsHolder.length, boundArg; i < len; i++) {\n boundArg = argsHolder[i];\n newArgs[i] = boundArg === _placeholder && lastIdx < argsLen ? arguments[lastIdx++] : boundArg;\n }\n\n while (lastIdx < argsLen) {\n newArgs[i++] = arguments[lastIdx++];\n }\n\n for (i = 0; i < argsLen; i++) {\n if (arguments[i] === _placeholder) {\n return _asPartial(fn, newArgs);\n }\n }\n\n for (i = 0, len = newArgs.length; i < len; i++) {\n if (newArgs[i] === _placeholder) {\n newArgs[i] = void 0;\n }\n }\n\n return fn.apply(this, newArgs);\n };\n }\n\n /**\n * Creates a function to check the given predicates.
\n * Used to build the {@link module:lamb.allOf|allOf} and the\n * {@link module:lamb.anyOf|anyOf} functions.\n * @private\n * @param {Boolean} checkAll\n * @returns {Function}\n */\n function _checkPredicates (checkAll) {\n return function (predicates) {\n if (!Array.isArray(predicates)) {\n throw _makeTypeErrorFor(predicates, \"array\");\n }\n\n return function () {\n for (var i = 0, len = predicates.length, result; i < len; i++) {\n result = predicates[i].apply(this, arguments);\n\n if (checkAll && !result) {\n return false;\n } else if (!checkAll && result) {\n return true;\n }\n }\n\n return checkAll;\n };\n };\n }\n\n /**\n * The default comparer for sorting functions.
\n * If the given values are of different types they\n * will be both converted to strings.
\n * Uses the SameValueZero comparison.\n * @private\n * @param {*} a\n * @param {*} b\n * @returns {Number} -1 | 0 | 1\n */\n function _comparer (a, b) {\n var result = 0;\n\n if (typeof a !== typeof b) {\n a = String(a);\n b = String(b);\n }\n\n /* eslint-disable no-self-compare */\n\n if (!areSVZ(a, b)) {\n if (a > b || a !== a) {\n result = 1;\n } else if (a < b || b !== b) {\n result = -1;\n }\n }\n\n /* eslint-enable no-self-compare */\n\n return result;\n }\n\n /**\n * Accepts a list of sorting criteria with at least one element\n * and builds a function that compares two values with such criteria.\n * @private\n * @param {Sorter[]} criteria\n * @returns {Function}\n */\n function _compareWith (criteria) {\n return function (a, b) {\n var len = criteria.length;\n var criterion = criteria[0];\n var result = criterion.compare(a.value, b.value);\n\n for (var i = 1; result === 0 && i < len; i++) {\n criterion = criteria[i];\n result = criterion.compare(a.value, b.value);\n }\n\n if (result === 0) {\n result = a.index - b.index;\n }\n\n return criterion.isDescending ? -result : result;\n };\n }\n\n /**\n * Used by curry functions to collect arguments until the arity is consumed,\n * then applies the original function.\n * @private\n * @param {Function} fn\n * @param {Number} arity\n * @param {Boolean} isRightCurry\n * @param {Boolean} isAutoCurry\n * @param {Array} argsHolder\n * @returns {Function}\n */\n function _currier (fn, arity, isRightCurry, isAutoCurry, argsHolder) {\n return function () {\n var holderLen = argsHolder.length;\n var argsLen = arguments.length;\n var newArgsLen = holderLen + (argsLen > 1 && isAutoCurry ? argsLen : 1);\n var newArgs = Array(newArgsLen);\n\n for (var i = 0; i < holderLen; i++) {\n newArgs[i] = argsHolder[i];\n }\n\n for (; i < newArgsLen; i++) {\n newArgs[i] = arguments[i - holderLen];\n }\n\n if (newArgsLen >= arity) {\n return fn.apply(this, isRightCurry ? newArgs.reverse() : newArgs);\n } else {\n return _currier(fn, arity, isRightCurry, isAutoCurry, newArgs);\n }\n };\n }\n\n /**\n * Prepares a function for currying. If it's not auto-currying and the arity\n * is 2 or 3 returns optimized functions, otherwise delegates the currying\n * to the _currier function.
\n * If the desumed arity isn't greater than one, it will return the received\n * function itself, instead.\n * @private\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @param {Boolean} [isRightCurry=false]\n * @param {Boolean} [isAutoCurry=false]\n * @returns {Function}\n */\n function _curry (fn, arity, isRightCurry, isAutoCurry) {\n if (arity >>> 0 !== arity) {\n arity = fn.length;\n }\n\n if (isAutoCurry && arity > 1 || arity > 3) {\n return _currier(fn, arity, isRightCurry, isAutoCurry, []);\n } else if (arity === 2) {\n return _curry2(fn, isRightCurry);\n } else if (arity === 3) {\n return _curry3(fn, isRightCurry);\n } else {\n return fn;\n }\n }\n\n /**\n * Curries a function of arity 2.\n * @private\n * @param {Function} fn\n * @param {Boolean} [isRightCurry=false]\n * @returns {Function}\n */\n function _curry2 (fn, isRightCurry) {\n return function (a) {\n return function (b) {\n return isRightCurry ? fn.call(this, b, a) : fn.call(this, a, b);\n };\n };\n }\n\n /**\n * Curries a function of arity 3.\n * @private\n * @param {Function} fn\n * @param {Boolean} [isRightCurry=false]\n * @returns {Function}\n */\n function _curry3 (fn, isRightCurry) {\n return function (a) {\n return function (b) {\n return function (c) {\n return isRightCurry ? fn.call(this, c, b, a) : fn.call(this, a, b, c);\n };\n };\n };\n }\n\n /**\n * Flattens an array.\n * @private\n * @param {Array} array - The source array\n * @param {Boolean} isDeep - Whether to perform a deep flattening or not\n * @param {Array} output - An array to collect the result\n * @param {Number} idx - The next index to be filled in the output\n * @returns {Array} The output array filled with the results\n */\n function _flatten (array, isDeep, output, idx) {\n for (var i = 0, len = array.length, value, j, vLen; i < len; i++) {\n value = array[i];\n\n if (!Array.isArray(value)) {\n output[idx++] = value;\n } else if (isDeep) {\n _flatten(value, true, output, idx);\n idx = output.length;\n } else {\n vLen = value.length;\n output.length += vLen;\n\n for (j = 0; j < vLen; j++) {\n output[idx++] = value[j];\n }\n }\n }\n\n return output;\n }\n\n /**\n * Converts a value to a number and returns it if it's not NaN, otherwise\n * returns zero.\n * @private\n * @param {*} value\n * @returns {Number}\n */\n function _forceToNumber (value) {\n var n = +value;\n\n return n === n ? n : 0; // eslint-disable-line no-self-compare\n }\n\n /**\n * Establishes at which index an element should be inserted in a sorted array to respect\n * the array order. Needs the comparer used to sort the array.\n * @private\n * @param {Array} array\n * @param {*} element\n * @param {Function} comparer\n * @param {Number} start\n * @param {Number} end\n * @returns {Number}\n */\n function _getInsertionIndex (array, element, comparer, start, end) {\n if (array.length === 0) {\n return 0;\n }\n\n var pivot = (start + end) >> 1;\n var result = comparer(\n {value: element, index: pivot},\n {value: array[pivot], index: pivot}\n );\n\n if (end - start <= 1) {\n return result < 0 ? pivot : pivot + 1;\n } else if (result < 0) {\n return _getInsertionIndex(array, element, comparer, start, pivot);\n } else if (result === 0) {\n return pivot + 1;\n } else {\n return _getInsertionIndex(array, element, comparer, pivot, end);\n }\n }\n\n /**\n * Gets the number of consecutive elements satisfying a predicate in an array-like object.\n * @private\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Number}\n */\n function _getNumConsecutiveHits (arrayLike, predicate) {\n var idx = 0;\n var len = arrayLike.length;\n\n while (idx < len && predicate(arrayLike[idx], idx, arrayLike)) {\n idx++;\n }\n\n return idx;\n }\n\n /**\n * Builds the prefix or suffix to be used when padding a string.\n * @private\n * @param {String} source\n * @param {String} char\n * @param {Number} len\n * @returns {String}\n */\n function _getPadding (source, char, len) {\n if (!isNil(source) && type(source) !== \"String\") {\n source = String(source);\n }\n\n return _repeat(String(char)[0] || \"\", Math.ceil(len - source.length));\n }\n\n /**\n * Checks if a path is valid in the given object and retrieves the path target.\n * @private\n * @param {Object} obj\n * @param {String[]} parts\n * @param {Boolean} walkNonEnumerables\n * @returns {Object}\n */\n function _getPathInfo (obj, parts, walkNonEnumerables) {\n if (isNil(obj)) {\n throw _makeTypeErrorFor(obj, \"object\");\n }\n\n var target = obj;\n var i = -1;\n var len = parts.length;\n var key;\n\n while (++i < len) {\n key = _getPathKey(target, parts[i], walkNonEnumerables);\n\n if (isUndefined(key)) {\n break;\n }\n\n target = target[key];\n }\n\n return i === len ? {isValid: true, target: target} : {isValid: false, target: void 0};\n }\n\n /**\n * Helper to retrieve the correct key while evaluating a path.\n * @private\n * @param {Object} target\n * @param {String} key\n * @param {Boolean} includeNonEnumerables\n * @returns {String|Number|Undefined}\n */\n function _getPathKey (target, key, includeNonEnumerables) {\n if (includeNonEnumerables && key in Object(target) || _isEnumerable(target, key)) {\n return key;\n }\n\n var n = +key;\n var len = target && target.length;\n\n return n >= -len && n < len ? n < 0 ? n + len : n : void 0;\n }\n\n /**\n * Builds a \"grouping function\" for an array-like object.\n * @private\n * @param {Function} makeValue\n * @returns {Function}\n */\n function _groupWith (makeValue) {\n return function (arrayLike, iteratee) {\n var result = {};\n var len = arrayLike.length;\n\n for (var i = 0, element, key; i < len; i++) {\n element = arrayLike[i];\n key = iteratee(element, i, arrayLike);\n result[key] = makeValue(result[key], element);\n }\n\n return result;\n };\n }\n\n /**\n * Makes an object immutable by recursively calling Object.freeze\n * on its members.\n * @private\n * @param {Object} obj\n * @param {Array} seen\n * @returns {Object} The obj parameter itself, not a copy.\n */\n function _immutable (obj, seen) {\n if (seen.indexOf(obj) === -1) {\n seen.push(Object.freeze(obj));\n\n forEach(Object.getOwnPropertyNames(obj), function (key) {\n var value = obj[key];\n\n if (typeof value === \"object\" && !isNull(value)) {\n _immutable(value, seen);\n }\n });\n }\n\n return obj;\n }\n\n /**\n * If a method with the given name exists on the target, applies it to the provided\n * arguments and returns the result. Returns undefined otherwise.
\n * The arguments for the method are built by concatenating the array of bound arguments,\n * optionally received by {@link module:lamb.invoker|invoker}, with the final set of, also\n * optional, args.\n * @private\n * @param {Array} boundArgs\n * @param {String} methodName\n * @param {Object} target\n * @param {...*} [args]\n * @returns {*}\n */\n function _invoker (boundArgs, methodName, target) {\n var method = target[methodName];\n\n if (typeof method !== \"function\") {\n return void 0;\n }\n\n var boundArgsLen = boundArgs.length;\n var ofs = 3 - boundArgsLen;\n var len = arguments.length - ofs;\n var args = Array(len);\n\n for (var i = 0; i < boundArgsLen; i++) {\n args[i] = boundArgs[i];\n }\n\n for (; i < len; i++) {\n args[i] = arguments[i + ofs];\n }\n\n return method.apply(target, args);\n }\n\n /**\n * Accepts a target object and a key name and verifies that the target is an array and that\n * the key is an existing index.\n * @private\n * @param {Object} target\n * @param {String|Number} key\n * @returns {Boolean}\n */\n function _isArrayIndex (target, key) {\n var n = +key;\n\n return Array.isArray(target) && n % 1 === 0 && !(n < 0 && _isEnumerable(target, key));\n }\n\n /**\n * Checks whether the specified key is an enumerable property of the given object or not.\n * @private\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n function _isEnumerable (obj, key) {\n return key in Object(obj) && (_isOwnEnumerable(obj, key) || ~_safeEnumerables(obj).indexOf(key));\n }\n\n /**\n * Checks whether the specified key is a own enumerable property of the given object or not.\n * @private\n * @function\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n var _isOwnEnumerable = generic(_objectProto.propertyIsEnumerable);\n\n /**\n * Checks whether the given value is the internal or the public placeholder.\n * @private\n * @param {*} value\n * @returns {Boolean}\n */\n function _isPlaceholder (value) {\n return value === _ || value === _placeholder;\n }\n\n /**\n * Accepts an object and build a function expecting a key to create a \"pair\" with the key\n * and its value.\n * @private\n * @function\n * @param {Object} obj\n * @returns {Function}\n */\n var _keyToPairIn = _curry2(function (obj, key) {\n return [key, obj[key]];\n });\n\n /**\n * Helper to build the {@link module:lamb.everyIn|everyIn} or the\n * {@link module:lamb.someIn|someIn} function.\n * @private\n * @param {Boolean} defaultResult\n * @returns {Function}\n */\n function _makeArrayChecker (defaultResult) {\n return function (arrayLike, predicate) {\n for (var i = 0, len = arrayLike.length; i < len; i++) {\n if (defaultResult ^ !!predicate(arrayLike[i], i, arrayLike)) {\n return !defaultResult;\n }\n }\n\n return defaultResult;\n };\n }\n\n /**\n * Helper to build the {@link module:lamb.flatten|flatten} and\n * {@link module:lamb.shallowFlatten|shallowFlatten} functions.\n * @private\n * @function\n * @param {Boolean} isDeep\n * @returns {Function}\n */\n var _makeArrayFlattener = _curry2(function (isDeep, array) {\n return Array.isArray(array) ? _flatten(array, isDeep, [], 0) : slice(array, 0, array.length);\n });\n\n /**\n * Builds a list of sorting criteria from a list of sorter functions. Returns a list containing\n * a single default sorting criterion if the sorter list is empty.\n * @private\n * @param {Function[]} sorters\n * @returns {Sorter[]}\n */\n function _makeCriteria (sorters) {\n return sorters && sorters.length ? map(sorters, _makeCriterion) : [_sorter()];\n }\n\n /**\n * Converts a sorting function to a sorting criterion if necessary.\n * @private\n * @param {Function} criterion\n * @returns {Sorter}\n */\n function _makeCriterion (criterion) {\n return criterion && typeof criterion.compare === \"function\" ? criterion : _sorter(criterion);\n }\n\n /**\n * Builds a partial application of a ternary function so that its first parameter\n * is expected as the last one.
\n * The shouldAritize parameter is for the \"reduce\" functions, where\n * the absence of the initialValue transforms a \"fold\" operation into a\n * \"reduce\" one.\n * @private\n * @param {Function} fn\n * @param {Boolean} shouldAritize\n * @returns {Function}\n */\n function _makePartial3 (fn, shouldAritize) {\n return function (a, b) {\n var f = shouldAritize && arguments.length !== 2 ? binary(fn) : fn;\n\n return partial(f, [_, a, b]);\n };\n }\n\n /**\n * Builds a reduce function. The step parameter must be 1\n * to build {@link module:lamb.reduce|reduce} and -1 to build\n * {@link module:lamb.reduceRight|reduceRight}.\n * @private\n * @param {Number} step\n * @returns {Function}\n */\n function _makeReducer (step) {\n return function (arrayLike, accumulator, initialValue) {\n var len = _toArrayLength(arrayLike.length);\n var idx = step === 1 ? 0 : len - 1;\n var nCalls;\n var result;\n\n if (arguments.length === 3) {\n nCalls = len;\n result = initialValue;\n } else {\n if (len === 0) {\n throw new TypeError(\"Reduce of empty array-like with no initial value\");\n }\n\n result = arrayLike[idx];\n idx += step;\n nCalls = len - 1;\n }\n\n for (; nCalls--; idx += step) {\n result = accumulator(result, arrayLike[idx], idx, arrayLike);\n }\n\n return result;\n };\n }\n\n /**\n * Builds a TypeError stating that it's not possible to convert the given value to the\n * desired type.\n * @private\n * @param {*} value\n * @param {String} desiredType\n * @returns {TypeError}\n */\n function _makeTypeErrorFor (value, desiredType) {\n return new TypeError(\"Cannot convert \" + type(value).toLowerCase() + \" to \" + desiredType);\n }\n\n /**\n * Merges the received objects using the provided function to retrieve their keys.\n * @private\n * @param {Function} getKeys\n * @param {Object} a\n * @param {Object} b\n * @returns {Function}\n */\n function _merge (getKeys, a, b) {\n return reduce([a, b], function (result, source) {\n forEach(getKeys(source), function (key) {\n result[key] = source[key];\n });\n\n return result;\n }, {});\n }\n\n /**\n * Using the provided function to retrieve the keys, builds a new function\n * expecting an object to create a list of key / value pairs.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _pairsFrom = _curry2(function (getKeys, obj) {\n return map(getKeys(obj), _keyToPairIn(obj));\n });\n\n /**\n * A null-safe function to repeat the source string the desired amount of times.\n * @private\n * @param {String} source\n * @param {Number} times\n * @returns {String}\n */\n function _repeat (source, times) {\n var result = \"\";\n\n for (var i = 0; i < times; i++) {\n result += source;\n }\n\n return result;\n }\n\n /**\n * Builds a list of the enumerable properties of an object.\n * The function is null-safe, unlike the public one.\n * @private\n * @param {Object} obj\n * @returns {String[]}\n */\n function _safeEnumerables (obj) {\n var result = [];\n\n for (var key in obj) {\n result.push(key);\n }\n\n return result;\n }\n\n /**\n * A null-safe version of Object.keys.\n * @private\n * @function\n * @param {Object} obj\n * @returns {String[]}\n */\n var _safeKeys = compose(Object.keys, Object);\n\n /**\n * A generic version of String.prototype.search\n * @private\n * @function\n * @param {String} s\n * @param {RegExp} pattern\n * @return {Number}\n */\n var _search = generic(_stringProto.search);\n\n /**\n * Sets, or creates, a property in a copy of the provided object to the desired value.\n * @private\n * @param {Object} source\n * @param {String} key\n * @param {*} value\n * @returns {Object}\n */\n function _setIn (source, key, value) {\n var result = {};\n\n for (var prop in source) {\n result[prop] = source[prop];\n }\n\n result[key] = value;\n\n return result;\n }\n\n /**\n * Sets an index in an array-like object.
\n * If provided with an updater function it will use it to update the current value,\n * otherwise sets the index to the specified value.\n * @private\n * @param {ArrayLike} arrayLike\n * @param {Number} idx\n * @param {*} [value]\n * @param {Function} [updater]\n * @returns {Array}\n */\n function _setIndex (arrayLike, idx, value, updater) {\n var result = slice(arrayLike, 0, arrayLike.length);\n var n = _toNaturalIndex(idx, result.length);\n\n if (n === n) { // eslint-disable-line no-self-compare\n result[n] = arguments.length === 4 ? updater(arrayLike[n]) : value;\n }\n\n return result;\n }\n\n /**\n * Sets the object's property targeted by the given path to the desired value.
\n * Works with arrays and is able to set their indexes, even negative ones.\n * @private\n * @param {Object|Array} obj\n * @param {String[]} parts\n * @param {*} value\n * @returns {Object|Array}\n */\n function _setPathIn (obj, parts, value) {\n var key = parts[0];\n var partsLen = parts.length;\n var v;\n\n if (partsLen === 1) {\n v = value;\n } else {\n var targetKey = _getPathKey(obj, key, false);\n\n v = _setPathIn(\n isUndefined(targetKey) ? targetKey : obj[targetKey],\n slice(parts, 1, partsLen),\n value\n );\n }\n\n return _isArrayIndex(obj, key) ? _setIndex(obj, key, v) : _setIn(obj, key, v);\n }\n\n /**\n * Builds a sorting criterion. If the comparer function is missing, the default\n * comparer will be used instead.\n * @private\n * @param {Function} reader\n * @param {Boolean} isDescending\n * @param {Function} [comparer]\n * @returns {Sorter}\n */\n function _sorter (reader, isDescending, comparer) {\n if (typeof reader !== \"function\" || reader === identity) {\n reader = null;\n }\n\n if (typeof comparer !== \"function\") {\n comparer = _comparer;\n }\n\n return {\n isDescending: isDescending === true,\n compare: function (a, b) {\n if (reader) {\n a = reader(a);\n b = reader(b);\n }\n\n return comparer(a, b);\n }\n };\n }\n\n /**\n * Using the provided function to retrieve the keys of an object, builds\n * a function expecting an object to create an array containing a list\n * of the keys in its first index and the corresponding list of values\n * in the second one.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _tearFrom = _curry2(function (getKeys, obj) {\n return reduce(getKeys(obj), function (result, key) {\n result[0].push(key);\n result[1].push(obj[key]);\n\n return result;\n }, [[], []]);\n });\n\n /**\n * Converts a value to a valid array length, thus an integer within\n * 0 and 232 - 1 (both included).\n * @private\n * @param {*} value\n * @returns {Number}\n */\n function _toArrayLength (value) {\n return clamp(value, 0, MAX_ARRAY_LENGTH) >>> 0;\n }\n\n /**\n * Converts a value to an integer.\n * @private\n * @param {*} value\n * @returns {Number}\n */\n function _toInteger (value) {\n var n = +value;\n\n if (n !== n) { // eslint-disable-line no-self-compare\n return 0;\n } else if (n % 1 === 0) {\n return n;\n } else {\n return Math.floor(Math.abs(n)) * (n < 0 ? -1 : 1);\n }\n }\n\n /**\n * Checks if the given number, even negative, represents an array-like index\n * within the provided length. If so returns its natural number equivalent.
\n * Returns NaN otherwise.\n * @private\n * @param {Number} idx\n * @param {Number} len\n * @returns {Number}\n */\n function _toNaturalIndex (idx, len) {\n idx = _toInteger(idx);\n\n return idx >= -len && idx < len ? idx < 0 ? idx + len : idx : NaN;\n }\n\n /**\n * Splits a sting path using the provided separator and returns an array\n * of path parts.\n * @private\n * @param {String} path\n * @param {String} separator\n * @returns {String[]}\n */\n function _toPathParts (path, separator) {\n return String(path).split(separator || \".\");\n }\n\n /**\n * Creates a non-null-safe version of the provided \"getKeys\" function.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _unsafeKeyListFrom = _curry2(function (getKeys, obj) {\n if (isNil(obj)) {\n throw _makeTypeErrorFor(obj, \"object\");\n }\n\n return getKeys(obj);\n });\n\n /**\n * Using the provided function to retrieve the keys of an object, builds\n * a function expecting an object to create the list of values for such keys.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _valuesFrom = _curry2(function (getKeys, obj) {\n return map(getKeys(obj), partial(getIn, [obj]));\n });\n\n /**\n * Builds a predicate to check if an array-like object contains the given value.
\n * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can\n * check for NaN, but 0 and -0 are the same value.
\n * See also {@link module:lamb.isIn|isIn} for an uncurried version.\n * @example\n * var containsNaN = _.contains(NaN);\n *\n * containsNaN([0, 1, 2, 3, NaN]) // => true\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.isIn|isIn}\n * @since 0.13.0\n * @param {*} value\n * @returns {Function}\n */\n var contains = _curry2(isIn, true);\n\n /**\n * Checks if all the elements in an array-like object satisfy the given predicate.
\n * The function will stop calling the predicate as soon as it returns a falsy value.
\n * Note that an empty array-like will always produce a true result regardless of the\n * predicate because of [vacuous truth]{@link https://en.wikipedia.org/wiki/Vacuous_truth}.
\n * Also note that unlike the native\n * [Array.prototype.every]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every},\n * this function won't skip deleted or unassigned indexes.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12, active: true},\n * {\"name\": \"John\", \"age\": 40, active: true},\n * {\"name\": \"Mario\", \"age\": 17, active: true},\n * {\"name\": \"Paolo\", \"age\": 15, active: true}\n * ];\n * var isAdult = _.keySatisfies(_.isGTE(18), \"age\");\n * var isActive = _.hasKeyValue(\"active\", true);\n *\n * _.everyIn(persons, isAdult) // => false\n * _.everyIn(persons, isActive) // => true\n *\n * @example Showing the difference with Array.prototype.every:\n * var isDefined = _.not(_.isUndefined);\n * var arr = new Array(5);\n * arr[3] = 99;\n *\n * arr.every(isDefined) // => true\n * _.everyIn(arr, isDefined) // => false\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.every|every}\n * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn}\n * @since 0.39.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Boolean}\n */\n var everyIn = _makeArrayChecker(true);\n\n /**\n * A curried version of {@link module:lamb.everyIn|everyIn} that expects a predicate\n * to build a function waiting for the array-like to act upon.\n * @example\n * var data = [2, 3, 5, 6, 8];\n * var isEven = function (n) { return n % 2 === 0; };\n * var allEvens = _.every(isEven);\n * var allIntegers = _.every(_.isInteger);\n *\n * allEvens(data) // => false\n * allIntegers(data) // => true\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.everyIn|everyIn}\n * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn}\n * @since 0.39.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var every = _curry2(everyIn, true);\n\n /**\n * Builds an array comprised of all values of the array-like object passing the predicate\n * test.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example\n * var isLowerCase = function (s) { return s.toLowerCase() === s; };\n *\n * _.filter([\"Foo\", \"bar\", \"baZ\"], isLowerCase) // => [\"bar\"]\n *\n * // the function will work with any array-like object\n * _.filter(\"fooBAR\", isLowerCase) // => [\"f\", \"o\", \"o\"]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.filterWith|filterWith}\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @since 0.1.0\n * @returns {Array}\n */\n function filter (arrayLike, predicate) {\n var len = arrayLike.length;\n var result = [];\n\n for (var i = 0; i < len; i++) {\n predicate(arrayLike[i], i, arrayLike) && result.push(arrayLike[i]);\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.filter|filter} that uses the given predicate\n * to build a function expecting the array-like object to act upon.\n * @example\n * var isLowerCase = function (s) { return s.toLowerCase() === s; };\n * var getLowerCaseEntries = _.filterWith(isLowerCase);\n *\n * getLowerCaseEntries([\"Foo\", \"bar\", \"baZ\"]) // => [\"bar\"]\n *\n * // array-like objects can be used as well\n * getLowerCaseEntries(\"fooBAR\") // => [\"f\", \"o\", \"o\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.filter|filter}\n * @since 0.9.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var filterWith = _curry2(filter, true);\n\n /**\n * Searches for an element satisfying the predicate in the given array-like object and returns it if\n * the search is successful. Returns undefined otherwise.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 40}\n * ];\n *\n * _.find(persons, _.hasKeyValue(\"age\", 40)) // => {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40}\n * _.find(persons, _.hasKeyValue(\"age\", 41)) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.findWhere|findWhere}\n * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere}\n * @since 0.7.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {*}\n */\n function find (arrayLike, predicate) {\n var idx = findIndex(arrayLike, predicate);\n\n return idx === -1 ? void 0 : arrayLike[idx];\n }\n\n /**\n * Searches for an element satisfying the predicate in the given array-like object and returns its\n * index if the search is successful. Returns -1 otherwise.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 40}\n * ];\n *\n * _.findIndex(persons, _.hasKeyValue(\"age\", 40)) // => 1\n * _.findIndex(persons, _.hasKeyValue(\"age\", 41)) // => -1\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.findIndexWhere|findIndexWhere}\n * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere}\n * @since 0.7.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Number}\n */\n function findIndex (arrayLike, predicate) {\n var result = -1;\n\n for (var i = 0, len = arrayLike.length; i < len; i++) {\n if (predicate(arrayLike[i], i, arrayLike)) {\n result = i;\n break;\n }\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.findIndex|findIndex} that uses the given predicate\n * to build a function expecting the array-like object to search.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var findEvenIdx = _.findIndexWhere(isEven);\n *\n * findEvenIdx([1, 3, 4, 5, 7]) // => 2\n * findEvenIdx([1, 3, 5, 7]) // => -1\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.findIndex|findIndex}\n * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere}\n * @since 0.41.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var findIndexWhere = _curry2(findIndex, true);\n\n /**\n * A curried version of {@link module:lamb.find|find} expecting the array-like object\n * to search.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var findEven = _.findWhere(isEven);\n *\n * findEven([1, 3, 4, 5, 7]) // => 4\n * findEven([1, 3, 5, 7]) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.find|find}\n * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere}\n * @since 0.41.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var findWhere = _curry2(find, true);\n\n /**\n * Executes the provided iteratee for each element of the given array-like object.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example Adding a CSS class to all elements of a NodeList in a browser environment:\n * var addClass = _.curry(function (className, element) {\n * element.classList.add(className);\n * });\n * var paragraphs = document.querySelectorAll(\"#some-container p\");\n *\n * _.forEach(paragraphs, addClass(\"main\"));\n * // each \"p\" element in the container will have the \"main\" class now\n *\n * @memberof module:lamb\n * @category Array\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Undefined}\n */\n function forEach (arrayLike, iteratee) {\n for (var i = 0, len = _toArrayLength(arrayLike.length); i < len; i++) {\n iteratee(arrayLike[i], i, arrayLike);\n }\n }\n\n /**\n * Checks if an array-like object contains the given value.
\n * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can\n * check for NaN, but 0 and -0 are the same value.
\n * See also {@link module:lamb.contains|contains} for a curried version building a predicate.\n * @example\n * var numbers = [0, 1, 2, 3, NaN];\n *\n * _.isIn(numbers, 1) // => true\n * _.isIn(numbers, 0) // => true\n * _.isIn(numbers, -0) // => true\n * _.isIn(numbers, NaN) // => true\n * _.isIn(numbers, 5) // => false\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.contains|contains}\n * @since 0.13.0\n * @param {ArrayLike} arrayLike\n * @param {*} value\n * @returns {Boolean}\n */\n function isIn (arrayLike, value) {\n var result = false;\n\n for (var i = 0, len = arrayLike.length; i < len; i++) {\n if (areSVZ(value, arrayLike[i])) {\n result = true;\n break;\n }\n }\n\n return result;\n }\n\n /**\n * Generates an array with the values passed as arguments.
\n * Behaves like ES6's [Array.of]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of}.\n * @example\n * _.list(1, 2, 3) // => [1, 2, 3]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @since 0.1.0\n * @param {...*} value\n * @returns {Array}\n */\n var list = _argsToArrayFrom(0);\n\n /**\n * Builds a new array by applying the iteratee function to each element of the\n * received array-like object.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example\n * _.map([\"Joe\", \"Mario\", \"Jane\"], _.invoker(\"toUpperCase\")) // => [\"JOE\", \"MARIO\", \"JANE\"]\n *\n * _.map([4, 9, 16], Math.sqrt); // => [2, 3, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.mapWith|mapWith}\n * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Array}\n */\n function map (arrayLike, iteratee) {\n var len = _toArrayLength(arrayLike.length);\n var result = Array(len);\n\n for (var i = 0; i < len; i++) {\n result[i] = iteratee(arrayLike[i], i, arrayLike);\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.map|map} that uses the provided iteratee to\n * build a function expecting the array-like object to act upon.\n * @example\n * var square = function (n) { return n * n; };\n * var getSquares = _.mapWith(square);\n *\n * getSquares([1, 2, 3, 4, 5]) // => [1, 4, 9, 16, 25]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.map|map}\n * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith}\n * @since 0.1.0\n * @param {ListIteratorCallback} iteratee\n * @returns {function}\n */\n var mapWith = _curry2(map, true);\n\n /**\n * Reduces (or folds) the values of an array-like object, starting from the first, to a new\n * value using the provided accumulator function.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example\n * _.reduce([1, 2, 3, 4], _.sum) // => 10\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduceRight|reduceRight}\n * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {*}\n */\n var reduce = _makeReducer(1);\n\n /**\n * Same as {@link module:lamb.reduce|reduce}, but starts the fold operation from the last\n * element instead.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduce|reduce}\n * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {*}\n */\n var reduceRight = _makeReducer(-1);\n\n /**\n * A partial application of {@link module:lamb.reduce|reduceRight} that uses the\n * provided accumulator and the optional initialValue to\n * build a function expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.reduceRightWith(_.sum)(arr) // => 15\n * _.reduceRightWith(_.subtract)(arr) // => -5\n * _.reduceRightWith(_.subtract, 0)(arr) // => -15\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduceWith|reduceWith}\n * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight}\n * @since 0.27.0\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {Function}\n */\n var reduceRightWith = _makePartial3(reduceRight, true);\n\n /**\n * A partial application of {@link module:lamb.reduce|reduce} that uses the\n * provided accumulator and the optional initialValue to\n * build a function expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.reduceWith(_.sum)(arr) // => 15\n * _.reduceWith(_.subtract)(arr) // => -13\n * _.reduceWith(_.subtract, 0)(arr) // => -15\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduceRightWith|reduceRightWith}\n * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight}\n * @since 0.27.0\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {Function}\n */\n var reduceWith = _makePartial3(reduce, true);\n\n /**\n * Reverses a copy of the given array-like object.\n * @example\n * var arr = [1, 2, 3];\n *\n * _.reverse(arr) // => [3, 2, 1];\n *\n * // `arr` still is [1, 2, 3]\n *\n * @memberof module:lamb\n * @category Array\n * @since 0.19.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n function reverse (arrayLike) {\n var len = _toArrayLength(arrayLike.length);\n var result = Array(len);\n\n for (var i = 0, ofs = len - 1; i < len; i++) {\n result[i] = arrayLike[ofs - i];\n }\n\n return result;\n }\n\n /**\n * Builds an array by extracting a portion of an array-like object.
\n * Note that unlike the native array method this function ensures that dense\n * arrays are returned.
\n * Also, unlike the native method, the start and end\n * parameters aren't optional and will be simply converted to integer.
\n * See {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a\n * slice to the end of the array-like.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.slice(arr, 0, 2) // => [1, 2]\n * _.slice(arr, 2, -1) // => [3, 4]\n * _.slice(arr, -3, 5) // => [3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.sliceAt|sliceAt}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike - Any array like object.\n * @param {Number} start - Index at which to begin extraction.\n * @param {Number} end - Index at which to end extraction. Extracts up to but not including end.\n * @returns {Array}\n */\n function slice (arrayLike, start, end) {\n var len = _toArrayLength(arrayLike.length);\n var begin = _toInteger(start);\n var upTo = _toInteger(end);\n\n if (begin < 0) {\n begin = begin < -len ? 0 : begin + len;\n }\n\n if (upTo < 0) {\n upTo = upTo < -len ? 0 : upTo + len;\n } else if (upTo > len) {\n upTo = len;\n }\n\n var resultLen = upTo - begin;\n var result = resultLen > 0 ? Array(resultLen) : [];\n\n for (var i = 0; i < resultLen; i++) {\n result[i] = arrayLike[begin + i];\n }\n\n return result;\n }\n\n /**\n * Given the start and end bounds, builds a partial application\n * of {@link module:lamb.slice|slice} expecting the array-like object to slice.
\n * See also {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a\n * slice to the end of the array-like.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n * var s = \"hello\";\n * var dropFirstAndLast = _.sliceAt(1, -1);\n *\n * dropFirstAndLast(arr) // => [2, 3, 4]\n * dropFirstAndLast(s) // => [\"e\", \"l\", \"l\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.slice|slice}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @since 0.48.0\n * @param {Number} start - Index at which to begin extraction.\n * @param {Number} end - Index at which to end extraction. Extracts up to but not including end.\n * @returns {Function}\n */\n var sliceAt = _makePartial3(slice);\n\n /**\n * Checks if at least one element in an array-like object satisfies the given predicate.
\n * The function will stop calling the predicate as soon as it returns a truthy value.
\n * Note that unlike the native\n * [Array.prototype.some]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some},\n * this function won't skip deleted or unassigned indexes.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12, active: false},\n * {\"name\": \"John\", \"age\": 40, active: false},\n * {\"name\": \"Mario\", \"age\": 17, active: false},\n * {\"name\": \"Paolo\", \"age\": 15, active: false}\n * ];\n * var isAdult = _.keySatisfies(_.isGTE(18), \"age\");\n * var isActive = _.hasKeyValue(\"active\", true);\n *\n * _.someIn(persons, isAdult) // => true\n * _.someIn(persons, isActive) // => false\n *\n * @example Showing the difference with Array.prototype.some:\n * var arr = new Array(5);\n * arr[3] = 99;\n *\n * arr.some(_.isUndefined) // => false\n * _.someIn(arr, _.isUndefined) // => true\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.some|some}\n * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn}\n * @since 0.39.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Boolean}\n */\n var someIn = _makeArrayChecker(false);\n\n /**\n * A curried version of {@link module:lamb.someIn|someIn} that uses the given predicate to\n * build a function waiting for the array-like to act upon.\n * @example\n * var data = [1, 3, 5, 6, 7, 8];\n * var isEven = function (n) { return n % 2 === 0; };\n * var containsEvens = _.some(isEven);\n * var containsStrings = _.some(_.isType(\"String\"));\n *\n * containsEvens(data) // => true\n * containsStrings(data) // => false\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.someIn|someIn}\n * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn}\n * @since 0.39.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var some = _curry2(someIn, true);\n\n lamb.contains = contains;\n lamb.every = every;\n lamb.everyIn = everyIn;\n lamb.filter = filter;\n lamb.filterWith = filterWith;\n lamb.find = find;\n lamb.findIndex = findIndex;\n lamb.findIndexWhere = findIndexWhere;\n lamb.findWhere = findWhere;\n lamb.forEach = forEach;\n lamb.isIn = isIn;\n lamb.list = list;\n lamb.map = map;\n lamb.mapWith = mapWith;\n lamb.reduce = reduce;\n lamb.reduceRight = reduceRight;\n lamb.reduceRightWith = reduceRightWith;\n lamb.reduceWith = reduceWith;\n lamb.reverse = reverse;\n lamb.slice = slice;\n lamb.sliceAt = sliceAt;\n lamb.some = some;\n lamb.someIn = someIn;\n\n /**\n * Accepts a series of functions and builds a function that applies the received\n * arguments to each one and returns the first non-undefined value.
\n * Meant to work in synergy with {@link module:lamb.case|case} and\n * {@link module:lamb.invoker|invoker}, can be useful as a strategy pattern for functions,\n * to mimic conditional logic or pattern matching, and also to build polymorphic functions.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var filterString = _.compose(_.invoker(\"join\", \"\"), _.filter);\n * var filterAdapter = _.adapter([\n * _.invoker(\"filter\"),\n * _.case(_.isType(\"String\"), filterString)\n * ]);\n *\n * filterAdapter([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6]\n * filterAdapter(\"123456\", isEven) // => \"246\"\n * filterAdapter({}, isEven) // => undefined\n *\n * // by its nature is composable\n * var filterWithDefault = _.adapter([filterAdapter, _.always(\"Not implemented\")]);\n *\n * filterWithDefault([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6]\n * filterWithDefault(\"123456\", isEven) // => \"246\"\n * filterWithDefault({}, isEven) // => \"Not implemented\"\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.case|case}\n * @see {@link module:lamb.invoker|invoker}\n * @since 0.6.0\n * @param {Function[]} functions\n * @returns {Function}\n */\n function adapter (functions) {\n if (!Array.isArray(functions)) {\n throw _makeTypeErrorFor(functions, \"array\");\n }\n\n return function () {\n var len = functions.length;\n var result;\n\n for (var i = 0; i < len; i++) {\n result = functions[i].apply(this, arguments);\n\n if (!isUndefined(result)) {\n break;\n }\n }\n\n return result;\n };\n }\n\n /**\n * Accepts an array of predicates and builds a new one that returns true if they are all satisfied\n * by the same arguments. The functions in the array will be applied one at a time until a\n * false value is produced, which is returned immediately.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var isPositiveEven = _.allOf([isEven, _.isGT(0)]);\n *\n * isPositiveEven(-2) // => false\n * isPositiveEven(11) // => false\n * isPositiveEven(6) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.anyOf|anyOf}\n * @since 0.1.0\n * @param {Function[]} predicates\n * @returns {Function}\n */\n var allOf = _checkPredicates(true);\n\n /**\n * Accepts an array of predicates and builds a new one that returns true if at least one of them is\n * satisfied by the received arguments. The functions in the array will be applied one at a time\n * until a true value is produced, which is returned immediately.\n * @example\n * var users = [\n * {id: 1, name: \"John\", group: \"guest\"},\n * {id: 2, name: \"Jane\", group: \"root\"},\n * {id: 3, name: \"Mario\", group: \"admin\"}\n * ];\n * var isInGroup = _.partial(_.hasKeyValue, [\"group\"]);\n * var isSuperUser = _.anyOf([isInGroup(\"admin\"), isInGroup(\"root\")]);\n *\n * isSuperUser(users[0]) // => false\n * isSuperUser(users[1]) // => true\n * isSuperUser(users[2]) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.allOf|allOf}\n * @since 0.1.0\n * @param {Function[]} predicates\n * @returns {Function}\n */\n var anyOf = _checkPredicates(false);\n\n /**\n * Verifies that the two supplied values are the same value using the \"SameValue\" comparison.
\n * Note that this doesn't behave as the strict equality operator, but rather as a shim of ES6's\n * [Object.is]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is}.\n * Differences are that 0 and -0 aren't the same value and, finally,\n * NaN is equal to itself.
\n * See also {@link module:lamb.is|is} for a curried version building a predicate and\n * {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} to perform a \"SameValueZero\"\n * comparison.\n * @example\n * var testObject = {};\n *\n * _.areSame({}, testObject) // => false\n * _.areSame(testObject, testObject) // => true\n * _.areSame(\"foo\", \"foo\") // => true\n * _.areSame(0, -0) // => false\n * _.areSame(0 / 0, NaN) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.is|is}\n * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.50.0\n * @param {*} a\n * @param {*} b\n * @returns {Boolean}\n */\n function areSame (a, b) {\n return a === 0 && b === 0 ? 1 / a === 1 / b : areSVZ(a, b);\n }\n\n /**\n * Verifies that the two supplied values are the same value using the \"SameValueZero\" comparison.
\n * With this comparison NaN is equal to itself, but 0 and -0 are\n * considered the same value.
\n * See also {@link module:lamb.isSVZ|isSVZ} for a curried version building a predicate and\n * {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} to perform a \"SameValue\" comparison.\n * @example\n * var testObject = {};\n *\n * _.areSVZ({}, testObject) // => false\n * _.areSVZ(testObject, testObject) // => true\n * _.areSVZ(\"foo\", \"foo\") // => true\n * _.areSVZ(0, -0) // => true\n * _.areSVZ(0 / 0, NaN) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.isSVZ|isSVZ}\n * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.50.0\n * @param {*} a\n * @param {*} b\n * @returns {Boolean}\n */\n function areSVZ (a, b) {\n return a !== a ? b !== b : a === b; // eslint-disable-line no-self-compare\n }\n\n /**\n * Builds a case for {@link module:lamb.adapter|adapter}.
\n * The function will apply the received arguments to fn if the predicate is satisfied\n * with the same arguments, otherwise will return undefined.
\n * See also {@link module:lamb.condition|condition} to build a condition with two branching functions\n * and {@link module:lamb.unless|unless} and {@link module:lamb.when|when} where one of the branches\n * is the identity function.\n * @example\n * var halveIfNumber = _.case(_.isType(\"Number\"), _.divideBy(2));\n *\n * halveIfNumber(2) // => 1\n * halveIfNumber(\"2\") // => undefined\n *\n * @alias module:lamb.case\n * @category Logic\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.condition|condition}\n * @see {@link module:lamb.unless|unless}\n * @see {@link module:lamb.when|when}\n * @since 0.51.0\n * @param {Function} predicate\n * @param {Function} fn\n * @returns {Function}\n */\n function case_ (predicate, fn) {\n return function () {\n return predicate.apply(this, arguments) ? fn.apply(this, arguments) : void 0;\n };\n }\n\n /**\n * Builds a function that will apply the received arguments to trueFn,\n * if the predicate is satisfied with the same arguments, or to falseFn otherwise.
\n * Although you can use other conditions as trueFn or falseFn,\n * it's probably better to use {@link module:lamb.adapter|adapter} to build more complex behaviours.
\n * See also {@link module:lamb.unless|unless} and {@link module:lamb.when|when} as they are\n * shortcuts to common use cases.\n * @example\n * var isEven = function (n) { return n % 2 === 0};\n * var halveEvenAndDoubleOdd = _.condition(isEven, _.divideBy(2), _.multiplyBy(2));\n *\n * halveEvenAndDoubleOdd(5) // => 10\n * halveEvenAndDoubleOdd(6) // => 3\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.unless|unless}\n * @see {@link module:lamb.when|when}\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.case|case}\n * @since 0.2.0\n * @param {Function} predicate\n * @param {Function} trueFn\n * @param {Function} falseFn\n * @returns {Function}\n */\n function condition (predicate, trueFn, falseFn) {\n return function () {\n return (predicate.apply(this, arguments) ? trueFn : falseFn).apply(this, arguments);\n };\n }\n\n /**\n * Verifies that the first given value is greater than the second.
\n * Wraps the native > operator within a function.\n * @example\n * var pastDate = new Date(2010, 2, 12);\n * var today = new Date();\n *\n * _.gt(today, pastDate) // => true\n * _.gt(pastDate, today) // => false\n * _.gt(3, 4) // => false\n * _.gt(3, 3) // => false\n * _.gt(3, 2) // => true\n * _.gt(0, -0) // => false\n * _.gt(-0, 0) // => false\n * _.gt(\"a\", \"A\") // => true\n * _.gt(\"b\", \"a\") // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.gte|gte}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function gt (a, b) {\n return a > b;\n }\n\n /**\n * Verifies that the first given value is greater than or equal to the second.\n * Regarding equality, beware that this is simply a wrapper for the native\n * >= operator, so -0 === 0.\n * @example\n * _.gte(3, 4) // => false\n * _.gte(3, 3) // => true\n * _.gte(3, 2) // => true\n * _.gte(0, -0) // => true\n * _.gte(-0, 0) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.gt|gt}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function gte (a, b) {\n return a >= b;\n }\n\n /**\n * A curried version of {@link module:lamb.areSame|areSame}.
\n * Accepts a value and builds a predicate that checks whether the value\n * and the one received by the predicate are the same using the \"SameValue\"\n * comparison.
\n * See also {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ}\n * to perform a \"SameValueZero\" comparison.\n * @example\n * var john = {name: \"John\", surname: \"Doe\"};\n * var isJohn = _.is(john);\n * var isNegativeZero = _.is(-0);\n * var isReallyNaN = _.is(NaN);\n *\n * isJohn(john) // => true\n * isJohn({name: \"John\", surname: \"Doe\"}) // => false\n *\n * isNegativeZero(0) // => false\n * isNegativeZero(-0) // => true\n *\n * isNaN(NaN) // => true\n * isNaN(\"foo\") // => true\n *\n * isReallyNaN(NaN) // => true\n * isReallyNaN(\"foo\") // => false\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.areSame|areSame}\n * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.1.0\n * @param {*} value\n * @returns {Function}\n */\n var is = _curry2(areSame);\n\n /**\n * A right curried version of {@link module:lamb.gt|gt}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is greater than the one received by the predicate.\n * @example\n * var isGreaterThan5 = _.isGT(5);\n *\n * isGreaterThan5(3) // => false\n * isGreaterThan5(5) // => false\n * isGreaterThan5(7) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isGT = _curry2(gt, true);\n\n /**\n * A right curried version of {@link module:lamb.gte|gte}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is greater than or equal to the one received by the predicate.\n * @example\n * var isPositiveOrZero = _.isGTE(0);\n *\n * isPositiveOrZero(-3) // => false\n * isPositiveOrZero(-0) // => true\n * isPositiveOrZero(0) // => true\n * isPositiveOrZero(5) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isGT|isGT}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isGTE = _curry2(gte, true);\n\n /**\n * A right curried version of {@link module:lamb.lt|lt}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is less than the one received by the predicate.\n * @example\n * var isLessThan5 = _.isLT(5);\n *\n * isLessThan5(7) // => false\n * isLessThan5(5) // => false\n * isLessThan5(3) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isLT = _curry2(lt, true);\n\n /**\n * A right curried version of {@link module:lamb.lte|lte}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is less than or equal to the one received by the predicate.\n * @example\n * var isNegativeOrZero = _.isLTE(0);\n *\n * isNegativeOrZero(5) // => false\n * isNegativeOrZero(-0) // => true\n * isNegativeOrZero(0) // => true\n * isNegativeOrZero(-3) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isLT|isLT}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isLTE = _curry2(lte, true);\n\n /**\n * A curried version of {@link module:lamb.areSVZ|areSVZ}.
\n * Accepts a value and builds a predicate that checks whether the value\n * and the one received by the predicate are the same using the \"SameValueZero\"\n * comparison.
\n * See also {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is}\n * to perform a \"SameValue\" comparison.\n * @example\n * var john = {name: \"John\", surname: \"Doe\"};\n * var isJohn = _.isSVZ(john);\n * var isZero = _.isSVZ(0);\n * var isReallyNaN = _.isSVZ(NaN);\n *\n * isJohn(john) // => true\n * isJohn({name: \"John\", surname: \"Doe\"}) // => false\n *\n * isZero(0) // => true\n * isZero(-0) // => true\n *\n * isNaN(NaN) // => true\n * isNaN(\"foo\") // => true\n *\n * isReallyNaN(NaN) // => true\n * isReallyNaN(\"foo\") // => false\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.areSVZ|areSVZ}\n * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.1.0\n * @param {*} value\n * @returns {Function}\n */\n var isSVZ = _curry2(areSVZ);\n\n /**\n * Verifies that the first given value is less than the second.
\n * Wraps the native < operator within a function.\n * @example\n * var pastDate = new Date(2010, 2, 12);\n * var today = new Date();\n *\n * _.lt(today, pastDate) // => false\n * _.lt(pastDate, today) // => true\n * _.lt(3, 4) // => true\n * _.lt(3, 3) // => false\n * _.lt(3, 2) // => false\n * _.lt(0, -0) // => false\n * _.lt(-0, 0) // => false\n * _.lt(\"a\", \"A\") // => false\n * _.lt(\"a\", \"b\") // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.lte|lte}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function lt (a, b) {\n return a < b;\n }\n\n /**\n * Verifies that the first given value is less than or equal to the second.\n * Regarding equality, beware that this is simply a wrapper for the native\n * <= operator, so -0 === 0.\n * @example\n * _.lte(3, 4) // => true\n * _.lte(3, 3) // => true\n * _.lte(3, 2) // => false\n * _.lte(0, -0) // => true\n * _.lte(-0, 0) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.lt|lt}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function lte (a, b) {\n return a <= b;\n }\n\n /**\n * Returns a predicate that negates the given one.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var isOdd = _.not(isEven);\n *\n * isOdd(5) // => true\n * isOdd(4) // => false\n *\n * @memberof module:lamb\n * @category Logic\n * @since 0.1.0\n * @param {Function} predicate\n * @returns {Function}\n */\n function not (predicate) {\n return function () {\n return !predicate.apply(this, arguments);\n };\n }\n\n /**\n * Builds a unary function that will check its argument against the given predicate.\n * If the predicate isn't satisfied, the provided fn function will be\n * applied to the same value. The received argument is returned as it is otherwise.
\n * See {@link module:lamb.when|when} for the opposite behaviour.
\n * It's a shortcut for a common use case of {@link module:lamb.condition|condition},\n * where its trueFn parameter is the [identity function]{@link module:lamb.identity}.\n * @example\n * var isEven = function (n) { return n % 2 === 0};\n * var halveUnlessIsEven = _.unless(isEven, _.divideBy(2));\n *\n * halveUnlessIsEven(5) // => 2.5\n * halveUnlessIsEven(6) // => 6\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.condition|condition}\n * @see {@link module:lamb.when|when}\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.case|case}\n * @since 0.42.0\n * @param {Function} predicate\n * @param {Function} fn\n * @returns {Function}\n */\n function unless (predicate, fn) {\n return function (value) {\n return predicate.call(this, value) ? value : fn.call(this, value);\n };\n }\n\n /**\n * Builds a unary function that will check its argument against the given predicate.\n * If the predicate is satisfied, the provided fn function will be\n * applied to the same value. The received argument is returned as it is otherwise.
\n * See {@link module:lamb.unless|unless} for the opposite behaviour.
\n * It's a shortcut for a common use case of {@link module:lamb.condition|condition},\n * where its falseFn parameter is the [identity function]{@link module:lamb.identity}.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var halveIfEven = _.when(isEven, _.divideBy(2));\n *\n * halveIfEven(5) // => 5\n * halveIfEven(6) // => 3\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.condition|condition}\n * @see {@link module:lamb.unless|unless}\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.case|case}\n * @since 0.42.0\n * @param {Function} predicate\n * @param {Function} fn\n * @returns {Function}\n */\n function when (predicate, fn) {\n return function (value) {\n return predicate.call(this, value) ? fn.call(this, value) : value;\n };\n }\n\n lamb.adapter = adapter;\n lamb.allOf = allOf;\n lamb.anyOf = anyOf;\n lamb.areSame = areSame;\n lamb.areSVZ = areSVZ;\n lamb.case = case_;\n lamb.condition = condition;\n lamb.gt = gt;\n lamb.gte = gte;\n lamb.is = is;\n lamb.isGT = isGT;\n lamb.isGTE = isGTE;\n lamb.isLT = isLT;\n lamb.isLTE = isLTE;\n lamb.isSVZ = isSVZ;\n lamb.lt = lt;\n lamb.lte = lte;\n lamb.not = not;\n lamb.unless = unless;\n lamb.when = when;\n\n /**\n * A curried version of {@link module:lamb.sum|sum}.\n * @example\n * var add5 = _.add(5);\n *\n * _.add5(4) // => 9\n * _.add5(-2) // => 3\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.sum|sum}\n * @since 0.1.0\n * @param {Number} a\n * @returns {Function}\n */\n var add = _curry2(sum, true);\n\n /**\n * \"Clamps\" a number within the given limits, both included.
\n * The function will convert to number all its parameters before starting any\n * evaluation, and will return NaN if min is greater\n * than max.\n * @example\n * _.clamp(-5, 0, 10) // => 0\n * _.clamp(5, 0, 10) // => 5\n * _.clamp(15, 0, 10) // => 10\n * _.clamp(0, 0, 10) // => 0\n * _.clamp(10, 0, 10) // => 10\n * _.is(_.clamp(-0, 0, 10), -0) // => true\n * _.clamp(10, 20, 15) // => NaN\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.clampWithin|clampWithin}\n * @since 0.13.0\n * @param {Number} n\n * @param {Number} min\n * @param {Number} max\n * @returns {Number}\n */\n function clamp (n, min, max) {\n n = +n;\n min = +min;\n max = +max;\n\n if (min > max) {\n return NaN;\n } else {\n return n < min ? min : n > max ? max : n;\n }\n }\n\n /**\n * A curried version of {@link module:lamb.clamp|clamp}, expecting a min\n * and a max value, that builds a function waiting for the number to clamp.\n * @example\n * _.clampWithin(0, 10)(-5) // => 0\n * _.clampWithin(0, 10)(5) // => 5\n * _.clampWithin(0, 10)(15) // => 10\n * _.clampWithin(0, 10)(0) // => 0\n * _.clampWithin(0, 10)(10) // => 10\n * _.is(_.clampWithin(0, 10)(-0), -0) // => true\n * _.clampWithin(20, 15)(10) // => NaN\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.clamp|clamp}\n * @since 0.47.0\n * @param {Number} min\n * @param {Number} max\n * @returns {Function}\n */\n var clampWithin = _makePartial3(clamp);\n\n /**\n * A curried version of {@link module:lamb.subtract|subtract} that expects the\n * subtrahend to build a function waiting for the minuend.\n * @example\n * var deduct5 = _.deduct(5);\n *\n * deduct5(12) // => 7\n * deduct5(3) // => -2\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.subtract|subtract}\n * @since 0.50.0\n * @param {Number} a\n * @returns {Function}\n */\n var deduct = _curry2(subtract, true);\n\n /**\n * Divides two numbers.\n * @example\n * _.divide(5, 2) // => 2.5\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.divideBy|divideBy}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function divide (a, b) {\n return a / b;\n }\n\n /**\n * A curried version of {@link module:lamb.divide|divide} that expects a divisor to\n * build a function waiting for the dividend.\n * @example\n * var halve = divideBy(2);\n *\n * halve(10) // => 5\n * halve(5) // => 2.5\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.divide|divide}\n * @since 0.50.0\n * @param {Number} a\n * @returns {Function}\n */\n var divideBy = _curry2(divide, true);\n\n /**\n * Generates a sequence of values of the desired length with the provided iteratee.\n * The values being iterated, and received by the iteratee, are the results generated so far.\n * @example\n * var fibonacci = function (n, idx, results) {\n * return n + (results[idx - 1] || 0);\n * };\n *\n * _.generate(1, 10, fibonacci) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.range|range}\n * @since 0.21.0\n * @param {*} start - The starting value\n * @param {Number} len - The desired length for the sequence\n * @param {ListIteratorCallback} iteratee\n * @returns {Array}\n */\n function generate (start, len, iteratee) {\n var result = [start];\n\n for (var i = 0, limit = len - 1; i < limit; i++) {\n result.push(iteratee(result[i], i, result));\n }\n\n return result;\n }\n\n /**\n * Verifies whether the received value is a finite number.
\n * Behaves almost as a shim of ES6's [Number.isFinite]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite},\n * but with a difference: it will return true even for Number object's instances.\n * @example\n * _.isFinite(5) // => true\n * _.isFinite(new Number(5)) // => true\n * _.isFinite(Infinity) // => false\n * _.isFinite(-Infinity) // => false\n * _.isFinite(\"5\") // => false\n * _.isFinite(NaN) // => false\n * _.isFinite(null) // => false\n *\n * @alias module:lamb.isFinite\n * @category Math\n * @since 0.46.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isFinite_ (value) {\n return type(value) === \"Number\" && isFinite(value);\n }\n\n /**\n * Verifies whether the received value is a number and an integer.\n * Behaves almost as a shim of ES6's [Number.isInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger},\n * but with a difference: it will return true even for Number object's instances.\n * @example\n * _.isInteger(5) // => true\n * _.isInteger(new Number(5)) // => true\n * _.isInteger(2.5) // => false\n * _.isInteger(Infinity) // => false\n * _.isInteger(-Infinity) // => false\n * _.isInteger(\"5\") // => false\n * _.isInteger(NaN) // => false\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.isSafeInteger|isSafeInteger}\n * @since 0.46.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isInteger (value) {\n return type(value) === \"Number\" && value % 1 === 0;\n }\n\n /**\n * Verifies whether the received value is a \"safe integer\", meaning that is a number and that\n * can be exactly represented as an IEEE-754 double precision number.\n * The safe integers consist of all integers from -(253 - 1) inclusive to\n * 253 - 1 inclusive.
\n * Behaves almost as a shim of ES6's [Number.isSafeInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger},\n * but with a difference: it will return true even for Number object's instances.\n * @example\n * _.isSafeInteger(5) // => true\n * _.isSafeInteger(new Number(5)) // => true\n * _.isSafeInteger(Math.pow(2, 53) - 1) // => true\n * _.isSafeInteger(Math.pow(2, 53)) // => false\n * _.isSafeInteger(2e32) // => false\n * _.isSafeInteger(2.5) // => false\n * _.isSafeInteger(Infinity) // => false\n * _.isSafeInteger(-Infinity) // => false\n * _.isSafeInteger(\"5\") // => false\n * _.isSafeInteger(NaN) // => false\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.isInteger|isInteger}\n * @since 0.46.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isSafeInteger (value) {\n return isInteger(value) && Math.abs(value) <= 9007199254740991;\n }\n\n /**\n * Performs the modulo operation and should not be confused with the\n * {@link module:lamb.remainder|remainder}.\n * The function performs a floored division to calculate the result and not\n * a truncated one, hence the sign of the dividend is not kept, unlike the\n * {@link module:lamb.remainder|remainder}.\n * @example\n * _.modulo(5, 3) // => 2\n * _.remainder(5, 3) // => 2\n *\n * _.modulo(-5, 3) // => 1\n * _.remainder(-5, 3) // => -2\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.remainder|remainder}\n * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function modulo (a, b) {\n return a - (b * Math.floor(a / b));\n }\n\n /**\n * Multiplies two numbers.\n * @example\n * _.multiply(5, 3) // => 15\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.multiplyBy|multiplyBy}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function multiply (a, b) {\n return a * b;\n }\n\n /**\n * A curried version of {@link module:lamb.multiply|multiply}.\n * @example\n * var double = _.multiplyBy(2);\n *\n * double(5) // => 10\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.multiply|multiply}\n * @since 0.50.0\n * @param {Number} a\n * @returns {Function}\n */\n var multiplyBy = _curry2(multiply, true);\n\n /**\n * Generates a random integer between two given integers, both included.\n * Note that no safety measure is taken if the provided arguments aren't integers, so\n * you may end up with unexpected (not really) results.\n * For example randomInt(0.1, 1.2) could be 2.\n * @example\n *\n * _.randomInt(1, 10) // => an integer >=1 && <= 10\n *\n * @memberof module:lamb\n * @category Math\n * @since 0.1.0\n * @param {Number} min\n * @param {Number} max\n * @returns {Number}\n */\n function randomInt (min, max) {\n return Math.floor(Math.random() * (max - min + 1) + min);\n }\n\n /**\n * Generates an arithmetic progression of numbers starting from start up to,\n * but not including, limit, using the given step.\n * @example\n * _.range(2, 10) // => [2, 3, 4, 5, 6, 7, 8, 9]\n * _.range(1, -10, -2) // => [1, -1, -3, -5, -7, -9]\n * _.range(0, 3, 1) // => [0, 1, 2]\n * _.range(-0, 3, 1) // => [-0, 1, 2]\n * _.range(1, -10, 2) // => []\n * _.range(3, 5, -1) // => []\n *\n * @example Behaviour if step happens to be zero:\n * _.range(2, 10, 0) // => [2]\n * _.range(2, -10, 0) // => [2]\n * _.range(2, 2, 0) // => []\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.generate|generate}\n * @since 0.1.0\n * @param {Number} start\n * @param {Number} limit\n * @param {Number} [step=1]\n * @returns {Number[]}\n */\n function range (start, limit, step) {\n start = _forceToNumber(start);\n limit = _forceToNumber(limit);\n step = arguments.length === 3 ? _forceToNumber(step) : 1;\n\n if (step === 0) {\n return limit === start ? [] : [start];\n }\n\n var len = Math.max(Math.ceil((limit - start) / step), 0);\n var result = Array(len);\n\n for (var i = 0, last = start; i < len; i++) {\n result[i] = last;\n last += step;\n }\n\n return result;\n }\n\n /**\n * Gets the remainder of the division of two numbers.\n * Not to be confused with the {@link module:lamb.modulo|modulo} as the remainder\n * keeps the sign of the dividend and may lead to some unexpected results.\n * @example\n * // example of wrong usage of the remainder\n * // (in this case the modulo operation should be used)\n * var isOdd = function (n) { return _.remainder(n, 2) === 1; };\n * isOdd(-3) // => false as -3 % 2 === -1\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.modulo|modulo}\n * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function remainder (a, b) {\n return a % b;\n }\n\n /**\n * Subtracts two numbers.\n * @example\n * _.subtract(5, 3) // => 2\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.deduct|deduct}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function subtract (a, b) {\n return a - b;\n }\n\n /**\n * Sums two numbers.\n * @example\n * _.sum(4, 5) // => 9\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.add|add}\n * @since 0.50.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function sum (a, b) {\n return a + b;\n }\n\n lamb.add = add;\n lamb.clamp = clamp;\n lamb.clampWithin = clampWithin;\n lamb.deduct = deduct;\n lamb.divide = divide;\n lamb.divideBy = divideBy;\n lamb.generate = generate;\n lamb.isFinite = isFinite_;\n lamb.isInteger = isInteger;\n lamb.isSafeInteger = isSafeInteger;\n lamb.modulo = modulo;\n lamb.multiply = multiply;\n lamb.multiplyBy = multiplyBy;\n lamb.randomInt = randomInt;\n lamb.range = range;\n lamb.remainder = remainder;\n lamb.subtract = subtract;\n lamb.sum = sum;\n\n /**\n * Accepts a constructor and builds a predicate expecting an object,\n * which will be tested to verify whether the prototype of the constructor\n * is in its prototype chain.
\n * Wraps in a convenient way the native\n * [instanceof]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof} operator.\n * @example\n * function SomeObjA () {}\n *\n * var a = new SomeObjA();\n * var sObj = new String(\"foo\");\n * var s = \"foo\";\n *\n * _.isInstanceOf(Object)(a) // => true\n * _.isInstanceOf(SomeObjA)(a) // => true\n *\n * _.isInstanceOf(Object)(sObj) // => true\n * _.isInstanceOf(String)(sObj) // => true\n *\n * _.isInstanceOf(Object)(s) // => false\n * _.isInstanceOf(String)(s) // => false\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isType|isType}\n * @since 0.47.0\n * @param {*} constructor\n * @returns {Function}\n */\n function isInstanceOf (constructor) {\n return function (obj) {\n return obj instanceof constructor;\n };\n }\n\n /**\n * Verifies if a value is null or undefined.\n * @example\n * _.isNil(NaN) // => false\n * _.isNil({}) // => false\n * _.isNil(null) // => true\n * _.isNil(void 0) // => true\n * _.isNil() // => true\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isNull|isNull}\n * @see {@link module:lamb.isUndefined|isUndefined}\n * @since 0.1.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isNil (value) {\n return isNull(value) || isUndefined(value);\n }\n\n /**\n * Verifies if a value is null.\n * @example\n * _.isNull(null) // => true\n * _.isNull(void 0) // => false\n * _.isNull(false) // => false\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isNil|isNil} if you want to check for undefined too.\n * @since 0.1.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isNull (value) {\n return value === null;\n }\n\n /**\n * Builds a predicate that expects a value to check against the specified type.\n * @example\n * var isString = _.isType(\"String\");\n *\n * isString(\"Hello\") // => true\n * isString(new String(\"Hi\")) // => true\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.type|type}\n * @since 0.1.0\n * @param {String} typeName\n * @returns {Function}\n */\n function isType (typeName) {\n return function (value) {\n return type(value) === typeName;\n };\n }\n\n /**\n * Verifies if a value is undefined.\n * @example\n * _.isUndefined(null) // => false\n * _.isUndefined(void 0) // => true\n * _.isUndefined(false) // => false\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isNil|isNil} if you want to check for null too.\n * @since 0.1.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isUndefined (value) {\n return value === void 0;\n }\n\n /**\n * Retrieves the \"type tag\" from the given value.\n * @example\n * var x = 5;\n * var y = new Number(5);\n *\n * typeof x // => \"number\"\n * typeof y // => \"object\"\n * _.type(x) // => \"Number\"\n * _.type(y) // => \"Number\"\n *\n * _.type(Object.prototype.toString) // => \"Function\"\n * _.type(/a/) // => \"RegExp\"\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isType|isType}\n * @since 0.9.0\n * @param {*} value\n * @returns {String}\n */\n function type (value) {\n return _objectProto.toString.call(value).slice(8, -1);\n }\n\n lamb.isInstanceOf = isInstanceOf;\n lamb.isNil = isNil;\n lamb.isNull = isNull;\n lamb.isType = isType;\n lamb.isUndefined = isUndefined;\n lamb.type = type;\n\n /**\n * A curried version of {@link module:lamb.getIndex|getIndex} that uses the provided index\n * to build a function expecting the array-like object holding the element we want to retrieve.\n * @example\n * var getFifthElement = _.getAt(4);\n *\n * getFifthElement([1, 2, 3, 4, 5]) // => 5\n * getFifthElement(\"foo bar\") // => \"b\"\n * getFifthElement([]) // => undefined\n * getFifthElement(\"foo\") // => undefined\n *\n * @example Using negative indexes:\n * _.getAt(-2)([1, 2, 3]) // => 2\n * _.getAt(-3)(\"foo\") // => \"f\"\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @since 0.16.0\n * @see {@link module:lamb.getIndex|getIndex}\n * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts.\n * @param {Number} index\n * @returns {Function}\n */\n var getAt = _curry2(getIndex, true);\n\n /**\n * Returns the value of the object property with the given key.\n * @example\n * var user = {name: \"John\"};\n *\n * _.getIn(user, \"name\") // => \"John\";\n * _.getIn(user, \"surname\") // => undefined\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.getKey|getKey}\n * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn}\n * @since 0.18.0\n * @param {Object} obj\n * @param {String} key\n * @returns {*}\n */\n function getIn (obj, key) {\n return obj[key];\n }\n\n /**\n * Retrieves the element at the given index in an array-like object.
\n * Like {@link module:lamb.slice|slice} the index can be negative.
\n * If the index isn't supplied, or if its value isn't an integer within the array-like bounds,\n * the function will return undefined.
\n * getIndex will throw an exception when receives null or\n * undefined in place of an array-like object, but returns undefined\n * for any other value.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.getIndex(arr, 1) // => 2\n * _.getIndex(arr, -1) // => 5\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.getAt|getAt}\n * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts.\n * @since 0.23.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @returns {*}\n */\n function getIndex (arrayLike, index) {\n var idx = _toNaturalIndex(index, _toArrayLength(arrayLike.length));\n\n return idx === idx ? arrayLike[idx] : void 0; // eslint-disable-line no-self-compare\n }\n\n /**\n * A curried version of {@link module:lamb.getIn|getIn}.
\n * Receives a property name and builds a function expecting the object from which we want to retrieve\n * the property.\n * @example\n * var user1 = {name: \"john\"};\n * var user2 = {name: \"jane\"};\n * var getName = _.getKey(\"name\");\n *\n * getName(user1) // => \"john\"\n * getName(user2) // => \"jane\"\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.getIn|getIn}\n * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn}\n * @since 0.1.0\n * @param {String} key\n * @returns {Function}\n */\n var getKey = _curry2(getIn, true);\n\n /**\n * Builds a partial application of {@link module:lamb.getPathIn|getPathIn} with the given\n * path and separator, expecting the object to act upon.
\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * \"user.name\": \"jdoe\",\n * password: \"abc123\"\n * }\n * };\n *\n * var getPwd = _.getPath(\"login.password\");\n * var getUsername = _.getPath(\"login/user.name\", \"/\");\n *\n * getPwd(user) // => \"abc123\";\n * getUsername(user) // => \"jdoe\"\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.getPathIn|getPathIn}\n * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey}\n * @since 0.19.0\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n var getPath = _makePartial3(getPathIn);\n\n /**\n * Gets a nested property value from an object using the given path.
\n * The path is a string with property names separated by dots by default, but\n * it can be customised with the optional third parameter.
\n * You can use integers in the path, even negative ones, to refer to array-like\n * object indexes, but the priority will be given to existing object keys:\n * the last example explains this particular case.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * \"user.name\": \"jdoe\",\n * password: \"abc123\"\n * },\n * scores: [\n * {id: 1, value: 10},\n * {id: 2, value: 20},\n * {id: 3, value: 30}\n * ]\n * };\n *\n * _.getPathIn(user, \"name\") // => \"John\"\n * _.getPathIn(user, \"login.password\") // => \"abc123\";\n * _.getPathIn(user, \"login/user.name\", \"/\") // => \"jdoe\"\n * _.getPathIn(user, \"name.foo\") // => undefined\n * _.getPathIn(user, \"name.foo.bar\") // => undefined\n *\n * @example Accessing array-like objects indexes:\n * _.getPathIn(user, \"login.password.1\") // => \"b\"\n * _.getPathIn(user, \"scores.0\") // => {id: 1, value: 10}\n * _.getPathIn(user, \"scores.-1.value\") // => 30\n *\n * @example Priority will be given to existing object keys over indexes:\n * _.getPathIn(user, \"scores.-1\") // => {id: 3, value: 30}\n *\n * // let's do something funny\n * user.scores[\"-1\"] = \"foo bar\";\n *\n * _.getPathIn(user, \"scores.-1\") // => \"foo bar\";\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.getPath|getPath}\n * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey}\n * @since 0.19.0\n * @param {Object|ArrayLike} obj\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {*}\n */\n function getPathIn (obj, path, separator) {\n return _getPathInfo(obj, _toPathParts(path, separator), true).target;\n }\n\n /**\n * Retrieves the first element of an array-like object.
\n * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience.\n * @example\n * _.head([1, 2, 3]) // => 1\n * _.head(\"hello\") // => \"h\"\n * _.head([]) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.last|last}\n * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {*}\n */\n var head = getAt(0);\n\n /**\n * Retrieves the last element of an array-like object.
\n * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience.\n * @example\n * _.last([1, 2, 3]) // => 3\n * _.last(\"hello\") // => \"o\"\n * _.last([]) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.head|head}\n * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {*}\n */\n var last = getAt(-1);\n\n /**\n * A curried version of {@link module:lamb.setIndex|setIndex} that builds\n * a function that creates a copy of an array-like object with the given\n * index changed to the desired value.
\n * If the index is not an integer or if it's out of bounds, the function\n * will return a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.setAt(2, 99)(arr) // => [1, 2, 99, 4, 5]\n * arr // => [1, 2, 3, 4, 5]\n *\n * _.setAt(10, 99)(arr) // => [1, 2, 3, 4, 5] (not a reference to `arr`)\n *\n * @example Using negative indexes:\n * _.setAt(-1, 99)(arr) // => [1, 2, 3, 4, 99]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.setIndex|setIndex}\n * @since 0.17.0\n * @param {Number} index\n * @param {*} value\n * @returns {Function}\n */\n var setAt = _makePartial3(_setIndex);\n\n /**\n * Sets the specified key to the given value in a copy of the provided object.
\n * All the remaining enumerable keys of the source object will be simply copied in the\n * result object without breaking references.
\n * If the specified key is not part of the source object, it will be added to the\n * result.
\n * The main purpose of the function is to work on simple plain objects used as\n * data structures, such as JSON objects, and makes no effort to play nice with\n * objects created from an OOP perspective (it's not worth it).
\n * For example the prototype of the result will be Object's regardless\n * of the source's one.\n * @example\n * var user = {name: \"John\", surname: \"Doe\", age: 30};\n *\n * _.setIn(user, \"name\", \"Jane\") // => {name: \"Jane\", surname: \"Doe\", age: 30}\n * _.setIn(user, \"gender\", \"male\") // => {name: \"John\", surname: \"Doe\", age: 30, gender: \"male\"}\n *\n * // `user` still is {name: \"John\", surname: \"Doe\", age: 30}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.setKey|setKey}\n * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn}\n * @since 0.18.0\n * @param {Object} source\n * @param {String} key\n * @param {*} value\n * @returns {Object}\n */\n function setIn (source, key, value) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n return _setIn(source, key, value);\n }\n\n /**\n * Creates a copy of an array-like object with the given index changed to\n * the desired value.
\n * If the index is not an integer or if it's out of bounds, the function\n * will return a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [1, 2, 3];\n *\n * _.setIndex(arr, 1, 99) // => [1, 99, 3]\n * _.setIndex(arr, -1, 99) // => [1, 2, 99]\n * _.setIndex(arr, 10, 99) // => [1, 2, 3] (not a reference to `arr`)\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.setAt|setAt}\n * @since 0.23.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @param {*} value\n * @returns {Array}\n */\n var setIndex = aritize(_setIndex, 3);\n\n /**\n * Builds a partial application of {@link module:lamb.setIn|setIn} with the provided\n * key and value.
\n * The resulting function expects the object to act upon.
\n * Please refer to {@link module:lamb.setIn|setIn}'s description for explanations about\n * how the copy of the source object is made.\n * @example\n * var user = {name: \"John\", surname: \"Doe\", age: 30};\n * var setAgeTo40 = _.setKey(\"age\", 40);\n *\n * setAgeTo40(user) // => {name: \"john\", surname: \"doe\", age: 40}\n *\n * // `user` still is {name: \"John\", surname: \"Doe\", age: 30}\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.setIn|setIn}\n * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn}\n * @since 0.18.0\n * @param {String} key\n * @param {*} value\n * @returns {Function}\n */\n var setKey = _makePartial3(setIn);\n\n /**\n * Builds a partial application of {@link module:lamb.setPathIn|setPathIn} expecting the\n * object to act upon.
\n * See {@link module:lamb.setPathIn|setPathIn} for more details and examples.\n * @example\n * var user = {id: 1, status: {active: false}};\n * var activate = _.setPath(\"status.active\", true);\n *\n * activate(user) // => {id: 1, status: {active: true}}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.setPathIn|setPathIn}\n * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey}\n * @since 0.20.0\n * @param {String} path\n * @param {*} value\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function setPath (path, value, separator) {\n return function (source) {\n return setPathIn(source, path, value, separator);\n };\n }\n\n /**\n * Allows to change a nested value in a copy of the provided object.
\n * The function will delegate the \"set action\" to {@link module:lamb.setIn|setIn} or\n * {@link module:lamb.setAt|setAt} depending on the value encountered in the path,\n * so please refer to the documentation of those functions for specifics about the\n * implementation.
\n * Note anyway that the distinction will be between Arrays, delegated\n * to {@link module:lamb.setAt|setAt}, and everything else (including array-like objects),\n * which will be delegated to {@link module:lamb.setIn|setIn}.
\n * As a result of that, array-like objects will be converted to objects having numbers as keys\n * and paths targeting non-object values will be converted to empty objects.
\n * You can anyway target array elements using integers in the path, even negative ones, but\n * the priority will be given to existing, and enumerable, object keys.
\n * Non-enumerable properties encountered in the path will be considered as non-existent properties.
\n * Like {@link module:lamb.getPathIn|getPathIn} or {@link module:lamb.getPath|getPath} you can\n * use custom path separators.\n * @example\n * var user = {id: 1, status: {active : false, scores: [2, 4, 6]}};\n *\n * _.setPathIn(user, \"status.active\", true) // => {id: 1, status: {active : true, scores: [2, 4, 6]}}\n *\n * @example Targeting arrays:\n * _.setPathIn(user, \"status.scores.0\", 8) // => {id: 1, status: {active : false, scores: [8, 4, 6]}}\n *\n * // you can use negative indexes as well\n * _.setPathIn(user, \"status.scores.-1\", 8) // => {id: 1, status: {active : false, scores: [2, 4, 8]}}\n *\n * @example Arrays can also be part of the path and not necessarily its target:\n * var user = {id: 1, scores: [\n * {value: 2, year: \"2000\"},\n * {value: 4, year: \"2001\"},\n * {value: 6, year: \"2002\"}\n * ]};\n *\n * var newUser = _.setPathIn(user, \"scores.0.value\", 8);\n * // \"newUser\" holds:\n * // {id: 1, scores: [\n * // {value: 8, year: \"2000\"},\n * // {value: 4, year: \"2001\"},\n * // {value: 6, year: \"2002\"}\n * // ]}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.setPath|setPath}\n * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey}\n * @since 0.20.0\n * @param {Object|Array} source\n * @param {String} path\n * @param {*} value\n * @param {String} [separator=\".\"]\n * @returns {Object|Array}\n */\n function setPathIn (source, path, value, separator) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n return _setPathIn(source, _toPathParts(path, separator), value);\n }\n\n /**\n * Builds a function that creates a copy of an array-like object with the given index\n * changed by applying the provided function to its value.
\n * If the index is not an integer or if it's out of bounds, the function will return\n * a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [\"a\", \"b\", \"c\"];\n * var toUpperCase = _.invoker(\"toUpperCase\");\n *\n * _.updateAt(1, toUpperCase)(arr) // => [\"a\", \"B\", \"c\"]\n * _.updateAt(-1, toUpperCase)(arr) // => [\"a\", \"b\", \"C\"]\n * _.updateAt(10, toUpperCase)(arr) // => [\"a\", \"b\", \"c\"] (not a reference to `arr`)\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.updateIndex|updateIndex}\n * @since 0.22.0\n * @param {Number} index\n * @param {Function} updater\n * @returns {Function}\n */\n function updateAt (index, updater) {\n return function (arrayLike) {\n return _setIndex(arrayLike, index, null, updater);\n };\n }\n\n /**\n * Creates a copy of the given object having the desired key value updated by applying\n * the provided function to it.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setIn|setIn}; a copy of the\n * source is returned otherwise.\n * @example\n * var user = {name: \"John\", visits: 2};\n * var toUpperCase = _.invoker(\"toUpperCase\");\n *\n * _.updateIn(user, \"name\", toUpperCase) // => {name: \"JOHN\", visits: 2}\n * _.updateIn(user, \"surname\", toUpperCase) // => {name: \"John\", visits: 2}\n *\n * @example Non-enumerable properties will be treated as non-existent:\n * var user = Object.create({name: \"John\"}, {visits: {value: 2}});\n *\n * _.updateIn(user, \"visits\", _.add(1)) // => {name: \"John\", visits: 2}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.updateKey|updateKey}\n * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn}\n * @since 0.22.0\n * @param {Object} source\n * @param {String} key\n * @param {Function} updater\n * @returns {Object}\n */\n function updateIn (source, key, updater) {\n return _isEnumerable(source, key) ?\n _setIn(source, key, updater(source[key])) :\n _merge(enumerables, source, {});\n }\n\n /**\n * Creates a copy of an array-like object with the given index changed by applying the\n * provided function to its value.
\n * If the index is not an integer or if it's out of bounds, the function will return\n * a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [\"a\", \"b\", \"c\"];\n * var toUpperCase = _.invoker(\"toUpperCase\");\n *\n * _.updateIndex(arr, 1, toUpperCase) // => [\"a\", \"B\", \"c\"]\n * _.updateIndex(arr, -1, toUpperCase) // => [\"a\", \"b\", \"C\"]\n * _.updateIndex(arr, 10, toUpperCase) // => [\"a\", \"b\", \"c\"] (not a reference to `arr`)\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.updateAt|updateAt}\n * @since 0.23.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @param {Function} updater\n * @returns {Array}\n */\n var updateIndex = partial(_setIndex, [_, _, null, _]);\n\n /**\n * Builds a partial application of {@link module:lamb.updateIn|updateIn} with the provided\n * key and updater, expecting the object to act upon.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setIn|setIn}; a copy of the\n * source is returned otherwise.\n * @example\n * var user = {name: \"John\", visits: 2};\n * var incrementVisits = _.updateKey(\"visits\", _.add(1));\n *\n * incrementVisits(user) // => {name: \"John\", visits: 3}\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.updateIn|updateIn}\n * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn}\n * @since 0.22.0\n * @param {String} key\n * @param {Function} updater\n * @returns {Function}\n */\n var updateKey = _makePartial3(updateIn);\n\n /**\n * Builds a partial application of {@link module:lamb.updatePathIn|updatePathIn}\n * expecting the object to act upon.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setPathIn|setPathIn}; a copy of the\n * source is returned otherwise.
\n * Like the other \"path\" functions, negative indexes can be used to access array elements, but\n * the priority will be given to existing, and enumerable, object keys.\n * @example\n * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}};\n * var incrementScores = _.updatePath(\"status.scores\", _.mapWith(_.add(1)))\n *\n * incrementScores(user) // => {id: 1, status: {scores: [3, 5, 7], visits: 0}}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.updatePathIn|updatePathIn}\n * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey}\n * @since 0.24.0\n * @param {String} path\n * @param {Function} updater\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function updatePath (path, updater, separator) {\n return function (source) {\n return updatePathIn(source, path, updater, separator);\n };\n }\n\n /**\n * Allows to change a nested value in a copy of the given object by applying the provided\n * function to it.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setPathIn|setPathIn}; a copy of the\n * source is returned otherwise.
\n * Like the other \"path\" functions, negative indexes can be used to access array elements, but\n * the priority will be given to existing, and enumerable, object keys.\n * @example\n * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}};\n * var inc = _.add(1);\n *\n * _.updatePathIn(user, \"status.visits\", inc) // => {id: 1, status: {scores: [2, 4, 6]}, visits: 1}\n *\n * @example Targeting arrays:\n * _.updatePathIn(user, \"status.scores.0\", inc) // => {id: 1, status: {scores: [3, 4, 6], visits: 0}}\n *\n * // you can use negative indexes as well\n * _.updatePathIn(user, \"status.scores.-1\", inc) // => {id: 1, status: {scores: [2, 4, 7], visits: 0}}\n *\n * @example Arrays can also be part of the path and not necessarily its target:\n * var user = {id: 1, scores: [\n * {value: 2, year: \"2000\"},\n * {value: 4, year: \"2001\"},\n * {value: 6, year: \"2002\"}\n * ]};\n *\n * var newUser = _.updatePathIn(user, \"scores.0.value\", inc);\n * // \"newUser\" holds:\n * // {id: 1, scores: [\n * // {value: 3, year: \"2000\"},\n * // {value: 4, year: \"2001\"},\n * // {value: 6, year: \"2002\"}\n * // ]}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.updatePath|updatePath}\n * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey}\n * @since 0.24.0\n * @param {Object|Array} source\n * @param {String} path\n * @param {Function} updater\n * @param {String} [separator=\".\"]\n * @returns {Object|Array}\n */\n function updatePathIn (source, path, updater, separator) {\n var parts = _toPathParts(path, separator);\n var pathInfo = _getPathInfo(source, parts, false);\n\n if (pathInfo.isValid) {\n return _setPathIn(source, parts, updater(pathInfo.target));\n } else {\n return Array.isArray(source) ? slice(source, 0, source.length) : _merge(enumerables, source, {});\n }\n }\n\n lamb.getAt = getAt;\n lamb.getIn = getIn;\n lamb.getIndex = getIndex;\n lamb.getKey = getKey;\n lamb.getPath = getPath;\n lamb.getPathIn = getPathIn;\n lamb.head = head;\n lamb.last = last;\n lamb.setAt = setAt;\n lamb.setIn = setIn;\n lamb.setIndex = setIndex;\n lamb.setKey = setKey;\n lamb.setPath = setPath;\n lamb.setPathIn = setPathIn;\n lamb.updateAt = updateAt;\n lamb.updateIn = updateIn;\n lamb.updateIndex = updateIndex;\n lamb.updateKey = updateKey;\n lamb.updatePath = updatePath;\n lamb.updatePathIn = updatePathIn;\n\n /**\n * A curried version of {@link module:lamb.appendTo|appendTo} that uses the value to append\n * to build a function expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4];\n *\n * _.append(5)(arr) // => [1, 2, 3, 4, 5]\n * _.append([5])(arr) // => [1, 2, 3, 4, [5]]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.appendTo|appendTo}\n * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt}\n * @since 0.44.0\n * @param {*} value\n * @returns {Function}\n */\n var append = _curry2(appendTo, true);\n\n /**\n * Appends the given value at the end of a copy of the provided array-like object.\n * @example\n * var arr = [1, 2, 3, 4];\n *\n * _.appendTo(arr, 5) // => [1, 2, 3, 4, 5]\n * _.appendTo(arr, [5]) // => [1, 2, 3, 4, [5]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.append|append}\n * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt}\n * @since 0.44.0\n * @param {ArrayLike} arrayLike\n * @param {*} value\n * @returns {Array}\n */\n function appendTo (arrayLike, value) {\n return slice(arrayLike, 0, arrayLike.length).concat([value]);\n }\n\n /**\n * Returns an array of unique items present only in the first of the two given\n * array-like objects. To determine uniqueness the function uses the\n * [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var a1 = [1, 2, 1, 3, 4];\n * var a2 = [2, 4, 5, 6];\n * var a3 = [3, 4, 5, 2, 1];\n *\n * _.difference(a1, a2) // => [1, 3]\n * _.difference(a2, a3) // => [6]\n * _.difference(a1, a3) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.intersection|intersection}\n * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy}\n * @see {@link module:lamb.pull|pull}, {@link module:lamb.pullFrom|pullFrom}\n * @since 0.6.0\n * @param {ArrayLike} arrayLike\n * @param {ArrayLike} other\n * @returns {Array}\n */\n function difference (arrayLike, other) {\n var isNotInOther = partial(not(isIn), [other]);\n\n return uniques(filter(arrayLike, isNotInOther));\n }\n\n /**\n * A curried version of {@link module:lamb.dropFrom|dropFrom} that expects the number of elements\n * to drop to build a function waiting for the list to take the elements from.
\n * See the note and examples for {@link module:lamb.dropFrom|dropFrom} about passing a\n * negative n.\n * @example\n * var drop2 = _.drop(2);\n *\n * drop2([1, 2, 3, 4, 5]) // => [3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @since 0.5.0\n * @see {@link module:lamb.dropFrom|dropFrom}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @param {Number} n\n * @returns {Function}\n */\n var drop = _curry2(dropFrom, true);\n\n /**\n * Builds an array without the first n elements of the given array or array-like object.\n * Note that, being this only a shortcut for a specific use case of {@link module:lamb.slice|slice},\n * n can be a negative number.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.dropFrom(arr, 2) // => [3, 4, 5]\n * _.dropFrom(arr, -1) // => [5]\n * _.dropFrom(arr, -10) // => [1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @since 0.51.0\n * @param {ArrayLike} arrayLike\n * @param {Number} n\n * @returns {Array}\n */\n function dropFrom (arrayLike, n) {\n return slice(arrayLike, n, arrayLike.length);\n }\n\n /**\n * Builds a function that drops the first n elements satisfying a predicate\n * from an array or array-like object.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var dropWhileIsEven = _.dropWhile(isEven);\n *\n * dropWhileIsEven([2, 4, 6, 8]) // => []\n * dropWhileIsEven([2, 4, 7, 8]) // => [7, 8]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.takeWhile|takeWhile}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @since 0.5.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n function dropWhile (predicate) {\n return function (arrayLike) {\n return slice(arrayLike, _getNumConsecutiveHits(arrayLike, predicate), arrayLike.length);\n };\n }\n\n /**\n * Similar to {@link module:lamb.map|map}, but if the mapping function returns an array this will\n * be concatenated, rather than pushed, to the final result.\n * @example Showing the difference with map:\n * var words = [\"foo\", \"bar\"];\n * var toCharArray = function (s) { return s.split(\"\"); };\n *\n * _.map(words, toCharArray) // => [[\"f\", \"o\", \"o\"], [\"b\", \"a\", \"r\"]]\n * _.flatMap(words, toCharArray) // => [\"f\", \"o\", \"o\", \"b\", \"a\", \"r\"]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.flatMapWith|flatMapWith}\n * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith}\n * @since 0.1.0\n * @param {Array} array\n * @param {ListIteratorCallback} iteratee\n * @returns {Array}\n */\n function flatMap (array, iteratee) {\n return reduce(array, function (result, el, idx, arr) {\n var v = iteratee(el, idx, arr);\n\n if (!Array.isArray(v)) {\n v = [v];\n }\n\n for (var i = 0, len = v.length, rLen = result.length; i < len; i++) {\n result[rLen + i] = v[i];\n }\n\n return result;\n }, []);\n }\n\n /**\n * A curried version of {@link module:lamb.flatMap|flatMap} that uses provided iteratee\n * to build a function expecting the array to act upon.\n * @example\n * var toCharArray = function (s) { return s.split(\"\"); };\n * var wordsToCharArray = _.flatMapWith(toCharArray);\n *\n * wordsToCharArray([\"foo\", \"bar\"]) // => [\"f\", \"o\", \"o\", \"b\", \"a\", \"r\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.flatMap|flatMap}\n * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith}\n * @since 0.11.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var flatMapWith = _curry2(flatMap, true);\n\n /**\n * Flattens an array.\n * @example Showing the difference with shallowFlatten:\n * var arr = [1, 2, [3, 4, [5, 6]], 7, 8];\n *\n * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8]\n * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.shallowFlatten|shallowFlatten}\n * @since 0.1.0\n * @param {Array} array\n * @returns {Array}\n */\n var flatten = _makeArrayFlattener(true);\n\n /**\n * Returns a copy of the given array-like object without the last element.\n * @example\n * _.init([1, 2, 3, 4]) // => [1, 2, 3]\n * _.init([1]) // => []\n * _.init([]) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.tail|tail}\n * @see {@link module:lamb.head|head}, {@link module:lamb.last|last}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n var init = partial(slice, [_, 0, -1]);\n\n /**\n * Inserts the provided element in a copy of an array-like object at the\n * specified index.
\n * If the index is greater than the length of the array-like, the element\n * will be appended at the end.
\n * Negative indexes are allowed; when a negative index is out of bounds\n * the element will be inserted at the start of the resulting array.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.insert(arr, 3, 99) // => [1, 2, 3, 99, 4, 5]\n * _.insert(arr, -2, 99) // => [1, 2, 3, 99, 4, 5]\n * _.insert(arr, 10, 99) // => [1, 2, 3, 4, 5, 99]\n * _.insert(arr, -10, 99) // => [99, 1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.insertAt|insertAt}\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @param {*} element\n * @returns {Array}\n */\n function insert (arrayLike, index, element) {\n var result = slice(arrayLike, 0, arrayLike.length);\n\n result.splice(index, 0, element);\n\n return result;\n }\n\n /**\n * Builds a partial application of {@link module:lamb.insert|insert}\n * expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.insertAt(3, 99)(arr) // => [1, 2, 3, 99, 4, 5]\n * _.insertAt(-2, 99)(arr) // => [1, 2, 3, 99, 4, 5]\n * _.insertAt(10, 99)(arr) // => [1, 2, 3, 4, 5, 99]\n * _.insertAt(-10, 99)(arr) // => [99, 1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.insert|insert}\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo}\n * @since 0.27.0\n * @param {Number} index\n * @param {*} element\n * @returns {Function}\n */\n var insertAt = _makePartial3(insert);\n\n /**\n * Returns an array of every unique item that is included in all two given arrays\n * or array-like objects.
\n * Note that this function uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var a1 = [1, 2, 3, 4];\n * var a2 = [2, 5, 4, 2, 6];\n * var a3 = [5, 6, 7];\n *\n * _.intersection(a1, a2) // => [2, 4]\n * _.intersection(a2, a3) // => [5, 6]\n * _.intersection(a1, a3) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.difference|difference}\n * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy}\n * @since 0.5.0\n * @param {ArrayLike} a\n * @param {ArrayLike} b\n * @returns {Array}\n */\n function intersection (a, b) {\n var result = [];\n var lenA = a.length;\n\n if (lenA && b.length) {\n for (var i = 0; i < lenA; i++) {\n !isIn(result, a[i]) && isIn(b, a[i]) && result.push(a[i]);\n }\n }\n\n return result;\n }\n\n /**\n * Splits an array-like object in two lists: the first with the elements satisfying the given predicate,\n * the others with the remaining elements.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n *\n * _.partition(numbers, isEven) // => [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.partitionWith|partitionWith}\n * @since 0.11.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Array}\n */\n function partition (arrayLike, predicate) {\n var result = [[], []];\n var len = arrayLike.length;\n\n for (var i = 0, el; i < len; i++) {\n el = arrayLike[i];\n result[predicate(el, i, arrayLike) ? 0 : 1].push(el);\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.partition|partition} that uses the provided\n * predicate to build a function expecting the array-like object to act upon.\n * @example\n * var users = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"active\": false},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"active\": true},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"active\": true},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"active\": false}\n * ];\n * var isActive = _.hasKeyValue(\"active\", true);\n * var splitByActiveStatus = _.partitionWith(isActive);\n *\n * splitByActiveStatus(users) // =>\n * // [[\n * // {\"name\": \"John\", \"surname\": \"Doe\", \"active\": true},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\", \"active\": true}\n * // ], [\n * // {\"name\": \"Jane\", \"surname\": \"Doe\", \"active\": false},\n * // {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"active\": false}\n * // ]]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.partition|partition}\n * @since 0.11.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var partitionWith = _curry2(partition, true);\n\n /**\n * \"Plucks\" the values of the specified key from a list of objects.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 15}\n * ];\n *\n * _.pluck(persons, \"age\") // => [12, 40, 18, 15]\n *\n * var lists = [\n * [1, 2],\n * [3, 4, 5],\n * [6]\n * ];\n *\n * _.pluck(lists, \"length\") // => [2, 3, 1]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.pluckKey|pluckKey}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {String} key\n * @returns {Array}\n */\n function pluck (arrayLike, key) {\n return map(arrayLike, getKey(key));\n }\n\n /**\n * A curried version of {@link module:lamb.pluck|pluck} expecting the key to retrieve to\n * build a function waiting for the array-like object to act upon.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 15}\n * ];\n * var getAges = _.pluckKey(\"age\");\n *\n * getAges(persons) // => [12, 40, 18, 15]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.pluck|pluck}\n * @since 0.12.0\n * @param {String} key\n * @returns {Function}\n */\n var pluckKey = compose(mapWith, getKey);\n\n /**\n * A curried version of {@link module:lamb.pullFrom|pullFrom} expecting\n * a list of values to build a function waiting for an array-like object.
\n * The new function will create an array copy of the array-like without\n * the specified values.
\n * The equality test is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.
\n * See examples in {@link module:lamb.pullFrom|pullFrom} about the\n * relationship with {@link module:lamb.difference|difference}.\n * @example\n * var scores = [40, 20, 30, 10];\n * var newScores = [30, 10];\n * var pullNewScores = _.pull(newScores);\n *\n * pullNewScores(scores) // => [40, 20]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.pullFrom|pullFrom}\n * @see {@link module:lamb.difference|difference}\n * @since 0.45.0\n * @param {ArrayLike} values\n * @returns {Function}\n */\n var pull = _curry2(pullFrom, true);\n\n /**\n * Creates an array copy of the given array-like object without the\n * specified values.
\n * The equality test is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.pullFrom(arr, [2, 5]) // => [1, 3, 4]\n *\n * @example It's not the same as {@link module:lamb.difference|difference}:\n *\n * var arr = [1,1,2,3,4,4,5];\n *\n * _.pullFrom(arr, [1, 2]) // => [3, 4, 4, 5]\n * _.difference(arr, [1, 2]) // => [3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.pull|pull}\n * @see {@link module:lamb.difference|difference}\n * @since 0.45.0\n * @param {ArrayLike} arrayLike\n * @param {ArrayLike} values\n * @returns {Array}\n */\n function pullFrom (arrayLike, values) {\n return values ? filter(arrayLike, function (element) {\n return !isIn(values, element);\n }) : slice(arrayLike, 0, arrayLike.length);\n }\n\n /**\n * Returns a copy of the given array-like with the element rotated by the desired amount.\n * Negative indexes are allowed.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.rotate(arr, 3) // => [3, 4, 5, 1, 2]\n * _.rotate(arr, -3) // => [4, 5, 1, 2, 3]\n * _.rotate(arr, 11) // => [5, 1, 2, 3, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.rotateBy|rotateBy}\n * @since 0.55.0\n * @param {ArrayLike} arrayLike\n * @param {Number} amount\n * @returns {Array}\n */\n function rotate (arrayLike, amount) {\n var len = arrayLike.length;\n var shift = amount % len;\n\n return slice(arrayLike, -shift, len).concat(slice(arrayLike, 0, -shift));\n }\n\n /**\n * A curried version of {@link module:lamb.rotate|rotate}.
\n * Uses the given amount to build a function expecting the array to rotate by that amount.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n * var rotateByTwo = _.rotateBy(2);\n *\n * rotateByTwo(arr) // => [4, 5, 1, 2, 3]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.rotate|rotate}\n * @since 0.55.0\n * @param {Number} amount\n * @returns {Function}\n */\n var rotateBy = _curry2(rotate, true);\n\n /**\n * Flattens the \"first level\" of an array.\n * @example Showing the difference with flatten:\n * var arr = [1, 2, [3, 4, [5, 6]], 7, 8];\n *\n * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8]\n * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.flatten|flatten}\n * @since 0.9.0\n * @param {Array} array\n * @returns {Array}\n */\n var shallowFlatten = _makeArrayFlattener(false);\n\n /**\n * Returns a copy of the given array-like object without the first element.\n * @example\n * _.tail([1, 2, 3, 4]) // => [2, 3, 4]\n * _.tail([1]) // => []\n * _.tail([]) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.init|init}\n * @see {@link module:lamb.head|head}, {@link module:lamb.last|last}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n var tail = drop(1);\n\n /**\n * A curried version of {@link module:lamb.takeFrom|takeFrom} that expects the number of elements\n * to retrieve to build a function waiting for the list to take the elements from.
\n * See the note and examples for {@link module:lamb.takeFrom|takeFrom} about passing a\n * negative n.\n * @example\n * var take2 = _.take(2);\n *\n * take2([1, 2, 3, 4, 5]) // => [1, 2]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.takeFrom|takeFrom}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @since 0.5.0\n * @param {Number} n\n * @returns {Function}\n */\n var take = _curry2(takeFrom, true);\n\n /**\n * Retrieves the first n elements from an array or array-like object.
\n * Note that, being this a shortcut for a common use case of {@link module:lamb.slice|slice},\n * n can be a negative number.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.takeFrom(arr, 3) // => [1, 2, 3]\n * _.takeFrom(arr, -1) // => [1, 2, 3, 4]\n * _.takeFrom(arr, -10) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.take|take}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @since 0.51.0\n * @param {ArrayLike} arrayLike\n * @param {Number} n\n * @returns {Array}\n */\n function takeFrom (arrayLike, n) {\n return slice(arrayLike, 0, n);\n }\n\n /**\n * Builds a function that takes the first n elements satisfying a predicate from\n * an array or array-like object.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var takeWhileIsEven = _.takeWhile(isEven);\n *\n * takeWhileIsEven([1, 2, 4, 6, 8]) // => []\n * takeWhileIsEven([2, 4, 7, 8]) // => [2, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.dropWhile|dropWhile}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @since 0.5.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n function takeWhile (predicate) {\n return function (arrayLike) {\n return slice(arrayLike, 0, _getNumConsecutiveHits(arrayLike, predicate));\n };\n }\n\n /**\n * Transposes a matrix. Can also be used to reverse a {@link module:lamb.zip|zip} operation.
\n * Just like {@link module:lamb.zip|zip}, the received array-like objects will be truncated to the\n * shortest length.\n * @example Transposing a matrix:\n * _.transpose([\n * [1, 2, 3],\n * [4, 5, 6],\n * [7, 8, 9]\n * ]) // =>\n * // [\n * // [1, 4, 7],\n * // [2, 5, 8],\n * // [3, 6, 9]\n * // ]\n *\n * @example Showing the relationship with zip:\n * var zipped = _.zip([\"a\", \"b\", \"c\"], [1, 2, 3]); // => [[\"a\", 1], [\"b\", 2], [\"c\", 3]]\n *\n * _.transpose(zipped) // => [[\"a\", \"b\", \"c\"], [1, 2, 3]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.zip|zip}\n * @since 0.14.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n function transpose (arrayLike) {\n var minLen = MAX_ARRAY_LENGTH;\n var len = _toArrayLength(arrayLike.length);\n\n if (len === 0) {\n return [];\n }\n\n for (var j = 0, elementLen; j < len; j++) {\n elementLen = _toArrayLength(arrayLike[j].length);\n\n if (elementLen < minLen) {\n minLen = elementLen;\n }\n }\n\n var result = Array(minLen);\n\n for (var i = 0, el; i < minLen; i++) {\n el = result[i] = Array(len);\n\n for (j = 0; j < len; j++) {\n el[j] = arrayLike[j][i];\n }\n }\n\n return result;\n }\n\n /**\n * Returns a list of every unique element present in the two given array-like objects.
\n * Uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}\n * to test the equality of values.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.unionBy|unionBy} if you need to transform the values before\n * the comparison or if you have to extract them from complex ones.\n * @example\n * _.union([1, 2, 3, 2], [2, 3, 4]) // => [1, 2, 3, 4]\n * _.union(\"abc\", \"bcd\") // => [\"a\", \"b\", \"c\", \"d\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.unionBy|unionBy}\n * @see {@link module:lamb.difference|difference}\n * @see {@link module:lamb.intersection|intersection}\n * @since 0.5.0\n * @param {ArrayLike} a\n * @param {ArrayLike} b\n * @returns {Array}\n */\n var union = unionBy(identity);\n\n /**\n * Using the provided iteratee to transform values, builds a function that will\n * return an array of the unique elements in the two provided array-like objects.
\n * Uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}\n * to test the equality of values.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.union|union} if you don't need to compare transformed values.\n * @example\n * var unionByFloor = _.unionBy(Math.floor);\n *\n * unionByFloor([2.8, 3.2, 1.5], [3.5, 1.2, 4]) // => [2.8, 3.2, 1.5, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.union|union}\n * @see {@link module:lamb.difference|difference}\n * @see {@link module:lamb.intersection|intersection}\n * @since 0.51.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n function unionBy (iteratee) {\n return pipe([binary(list), flatMapWith(drop(0)), uniquesBy(iteratee)]);\n }\n\n /**\n * Returns an array comprised of the unique elements of the given array-like object.
\n * Note that this function uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}\n * to test the equality of values.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.uniquesBy|uniquesBy} if you need to transform your values before\n * the comparison or if you have to extract them from complex ones.\n * @example\n * _.uniques([-0, 1, 2, 0, 2, 3, 4, 3, 5, 1]) // => [-0, 1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.uniquesBy|uniquesBy}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n var uniques = uniquesBy(identity);\n\n /**\n * Using the provided iteratee, builds a function that will return an array comprised of the\n * unique elements of an array-like object. The values being compared are the ones returned by\n * the iteratee.
\n * The equality test is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.uniques|uniques} if you don't need to transform your values before the\n * comparison.\n * @example\n * var data = [\n * {id: \"1\", name: \"John\"},\n * {id: \"4\", name: \"Jane\"},\n * {id: \"5\", name: \"Joe\"},\n * {id: \"1\", name: \"Mario\"},\n * {id: \"5\", name: \"Paolo\"},\n * ];\n * var uniquesById = _.uniquesBy(_.getKey(\"id\"));\n *\n * uniquesById(data) // => [{id: \"1\", name: \"John\"}, {id: \"4\", name: \"Jane\"}, {id: \"5\", name: \"Joe\"}]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.uniques|uniques}\n * @since 0.51.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n function uniquesBy (iteratee) {\n return function (arrayLike) {\n var result = [];\n\n for (var i = 0, len = arrayLike.length, seen = [], value; i < len; i++) {\n value = iteratee(arrayLike[i], i, arrayLike);\n\n if (!isIn(seen, value)) {\n seen.push(value);\n result.push(arrayLike[i]);\n }\n }\n\n return result;\n };\n }\n\n /**\n * Builds a list of arrays out of the two given array-like objects by pairing items with\n * the same index.
\n * The received array-like objects will be truncated to the shortest length.\n * @example\n * _.zip(\n * [\"a\", \"b\", \"c\"],\n * [1, 2, 3]\n * ) // => [[\"a\", 1], [\"b\", 2], [\"c\", 3]]\n *\n * _.zip([1, 2, 3, 4], [5, 6, 7]) // => [[1, 5], [2, 6], [3, 7]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.transpose|transpose} for the reverse operation\n * @see {@link module:lamb.zipWithIndex|zipWithIndex}\n * @since 0.14.0\n * @param {ArrayLike} a\n * @param {ArrayLike} b\n * @returns {Array}\n */\n function zip (a, b) {\n return transpose([a, b]);\n }\n\n /**\n * \"{@link module:lamb.zip|Zips}\" an array-like object by pairing its values with their index.\n * @example\n * _.zipWithIndex([\"a\", \"b\", \"c\"]) // => [[\"a\", 0], [\"b\", 1], [\"c\", 2]]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.zip|zip}\n * @since 0.14.0\n * @param {ArrayLike} arrayLike\n * @returns {Array>}\n */\n var zipWithIndex = mapWith(binary(list));\n\n lamb.append = append;\n lamb.appendTo = appendTo;\n lamb.difference = difference;\n lamb.drop = drop;\n lamb.dropFrom = dropFrom;\n lamb.dropWhile = dropWhile;\n lamb.flatMap = flatMap;\n lamb.flatMapWith = flatMapWith;\n lamb.flatten = flatten;\n lamb.init = init;\n lamb.insert = insert;\n lamb.insertAt = insertAt;\n lamb.intersection = intersection;\n lamb.partition = partition;\n lamb.partitionWith = partitionWith;\n lamb.pluck = pluck;\n lamb.pluckKey = pluckKey;\n lamb.pull = pull;\n lamb.pullFrom = pullFrom;\n lamb.rotate = rotate;\n lamb.rotateBy = rotateBy;\n lamb.shallowFlatten = shallowFlatten;\n lamb.tail = tail;\n lamb.take = take;\n lamb.takeFrom = takeFrom;\n lamb.takeWhile = takeWhile;\n lamb.transpose = transpose;\n lamb.union = union;\n lamb.unionBy = unionBy;\n lamb.uniques = uniques;\n lamb.uniquesBy = uniquesBy;\n lamb.zip = zip;\n lamb.zipWithIndex = zipWithIndex;\n\n /**\n * Transforms an array-like object in a lookup table with the keys generated by the provided\n * iteratee, having as values the count of matches for the key.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12},\n * {\"name\": \"John\", \"age\": 40},\n * {\"name\": \"Mario\", \"age\": 17},\n * {\"name\": \"Paolo\", \"age\": 15}\n * ];\n * var getAgeStatus = function (person) { return person.age >= 18 ? \"adult\" : \"minor\"; };\n *\n * _.count(persons, getAgeStatus) // => {\"adult\": 1, \"minor\": 3}\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.21.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Object}\n */\n var count = _groupWith(function (a) {\n return a ? ++a : 1;\n });\n\n /**\n * A curried version of {@link module:lamb.count|count} that uses the provided iteratee to\n * build a function expecting the array-like object to act upon.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"city\": \"New York\"},\n * {\"name\": \"John\", \"city\": \"New York\"},\n * {\"name\": \"Mario\", \"city\": \"Rome\"},\n * {\"name\": \"Paolo\"}\n * ];\n * var getCityOrUnknown = _.adapter([_.getKey(\"city\"), _.always(\"Unknown\")]);\n * var countByCity = _.countBy(getCityOrUnknown);\n *\n * countByCity(persons) // => {\"New York\": 2, \"Rome\": 1, \"Unknown\": 1}\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.count|count}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.21.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var countBy = _curry2(count, true);\n\n /**\n * Transforms an array-like object into a lookup table using the provided iteratee as a grouping\n * criterion to generate keys and values.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"city\": \"New York\"},\n * {\"name\": \"John\", \"city\": \"New York\"},\n * {\"name\": \"Mario\", \"city\": \"Rome\"},\n * {\"name\": \"Paolo\"}\n * ];\n * var getCity = _.getKey(\"city\");\n * var personsByCity = _.group(persons, getCity);\n *\n * // \"personsByCity\" holds:\n * // {\n * // \"New York\": [\n * // {\"name\": \"Jane\", \"city\": \"New York\"},\n * // {\"name\": \"John\", \"city\": \"New York\"}\n * // ],\n * // \"Rome\": [\n * // {\"name\": \"Mario\", \"city\": \"Rome\"}\n * // ],\n * // \"undefined\": [\n * // {\"name\": \"Paolo\"}\n * // ]\n * // }\n *\n * @example Adding a custom value for missing keys:\n *\n * var getCityOrUnknown = _.adapter([getCity, _.always(\"Unknown\")]);\n *\n * var personsByCity = _.group(persons, getCityOrUnknown);\n *\n * // \"personsByCity\" holds:\n * // {\n * // \"New York\": [\n * // {\"name\": \"Jane\", \"city\": \"New York\"},\n * // {\"name\": \"John\", \"city\": \"New York\"}\n * // ],\n * // \"Rome\": [\n * // {\"name\": \"Mario\", \"city\": \"Rome\"}\n * // ],\n * // \"Unknown\": [\n * // {\"name\": \"Paolo\"}\n * // ]\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.groupBy|groupBy}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.7.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Object}\n */\n var group = _groupWith(function (a, b) {\n if (!a) {\n return [b];\n }\n\n a[a.length] = b;\n\n return a;\n });\n\n /**\n * A curried version of {@link module:lamb.group|group} that uses the provided iteratee\n * to build a function expecting the array-like object to act upon.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12},\n * {\"name\": \"John\", \"age\": 40},\n * {\"name\": \"Mario\", \"age\": 18},\n * {\"name\": \"Paolo\", \"age\": 15}\n * ];\n *\n * var getAgeStatus = function (person) { return person.age > 20 ? \"over 20\" : \"under 20\"; };\n * var groupByAgeStatus = _.groupBy(getAgeStatus);\n *\n * var personsByAgeStatus = groupByAgeStatus(persons);\n *\n * // \"personsByAgeStatus\" holds:\n * // {\n * // \"under 20\": [\n * // {\"name\": \"Jane\", \"age\": 12},\n * // {\"name\": \"Mario\", \"age\": 18},\n * // {\"name\": \"Paolo\", \"age\": 15}\n * // ],\n * // \"over 20\": [\n * // {\"name\": \"John\", \"age\": 40}\n * // ]\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.group|group}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.7.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var groupBy = _curry2(group, true);\n\n /**\n * Similar to {@link module:lamb.group|group}, but the generated lookup table will have\n * only one element of the original array-like object for each value.
\n * Should be used only when you're sure that your iteratee won't produce\n * duplicate keys, otherwise only the last evaluated element will be in the result.\n * @example\n * var users = [\n * {id: 1, name: \"John\"},\n * {id: 2, name: \"Jane\"},\n * {id: 3, name: \"Mario\"},\n * {id: 4, name: \"John\"}\n * ];\n *\n * var indexedUsers = _.index(users, _.getKey(\"id\"));\n *\n * // \"indexedUsers\" holds:\n * // {\n * // \"1\": {id: 1, name: \"John\"},\n * // \"2\": {id: 2, name: \"Jane\"},\n * // \"3\": {id: 3, name: \"Mario\"},\n * // \"4\": {id: 4, name: \"John\"}\n * // }\n *\n * @example Result of an iteratee producing a duplicate key:\n * var users = [\n * {id: 1, name: \"John\"},\n * {id: 2, name: \"Jane\"},\n * {id: 3, name: \"Mario\"},\n * {id: 4, name: \"John\"}\n * ];\n *\n * var indexedUsers = _.index(users, _.getKey(\"name\"));\n *\n * // \"indexedUsers\" holds:\n * // {\n * // \"John\": {\"id\": 4, \"name\": \"John\"},\n * // \"Jane\": {\"id\": 2, \"name\": \"Jane\"},\n * // \"Mario\": {\"id\": 3, \"name\": \"Mario\"}\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.indexBy|indexBy}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @since 0.21.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Object}\n */\n var index = _groupWith(function (a, b) {\n return b;\n });\n\n /**\n * A curried version of {@link module:lamb.index|index} that uses the provided iteratee\n * to build a function expecting the array-like object to act upon.\n * @example\n * var users = [\n * {id: 1, name: \"John\"},\n * {id: 2, name: \"Jane\"},\n * {id: 3, name: \"Mario\"}\n * ];\n * var indexByID = _.indexBy(_.getKey(\"id\"));\n *\n * var indexedUsers = indexByID(users);\n *\n * // \"indexedUsers\" holds:\n * // {\n * // \"1\": {id: 1, name: \"John\"},\n * // \"2\": {id: 2, name: \"Jane\"},\n * // \"3\": {id: 3, name: \"Mario\"}\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.index|index}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @since 0.21.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var indexBy = _curry2(index, true);\n\n lamb.count = count;\n lamb.countBy = countBy;\n lamb.group = group;\n lamb.groupBy = groupBy;\n lamb.index = index;\n lamb.indexBy = indexBy;\n\n /**\n * Returns a [stably]{@link https://en.wikipedia.org/wiki/Sorting_algorithm#Stability} sorted\n * copy of an array-like object using the given criteria.
\n * Sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, but you\n * can also pass simple \"reader\" functions and default ascending sorters will be built for you.
\n * A \"reader\" is a function that evaluates the array element and supplies the value to be used\n * in the comparison.
\n * Please note that if the arguments received by the default comparer aren't of the same type,\n * they will be compared as strings.\n *\n * @example Stable sort:\n * var persons = [\n * {\"name\": \"John\", \"surname\" :\"Doe\"},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\"},\n * {\"name\": \"John\", \"surname\" :\"Moe\"},\n * {\"name\": \"Jane\", \"surname\": \"Foe\"}\n * ];\n *\n * var personsByName = _.sort(persons, [_.getKey(\"name\")]);\n *\n * // personsByName holds:\n * // [\n * // {\"name\": \"Jane\", \"surname\": \"Foe\"},\n * // {\"name\": \"John\", \"surname\" :\"Doe\"},\n * // {\"name\": \"John\", \"surname\" :\"Moe\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @example Stable multi-sort:\n * var personsByNameAscSurnameDesc = _.sort(persons, [\n * _.getKey(\"name\"),\n * _.sorterDesc(_.getKey(\"surname\"))\n * ]);\n *\n * // personsByNameAscSurnameDesc holds:\n * // [\n * // {\"name\": \"Jane\", \"surname\": \"Foe\"},\n * // {\"name\": \"John\", \"surname\" :\"Moe\"},\n * // {\"name\": \"John\", \"surname\" :\"Doe\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @example Using custom comparers:\n * var localeSorter = new Intl.Collator(\"it\");\n * var chars = [\"a\", \"è\", \"à\", \"é\", \"c\", \"b\", \"e\"];\n *\n * _.sort(chars, [localeSorter]) // => [\"a\", \"à\", \"b\", \"c\", \"e\", \"é\", \"è\"]\n *\n * var localeSorterDesc = _.sorterDesc(_.identity, localeSorter.compare);\n *\n * _.sort(chars, [localeSorterDesc]) // => [\"è\", \"é\", \"e\", \"c\", \"b\", \"à\", \"a\"]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc}\n * @since 0.15.0\n * @param {ArrayLike} arrayLike\n * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]]\n * @returns {Array}\n */\n function sort (arrayLike, sorters) {\n var criteria = _makeCriteria(sorters);\n var len = _toArrayLength(arrayLike.length);\n var result = Array(len);\n\n for (var i = 0; i < len; i++) {\n result[i] = {value: arrayLike[i], index: i};\n }\n\n result.sort(_compareWith(criteria));\n\n for (i = 0; i < len; i++) {\n result[i] = result[i].value;\n }\n\n return result;\n }\n\n /**\n * Inserts an element in a copy of a sorted array respecting the sort order.\n * @example With simple values:\n * _.sortedInsert([], 1) // => [1]\n * _.sortedInsert([2, 4, 6], 5) // => [2, 4, 5, 6]\n * _.sortedInsert([4, 2, 1], 3, _.sorterDesc()) // => [4, 3, 2, 1]\n *\n * @example With complex values:\n * var persons = [\n * {\"name\": \"jane\", \"surname\": \"doe\"},\n * {\"name\": \"John\", \"surname\": \"Doe\"},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * ];\n *\n * var getLowerCaseName = _.compose(\n * _.invoker(\"toLowerCase\"),\n * _.getKey(\"name\")\n * );\n *\n * var result = _.sortedInsert(\n * persons,\n * {\"name\": \"marco\", \"surname\": \"Rossi\"},\n * getLowerCaseName\n * );\n *\n * // `result` holds:\n * // [\n * // {\"name\": \"jane\", \"surname\": \"doe\"},\n * // {\"name\": \"John\", \"surname\": \"Doe\"},\n * // {\"name\": \"marco\", \"surname\": \"Rossi\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc}\n * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} to insert the element\n * at a specific index\n * @since 0.27.0\n * @param {ArrayLike} arrayLike\n * @param {*} element\n * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - The sorting criteria\n * used to sort the array.\n * @returns {Array}\n */\n function sortedInsert (arrayLike, element, sorters) {\n var result = slice(arrayLike, 0, arrayLike.length);\n\n if (arguments.length === 1) {\n return result;\n }\n\n var criteria = _makeCriteria(sorters);\n var idx = _getInsertionIndex(result, element, _compareWith(criteria), 0, result.length);\n\n result.splice(idx, 0, element);\n\n return result;\n }\n\n /**\n * Creates an ascending sort criterion with the provided reader and\n * comparer.
\n * See {@link module:lamb.sort|sort} for various examples.\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorterDesc|sorterDesc}\n * @since 0.1.0\n * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a\n * simple value from a complex one. The function should evaluate the array element and supply the\n * value to be passed to the comparer.\n * @param {Function} [comparer] An optional custom comparer function.\n * @returns {Sorter}\n */\n var sorter = partial(_sorter, [_, false, _]);\n\n /**\n * Creates a descending sort criterion with the provided reader and\n * comparer.
\n * See {@link module:lamb.sort|sort} for various examples.\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorter|sorter}\n * @since 0.15.0\n * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a\n * simple value from a complex one. The function should evaluate the array element and supply the\n * value to be passed to the comparer.\n * @param {Function} [comparer] An optional custom comparer function.\n * @returns {Sorter}\n */\n var sorterDesc = partial(_sorter, [_, true, _]);\n\n /**\n * Builds a partial application of {@link module:lamb.sort|sort} using the provided criteria.\n * The returned function expects the array-like object to sort.\n * As usual, sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function,\n * but you can also pass simple \"reader\" functions and default ascending sorters will be built.
\n * A \"reader\" is a function that evaluates the array element and supplies the value to be used in\n * the comparison.
\n * See {@link module:lamb.sort|sort} for more examples.\n *\n * @example\n * var sortAsNumbers = _.sortWith([parseFloat]);\n * var weights = [\"2 Kg\", \"10 Kg\", \"1 Kg\", \"7 Kg\"];\n *\n * sortAsNumbers(weights) // => [\"1 Kg\", \"2 Kg\", \"7 Kg\", \"10 Kg\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.sort|sort}\n * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc}\n * @since 0.15.0\n * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]]\n * @returns {Function}\n */\n var sortWith = _curry2(sort, true);\n\n lamb.sort = sort;\n lamb.sortedInsert = sortedInsert;\n lamb.sorter = sorter;\n lamb.sorterDesc = sorterDesc;\n lamb.sortWith = sortWith;\n\n /**\n * Applies the given function to a list of arguments.\n * @example\n * _.application(_.sum, [3, 4]) // => 7\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.apply|apply}, {@link module:lamb.applyTo|applyTo}\n * @since 0.47.0\n * @param {Function} fn\n * @param {ArrayLike} args\n * @returns {*}\n */\n function application (fn, args) {\n return fn.apply(this, Object(args));\n }\n\n /**\n * A left-curried version of {@link module:lamb.application|application}. Expects the function\n * to apply and builds a function waiting for the arguments array.\n * @example\n * var arrayMax = _.apply(Math.max);\n *\n * arrayMax([4, 5, 2, 6, 1]) // => 6\n *\n * @memberof module:lamb\n * @category Function\n * @function\n * @see {@link module:lamb.application|application}, {@link module:lamb.applyTo|applyTo}\n * @since 0.1.0\n * @param {Function} fn\n * @returns {Function}\n */\n var apply = _curry2(application);\n\n /**\n * A right-curried version of {@link module:lamb.application|application}. Expects an array-like\n * object to use as arguments and builds a function waiting for the target of the application.\n * @example\n * var data = [3, 4];\n * var applyToData = _.applyTo(data);\n *\n * applyToData(_.sum) // => 7\n * applyToData(_.multiply) // => 12\n *\n * @memberof module:lamb\n * @category Function\n * @function\n * @see {@link module:lamb.application|application}, {@link module:lamb.apply|apply}\n * @since 0.47.0\n * @param {ArrayLike} args\n * @returns {Function}\n */\n var applyTo = _curry2(application, true);\n\n /**\n * Builds a new function that passes only the specified amount of arguments to the original one.
\n * As {@link module:lamb.slice|slice} is used to extract the arguments, you can also\n * pass a negative arity.\n * @example\n * Math.max(10, 11, 45, 99) // => 99\n * _.aritize(Math.max, 2)(10, 11, 45, 99) // => 11\n *\n * @example Using a negative arity:\n * _.aritize(Math.max, -1)(10, 11, 45, 99) // => 45\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.binary|binary}, {@link module:lamb.unary|unary} for common use cases shortcuts\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} arity\n * @returns {Function}\n */\n function aritize (fn, arity) {\n return function () {\n var n = _toInteger(arity);\n var args = list.apply(null, arguments).slice(0, n);\n\n for (var i = args.length; i < n; i++) {\n args[i] = void 0;\n }\n\n return fn.apply(this, args);\n };\n }\n\n /**\n * Decorates the received function so that it can be called with\n * placeholders to build a partial application of it.
\n * The difference with {@link module:lamb.partial|partial} is that, as long as\n * you call the generated function with placeholders, another partial application\n * of the original function will be built.
\n * The final application will happen when one of the generated functions is\n * invoked without placeholders, using the parameters collected so far.
\n * This function comes in handy when you need to build different specialized\n * functions starting from a basic one, but it's also useful when dealing with\n * optional parameters as you can decide to apply the function even if its arity\n * hasn't been entirely consumed.\n * @example Explaining the function's behaviour:\n * var f = _.asPartial(function (a, b, c) {\n * return a + b + c;\n * });\n *\n * f(4, 3, 2) // => 9\n * f(4, _, 2)(3) // => 9\n * f(_, 3, _)(4, _)(2) // => 9\n *\n * @example Exploiting optional parameters:\n * var f = _.asPartial(function (a, b, c) {\n * return a + b + (c || 0);\n * });\n *\n * var addFive = f(5, _);\n * addFive(2) // => 7\n *\n * var addNine = addFive(4, _);\n * addNine(11) // => 20\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder}\n * @since 0.36.0\n * @param {Function} fn\n * @returns {Function}\n */\n function asPartial (fn) {\n return _asPartial(fn, []);\n }\n\n /**\n * Builds a function that passes only two arguments to the given function.
\n * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize},\n * exposed for convenience.\n * @example\n * _.list(1, 2, 3, 4, 5) // => [1, 2, 3, 4, 5]\n * _.binary(_.list)(1, 2, 3, 4, 5) // => [1, 2]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.aritize|aritize}\n * @see {@link module:lamb.unary|unary}\n * @since 0.10.0\n * @param {Function} fn\n * @returns {Function}\n */\n function binary (fn) {\n return function (a, b) {\n return fn.call(this, a, b);\n };\n }\n\n /**\n * Accepts a series of functions and builds a new function. The functions in the series\n * will then be applied, in order, with the values received by the function built with\n * collect.
\n * The collected results will be returned in an array.\n * @example\n * var user = {\n * id: \"jdoe\",\n * name: \"John\",\n * surname: \"Doe\",\n * scores: [2, 4, 7]\n * };\n * var getIDAndLastScore = _.collect([_.getKey(\"id\"), _.getPath(\"scores.-1\")]);\n *\n * getIDAndLastScore(user) // => [\"jdoe\", 7]\n *\n * @example\n * var minAndMax = _.collect([Math.min, Math.max]);\n *\n * minAndMax(3, 1, -2, 5, 4, -1) // => [-2, 5]\n *\n * @memberof module:lamb\n * @category Function\n * @since 0.35.0\n * @param {Function[]} functions\n * @returns {Function}\n */\n function collect (functions) {\n if (!Array.isArray(functions)) {\n throw _makeTypeErrorFor(functions, \"array\");\n }\n\n return function () {\n return map(functions, applyTo(arguments));\n };\n }\n\n /**\n * Transforms the evaluation of the given function in the evaluation of a sequence of functions\n * expecting only one argument. Each function of the sequence is a partial application of the\n * original one, which will be applied when the specified (or derived) arity is consumed.
\n * Currying will start from the leftmost argument: use {@link module:lamb.curryRight|curryRight}\n * for right currying.\n * @example\n * var makeWithKeys = _.curry(_.make);\n * var makePerson = makeWithKeys([\"name\", \"surname\"]);\n *\n * makePerson([\"John\", \"Doe\"]) // => {name: \"John\", surname: \"Doe\"};\n * makePerson([\"Mario\", \"Rossi\"]) // => {name: \"Mario\", surname: \"Rossi\"};\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curry (fn, arity) {\n return _curry(fn, arity, false);\n }\n\n /**\n * Builds an auto-curried function. The resulting function can be called multiple times with\n * any number of arguments, and the original function will be applied only when the specified\n * (or derived) arity is consumed.
\n * Currying will start from the leftmost argument: use {@link module:lamb.curryableRight|curryableRight}\n * for right currying.\n * @example\n * var collectFourElements = _.curryable(_.list, 4);\n *\n * collectFourElements(2)(3)(4)(5) // => [2, 3, 4, 5]\n * collectFourElements(2)(3, 4)(5) // => [2, 3, 4, 5]\n * collectFourElements(2, 3, 4, 5) // => [2, 3, 4, 5]\n * collectFourElements(2, 3)(4, 5) // => [2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.6.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curryable (fn, arity) {\n return _curry(fn, arity, false, true);\n }\n\n /**\n * Same as {@link module:lamb.curryable|curryable}, but currying starts from the rightmost argument.\n * @example\n * var collectFourElements = _.curryableRight(_.list, 4);\n *\n * collectFourElements(2)(3)(4)(5) // => [5, 4, 3, 2]\n * collectFourElements(2)(3, 4)(5) // => [5, 4, 3, 2]\n * collectFourElements(2, 3, 4, 5) // => [5, 4, 3, 2]\n * collectFourElements(2, 3)(4, 5) // => [5, 4, 3, 2]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curryable|curryable}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.9.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curryableRight (fn, arity) {\n return _curry(fn, arity, true, true);\n }\n\n /**\n * Same as {@link module:lamb.curry|curry}, but currying starts from the rightmost argument.\n * @example\n * var makeWithValues = _.curryRight(_.make);\n * var makeJohnDoe = makeWithValues([\"John\", \"Doe\"]);\n *\n * makeJohnDoe([\"name\", \"surname\"]) // => {name: \"John\", surname: \"Doe\"};\n * makeJohnDoe([\"firstName\", \"lastName\"]) // => {firstName: \"John\", lastName: \"Doe\"};\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curry|curry}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.9.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curryRight (fn, arity) {\n return _curry(fn, arity, true);\n }\n\n /**\n * Returns a function that will execute the given function only if it stops being called for the\n * specified timespan.
\n * See also {@link module:lamb.throttle|throttle} for a different behaviour where the first call\n * happens immediately.\n * @example A common use case of debounce in a browser environment:\n * var updateLayout = function () {\n * // some heavy DOM operations here\n * };\n *\n * window.addEventListener(\"resize\", _.debounce(updateLayout, 200), false);\n *\n * // The resize event is fired repeteadly until the user stops resizing the\n * // window, while the `updateLayout` function is called only once: 200 ms\n * // after he stopped.\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.throttle|throttle}\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} timespan - Expressed in milliseconds\n * @returns {Function}\n */\n function debounce (fn, timespan) {\n var timeoutID;\n\n return function () {\n var args = arguments;\n var debounced = function () {\n timeoutID = null;\n fn.apply(this, args);\n }.bind(this);\n\n clearTimeout(timeoutID);\n timeoutID = setTimeout(debounced, timespan);\n };\n }\n\n /**\n * Returns a function that applies the original function with the arguments in reverse order.\n * @example\n * _.list(1, 2, 3) // => [1, 2, 3]\n * _.flip(_.list)(1, 2, 3) // => [3, 2, 1]\n *\n * @memberof module:lamb\n * @category Function\n * @since 0.1.0\n * @param {Function} fn\n * @returns {Function}\n */\n function flip (fn) {\n return function () {\n var args = list.apply(null, arguments).reverse();\n\n return fn.apply(this, args);\n };\n }\n\n /**\n * Builds a function that returns the argument received at the given index.
\n * As with {@link module:lamb.getAt|getAt} negative indexes are allowed.
\n * The resulting function will return undefined if no arguments are\n * passed or if the index is out of bounds.\n * @example\n * var getFirstArg = _.getArgAt(0);\n * var getLastArg = _.getArgAt(-1);\n *\n * getFirstArg(1, 2, 3) // => 1\n * getLastArg(1, 2, 3) // => 3\n *\n * _.getArgAt()(1, 2, 3) // => undefined\n * _.getArgAt(6)(1, 2, 3) // => undefined\n * _.getArgAt(1)() // => undefined\n *\n * @memberof module:lamb\n * @category Function\n * @since 0.17.0\n * @param {Number} idx\n * @returns {Function}\n */\n function getArgAt (idx) {\n return function () {\n return arguments[_toNaturalIndex(idx, arguments.length)];\n };\n }\n\n /**\n * Builds a function that will invoke the given method name on any received object and\n * return the result. If no method with such name is found the function will return\n * undefined.
\n * Along with the method name it's possible to supply some arguments that will be bound to the\n * method call. Further arguments can also be passed when the function is actually called, and\n * they will be concatenated to the bound ones.
\n * Returning undefined is a behaviour meant to quickly create a case for\n * {@link module:lamb.adapter|adapter} without the need to check for the existence of the\n * desired method.
\n * See also {@link module:lamb.generic|generic} to create functions out of object methods.\n * @example Basic polymorphism with invoker:\n * var polySlice = _.invoker(\"slice\");\n *\n * polySlice([1, 2, 3, 4, 5], 1, 3) // => [2, 3]\n * polySlice(\"Hello world\", 1, 3) // => \"el\"\n *\n * @example With bound arguments:\n * var substrFrom2 = _.invoker(\"substr\", 2);\n * substrFrom2(\"Hello world\") // => \"llo world\"\n * substrFrom2(\"Hello world\", 5) // => \"llo w\"\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.invokerOn|invokerOn}\n * @since 0.1.0\n * @param {String} methodName\n * @param {...*} [boundArg]\n * @returns {Function}\n */\n function invoker (methodName) {\n return partial(_invoker, [_argsTail.apply(null, arguments), methodName]);\n }\n\n /**\n * Accepts an object and builds a function expecting a method name, and optionally arguments,\n * to call on such object.\n * Like {@link module:lamb.invoker|invoker}, if no method with the given name is found the\n * function will return undefined.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var arr = [1, 2, 3, 4, 5];\n * var invokerOnArr = _.invokerOn(arr);\n *\n * invokerOnArr(\"filter\", isEven) // => [2, 4]\n * invokerOnArr(\"slice\", 1, 3) // => [2, 3]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.invoker|invoker}\n * @since 0.15.0\n * @param {Object} target\n * @returns {Function}\n */\n function invokerOn (target) {\n return partial(_invoker, [[], _, target]);\n }\n\n /**\n * Builds a function that allows to map over the received arguments before applying them\n * to the original one.\n * @example\n * var sumArray = _.reduceWith(_.sum);\n * var sumArgs = _.compose(sumArray, _.list);\n *\n * sumArgs(1, 2, 3, 4, 5) // => 15\n *\n * var square = _.partial(Math.pow, [_, 2]);\n * var sumSquares = _.mapArgs(sumArgs, square);\n *\n * sumSquares(1, 2, 3, 4, 5) // => 55\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.tapArgs|tapArgs}\n * @since 0.3.0\n * @param {Function} fn\n * @param {ListIteratorCallback} mapper\n * @returns {Function}\n */\n function mapArgs (fn, mapper) {\n return pipe([list, mapWith(mapper), apply(fn)]);\n }\n\n /**\n * Creates a pipeline of functions, where each function consumes the result of the previous one.\n * @example\n * var square = _.partial(Math.pow, [_, 2]);\n * var getMaxAndSquare = _.pipe([Math.max, square]);\n *\n * getMaxAndSquare(3, 5) // => 25\n *\n * @memberof module:lamb\n * @category Function\n * @function\n * @see {@link module:lamb.compose|compose}\n * @since 0.1.0\n * @param {Function[]} functions\n * @returns {Function}\n */\n function pipe (functions) {\n if (!Array.isArray(functions)) {\n throw _makeTypeErrorFor(functions, \"array\");\n }\n\n var len = functions.length;\n\n return len ? function () {\n var result = functions[0].apply(this, arguments);\n\n for (var i = 1; i < len; i++) {\n result = functions[i].call(this, result);\n }\n\n return result;\n } : identity;\n }\n\n /**\n * Builds a function that allows to \"tap\" into the arguments of the original one.\n * This allows to extract simple values from complex ones, transform arguments or simply intercept them.\n * If a \"tapper\" isn't found the argument is passed as it is.\n * @example\n * var someObject = {count: 5};\n * var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0];\n * var getDataAmount = _.tapArgs(_.sum, [_.getKey(\"count\"), _.getKey(\"length\")]);\n *\n * getDataAmount(someObject, someArrayData); // => 15\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.mapArgs|mapArgs}\n * @since 0.3.0\n * @param {Function} fn\n * @param {Function[]} tappers\n * @returns {Function}\n */\n function tapArgs (fn, tappers) {\n return function () {\n var len = arguments.length;\n var tappersLen = tappers.length;\n var args = [];\n\n for (var i = 0; i < len; i++) {\n args.push(i < tappersLen ? tappers[i](arguments[i]) : arguments[i]);\n }\n\n return fn.apply(this, args);\n };\n }\n\n /**\n * Returns a function that will invoke the passed function at most once in the given timespan.
\n * The first call in this case happens as soon as the function is invoked; see also\n * {@link module:lamb.debounce|debounce} for a different behaviour where the first call is delayed.\n * @example\n * var log = _.throttle(console.log.bind(console), 5000);\n *\n * log(\"Hi\"); // console logs \"Hi\"\n * log(\"Hi again\"); // nothing happens\n * // after five seconds\n * log(\"Hello world\"); // console logs \"Hello world\"\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.debounce|debounce}\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} timespan - Expressed in milliseconds.\n * @returns {Function}\n */\n function throttle (fn, timespan) {\n var result;\n var lastCall = 0;\n\n return function () {\n var now = Date.now();\n\n if (now - lastCall >= timespan) {\n lastCall = now;\n result = fn.apply(this, arguments);\n }\n\n return result;\n };\n }\n\n /**\n * Builds a function that passes only one argument to the given function.
\n * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize},\n * exposed for convenience.\n * @example\n * var weights = [\"2 Kg\", \"10 Kg\", \"1 Kg\", \"7 Kg\"];\n *\n * _.map(weights, _.unary(parseInt)) // => [2, 10, 1, 7]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.aritize|aritize}\n * @see {@link module:lamb.binary|binary}\n * @since 0.10.0\n * @param {Function} fn\n * @returns {Function}\n */\n function unary (fn) {\n return function (a) {\n return fn.call(this, a);\n };\n }\n\n lamb.application = application;\n lamb.apply = apply;\n lamb.applyTo = applyTo;\n lamb.aritize = aritize;\n lamb.asPartial = asPartial;\n lamb.binary = binary;\n lamb.collect = collect;\n lamb.curry = curry;\n lamb.curryRight = curryRight;\n lamb.curryable = curryable;\n lamb.curryableRight = curryableRight;\n lamb.debounce = debounce;\n lamb.flip = flip;\n lamb.getArgAt = getArgAt;\n lamb.invoker = invoker;\n lamb.invokerOn = invokerOn;\n lamb.mapArgs = mapArgs;\n lamb.pipe = pipe;\n lamb.tapArgs = tapArgs;\n lamb.throttle = throttle;\n lamb.unary = unary;\n\n /**\n * Creates an array with all the enumerable properties of the given object.\n * @example Showing the difference with {@link module:lamb.keys|keys}:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3},\n * d: {value: 4, enumerable: true}\n * });\n *\n * _.keys(foo) // => [\"d\"]\n * _.enumerables(foo) // => [\"d\", \"a\"]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.keys|keys}\n * @since 0.12.0\n * @param {Object} obj\n * @returns {String[]}\n */\n var enumerables = _unsafeKeyListFrom(_safeEnumerables);\n\n /**\n * Builds an object from a list of key / value pairs like the one\n * returned by {@link module:lamb.pairs|pairs} or {@link module:lamb.ownPairs|ownPairs}.
\n * In case of duplicate keys the last key / value pair is used.\n * @example\n * _.fromPairs([[\"a\", 1], [\"b\", 2], [\"c\", 3]]) // => {\"a\": 1, \"b\": 2, \"c\": 3}\n * _.fromPairs([[\"a\", 1], [\"b\", 2], [\"a\", 3]]) // => {\"a\": 3, \"b\": 2}\n * _.fromPairs([[1], [void 0, 2], [null, 3]]) // => {\"1\": undefined, \"undefined\": 2, \"null\": 3}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.ownPairs|ownPairs}, {@link module:lamb.pairs|pairs}\n * @since 0.8.0\n * @param {Array>} pairsList\n * @returns {Object}\n */\n function fromPairs (pairsList) {\n var result = {};\n\n forEach(pairsList, function (pair) {\n result[pair[0]] = pair[1];\n });\n\n return result;\n }\n\n /**\n * Makes an object immutable by recursively calling [Object.freeze]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze}\n * on its members.
\n * Any attempt to extend or modify the object can throw a TypeError or fail silently,\n * depending on the environment and the [strict mode]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode} directive.\n * @example\n * var user = _.immutable({\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * username: \"jdoe\",\n * password: \"abc123\"\n * },\n * luckyNumbers: [13, 17]\n * });\n *\n * // All of these statements will fail and possibly\n * // throw a TypeError (see the function description)\n * user.name = \"Joe\";\n * delete user.name;\n * user.newProperty = [];\n * user.login.password = \"foo\";\n * user.luckyNumbers.push(-13);\n *\n * @memberof module:lamb\n * @category Object\n * @since 0.8.0\n * @param {Object} obj\n * @returns {Object}\n */\n function immutable (obj) {\n return _immutable(obj, []);\n }\n\n /**\n * Retrieves the list of the own enumerable properties of an object.
\n * Although [Object.keys]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys}\n * is already present in ECMAScript 5, its behaviour changed in the subsequent specifications\n * of the standard.
\n * This function shims the ECMAScript 6 version, by forcing a conversion to\n * object for any value but null and undefined.\n * @example Showing the difference with {@link module:lamb.enumerables|enumerables}:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3},\n * d: {value: 4, enumerable: true}\n * });\n *\n * _.enumerables(foo) // => [\"d\", \"a\"]\n * _.keys(foo) // => [\"d\"]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.enumerables|enumerables}\n * @since 0.25.1\n * @param {Object} obj\n * @returns {String[]}\n */\n var keys = _unsafeKeyListFrom(_safeKeys);\n\n /**\n * Builds an object from the two given lists, using the first one as keys and the last\n * one as values.
\n * If the list of keys is longer than the values one, the keys will be created with\n * undefined values.
\n * If more values than keys are supplied, the extra values will be ignored.\n * @example\n * _.make([\"a\", \"b\", \"c\"], [1, 2, 3]) // => {a: 1, b: 2, c: 3}\n * _.make([\"a\", \"b\", \"c\"], [1, 2]) // => {a: 1, b: 2, c: undefined}\n * _.make([\"a\", \"b\"], [1, 2, 3]) // => {a: 1, b: 2}\n * _.make([null, void 0, 2], [1, 2, 3]) // => {\"null\": 1, \"undefined\": 2, \"2\": 3}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.tear|tear}, {@link module:lamb.tearOwn|tearOwn} for the reverse operation\n * @since 0.8.0\n * @param {String[]} names\n * @param {ArrayLike} values\n * @returns {Object}\n */\n function make (names, values) {\n var result = {};\n var valuesLen = values.length;\n\n for (var i = 0, len = names.length; i < len; i++) {\n result[names[i]] = i < valuesLen ? values[i] : void 0;\n }\n\n return result;\n }\n\n /**\n * Creates a new object by applying the given function\n * to all enumerable properties of the source one.\n * @example\n * var weights = {\n * john: \"72.5 Kg\",\n * jane: \"52.3 Kg\"\n * };\n *\n * _.mapValues(weights, parseFloat) // => {john: 72.5, jane: 52.3}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.mapValuesWith|mapValuesWith}\n * @since 0.54.0\n * @param {Object} source\n * @param {ObjectIteratorCallback} fn\n * @returns {Object}\n */\n function mapValues (source, fn) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n var result = {};\n\n for (var key in source) {\n result[key] = fn(source[key], key, source);\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.mapValues|mapValues}.
\n * Expects a mapping function to build a new function waiting for the\n * object to act upon.\n * @example\n * var incValues = _.mapValuesWith(_.add(1));\n * var results = {\n * first: 10,\n * second: 5,\n * third: 3\n * };\n *\n * incValues(results) // => {first: 11, second: 6, third: 4}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.mapValues|mapValues}\n * @since 0.54.0\n * @function\n * @param {ObjectIteratorCallback} fn\n * @returns {Function}\n */\n var mapValuesWith = _curry2(mapValues, true);\n\n /**\n * Merges the enumerable properties of the provided sources into a new object.
\n * In case of key homonymy the last source has precedence over the first.\n * @example\n * _.merge({a: 1, b: 3}, {b: 5, c: 4}) // => {a: 1, b: 5, c: 4}\n *\n * @example Array-like objects will be transformed to objects with numbers as keys:\n * _.merge([1, 2], {a: 2}) // => {\"0\": 1, \"1\": 2, a: 2}\n * _.merge(\"foo\", {a: 2}) // => {\"0\": \"f\", \"1\": \"o\", \"2\": \"o\", a: 2}\n *\n * @example Every other non-nil value will be treated as an empty object:\n * _.merge({a: 2}, 99) // => {a: 2}\n * _.merge({a: 2}, NaN) // => {a: 2}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.mergeOwn|mergeOwn} to merge own properties only\n * @since 0.10.0\n * @function\n * @param {Object} a\n * @param {Object} b\n * @returns {Object}\n */\n var merge = partial(_merge, [enumerables]);\n\n /**\n * Same as {@link module:lamb.merge|merge}, but only the own properties of the\n * sources are taken into account.\n * @example Showing the difference with merge:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * var bar = {d: 4};\n *\n * _.merge(foo, bar) // => {a: 1, b: 2, c: 3, d: 4}\n * _.mergeOwn(foo, bar) // => {c: 3, d: 4}\n *\n * @example Array-like objects will be transformed to objects with numbers as keys:\n * _.mergeOwn([1, 2], {a: 2}) // => {\"0\": 1, \"1\": 2, a: 2}\n * _.mergeOwn(\"foo\", {a: 2}) // => {\"0\": \"f\", \"1\": \"o\", \"2\": \"o\", a: 2}\n *\n * @example Every other non-nil value will be treated as an empty object:\n * _.mergeOwn({a: 2}, 99) // => {a: 2}\n * _.mergeOwn({a: 2}, NaN) // => {a: 2}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.merge|merge} to merge all enumerable properties\n * @since 0.12.0\n * @function\n * @param {Object} a\n * @param {Object} b\n * @returns {Object}\n */\n var mergeOwn = partial(_merge, [keys]);\n\n /**\n * Same as {@link module:lamb.pairs|pairs}, but only the own enumerable properties of the object are\n * taken into account.
\n * See also {@link module:lamb.fromPairs|fromPairs} for the reverse operation.\n * @example Showing the difference with pairs:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * _.pairs(foo) // => [[\"c\", 3], [\"b\", 2], [\"a\", 1]]\n * _.ownPairs(foo) // => [[\"c\", 3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.pairs|pairs}\n * @see {@link module:lamb.fromPairs|fromPairs}\n * @since 0.12.0\n * @param {Object} obj\n * @returns {Array>}\n */\n var ownPairs = _pairsFrom(keys);\n\n /**\n * Same as {@link module:lamb.values|values}, but only the own enumerable properties of the object are\n * taken into account.
\n * @example Showing the difference with values:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * _.values(foo) // => [3, 2, 1]\n * _.ownValues(foo) // => [3]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.values|values}\n * @since 0.12.0\n * @param {Object} obj\n * @returns {Array}\n */\n var ownValues = _valuesFrom(keys);\n\n /**\n * Converts an object into an array of key / value pairs of its enumerable properties.
\n * See also {@link module:lamb.ownPairs|ownPairs} for picking only the own enumerable\n * properties and {@link module:lamb.fromPairs|fromPairs} for the reverse operation.\n * @example\n * _.pairs({a: 1, b: 2, c: 3}) // => [[\"a\", 1], [\"b\", 2], [\"c\", 3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.ownPairs|ownPairs}\n * @see {@link module:lamb.fromPairs|fromPairs}\n * @since 0.8.0\n * @param {Object} obj\n * @returns {Array>}\n */\n var pairs = _pairsFrom(enumerables);\n\n /**\n * Returns an object containing only the specified properties of the given object.
\n * Non existent properties will be ignored.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n *\n * _.pick(user, [\"name\", \"age\"]) // => {\"name\": \"john\", \"age\": 30};\n * _.pick(user, [\"name\", \"email\"]) // => {\"name\": \"john\"}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pickIf|pickIf}, {@link module:lamb.pickKeys|pickKeys}\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf}\n * @since 0.1.0\n * @param {Object} source\n * @param {String[]} whitelist\n * @returns {Object}\n */\n function pick (source, whitelist) {\n var result = {};\n\n for (var i = 0, len = whitelist.length, key; i < len; i++) {\n key = whitelist[i];\n\n if (has(source, key)) {\n result[key] = source[key];\n }\n }\n\n return result;\n }\n\n /**\n * Builds a function expecting an object whose enumerable properties will be checked\n * against the given predicate.
\n * The properties satisfying the predicate will be included in the resulting object.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n * var pickIfIsString = _.pickIf(_.isType(\"String\"));\n *\n * pickIfIsString(user) // => {name: \"john\", surname: \"doe\"}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys},\n * {@link module:lamb.skipIf|skipIf}\n * @since 0.1.0\n * @param {ObjectIteratorCallback} predicate\n * @returns {Function}\n */\n function pickIf (predicate) {\n return function (source) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n var result = {};\n\n for (var key in source) {\n if (predicate(source[key], key, source)) {\n result[key] = source[key];\n }\n }\n\n return result;\n };\n }\n\n /**\n * A curried version of {@link module:lamb.pick|pick}, expecting a whitelist of keys to build\n * a function waiting for the object to act upon.\n * @example\n * var user = {id: 1, name: \"Jane\", surname: \"Doe\", active: false};\n * var getUserInfo = _.pickKeys([\"id\", \"active\"]);\n *\n * getUserInfo(user) // => {id: 1, active: false}\n *\n * @example A useful composition with mapWith:\n * var users = [\n * {id: 1, name: \"Jane\", surname: \"Doe\", active: false},\n * {id: 2, name: \"John\", surname: \"Doe\", active: true},\n * {id: 3, name: \"Mario\", surname: \"Rossi\", active: true},\n * {id: 4, name: \"Paolo\", surname: \"Bianchi\", active: false}\n * ];\n * var select = _.compose(_.mapWith, _.pickKeys);\n * var selectUserInfo = select([\"id\", \"active\"]);\n *\n * selectUserInfo(users) // =>\n * // [\n * // {id: 1, active: false},\n * // {id: 2, active: true},\n * // {id: 3, active: true},\n * // {id: 4, active: false}\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickIf|pickIf}\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys},\n * {@link module:lamb.skipIf|skipIf}\n * @since 0.35.0\n * @param {String[]} whitelist\n * @returns {Function}\n */\n var pickKeys = _curry2(pick, true);\n\n /**\n * Creates a copy of the given object with its enumerable keys renamed as\n * indicated in the provided lookup table.\n * @example\n * var person = {\"firstName\": \"John\", \"lastName\": \"Doe\"};\n * var keysMap = {\"firstName\": \"name\", \"lastName\": \"surname\"};\n *\n * _.rename(person, keysMap) // => {\"name\": \"John\", \"surname\": \"Doe\"}\n *\n * @example It's safe using it to swap keys:\n * var keysMap = {\"firstName\": \"lastName\", \"lastName\": \"firstName\"};\n *\n * _.rename(person, keysMap) // => {\"lastName\": \"John\", \"firstName\": \"Doe\"}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.renameKeys|renameKeys}, {@link module:lamb.renameWith|renameWith}\n * @since 0.26.0\n * @param {Object} source\n * @param {Object} keysMap\n * @returns {Object}\n */\n function rename (source, keysMap) {\n keysMap = Object(keysMap);\n var result = {};\n var oldKeys = enumerables(source);\n\n for (var prop in keysMap) {\n if (~oldKeys.indexOf(prop)) {\n result[keysMap[prop]] = source[prop];\n }\n }\n\n for (var i = 0, len = oldKeys.length, key; i < len; i++) {\n key = oldKeys[i];\n\n if (!(key in keysMap || key in result)) {\n result[key] = source[key];\n }\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.rename|rename} expecting a\n * keysMap to build a function waiting for the object to act upon.\n * @example\n * var persons = [\n * {\"firstName\": \"John\", \"lastName\": \"Doe\"},\n * {\"first_name\": \"Mario\", \"last_name\": \"Rossi\"},\n * ];\n * var normalizeKeys = _.renameKeys({\n * \"firstName\": \"name\",\n * \"first_name\": \"name\",\n * \"lastName\": \"surname\",\n * \"last_name\": \"surname\"\n * });\n *\n * _.map(persons, normalizeKeys) // =>\n * // [\n * // {\"name\": \"John\", \"surname\": \"Doe\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameWith|renameWith}\n * @since 0.26.0\n * @param {Object} keysMap\n * @returns {Function}\n */\n var renameKeys = _curry2(rename, true);\n\n /**\n * Uses the provided function as a keysMap generator and returns\n * a function expecting the object whose keys we want to {@link module:lamb.rename|rename}.\n * @example\n * var person = {\"NAME\": \"John\", \"SURNAME\": \"Doe\"};\n * var arrayToLower = _.mapWith(_.invoker(\"toLowerCase\"));\n * var makeLowerKeysMap = function (source) {\n * var sourceKeys = _.keys(source);\n *\n * return _.make(sourceKeys, arrayToLower(sourceKeys));\n * };\n * var lowerKeysFor = _.renameWith(makeLowerKeysMap);\n *\n * lowerKeysFor(person) // => {\"name\": \"John\", \"surname\": \"doe\"};\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameKeys|renameKeys}\n * @since 0.26.0\n * @param {Function} fn\n * @returns {Function}\n */\n function renameWith (fn) {\n return function (source) {\n return rename(source, fn(source));\n };\n }\n\n /**\n * Returns a copy of the source object without the specified properties.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n *\n * _.skip(user, [\"name\", \"age\"]) // => {surname: \"doe\"};\n * _.skip(user, [\"name\", \"email\"]) // => {surname: \"doe\", age: 30};\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.skipKeys|skipKeys}, {@link module:lamb.skipIf|skipIf}\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys},\n * {@link module:lamb.pickIf|pickIf}\n * @since 0.1.0\n * @param {Object} source\n * @param {String[]} blacklist\n * @returns {Object}\n */\n function skip (source, blacklist) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n var result = {};\n var props = make(blacklist, []);\n\n for (var key in source) {\n if (!(key in props)) {\n result[key] = source[key];\n }\n }\n\n return result;\n }\n\n /**\n * Builds a function expecting an object whose enumerable properties will be checked\n * against the given predicate.
\n * The properties satisfying the predicate will be omitted in the resulting object.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n * var skipIfIstring = _.skipIf(_.isType(\"String\"));\n *\n * skipIfIstring(user) // => {age: 30}\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys}\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys},\n * {@link module:lamb.pickIf|pickIf}\n * @since 0.1.0\n * @param {ObjectIteratorCallback} predicate\n * @returns {Function}\n */\n var skipIf = compose(pickIf, not);\n\n /**\n * A curried version of {@link module:lamb.skip|skip}, expecting a blacklist of keys to build\n * a function waiting for the object to act upon.\n * @example\n * var user = {id: 1, name: \"Jane\", surname: \"Doe\", active: false};\n * var getUserInfo = _.skipKeys([\"name\", \"surname\"]);\n *\n * getUserInfo(user) // => {id: 1, active: false}\n *\n * @example A useful composition with mapWith:\n * var users = [\n * {id: 1, name: \"Jane\", surname: \"Doe\", active: false},\n * {id: 2, name: \"John\", surname: \"Doe\", active: true},\n * {id: 3, name: \"Mario\", surname: \"Rossi\", active: true},\n * {id: 4, name: \"Paolo\", surname: \"Bianchi\", active: false}\n * ];\n * var discard = _.compose(_.mapWith, _.skipKeys);\n * var discardNames = discard([\"name\", \"surname\"]);\n *\n * discardNames(users) // =>\n * // [\n * // {id: 1, active: false},\n * // {id: 2, active: true},\n * // {id: 3, active: true},\n * // {id: 4, active: false}\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf}\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys},\n * {@link module:lamb.pickIf|pickIf}\n * @since 0.35.0\n * @param {String[]} blacklist\n * @returns {Function}\n */\n var skipKeys = _curry2(skip, true);\n\n /**\n * Tears an object apart by transforming it in an array of two lists: one containing\n * its enumerable keys, the other containing the corresponding values.
\n * Although this \"tearing apart\" may sound as a rather violent process, the source\n * object will be unharmed.\n * @example\n * _.tear({a: 1, b: 2, c: 3}) // => [[\"a\", \"b\", \"c\"], [1, 2, 3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.tearOwn|tearOwn}\n * @see {@link module:lamb.make|make} for the reverse operation\n * @since 0.8.0\n * @param {Object} obj\n * @returns {Array}\n */\n var tear = _tearFrom(enumerables);\n\n /**\n * Same as {@link module:lamb.tear|tear}, but only the own properties of the object are\n * taken into account.\n * @example Showing the difference with tear:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * _.tear(foo) // => [[\"c\", \"b\", \"a\"], [3, 2, 1]]\n * _.tearOwn(foo) // => [[\"c\"], [3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.tear|tear}\n * @see {@link module:lamb.make|make} for the reverse operation\n * @since 0.12.0\n * @param {Object} obj\n * @returns {Array}\n */\n var tearOwn = _tearFrom(keys);\n\n /**\n * Generates an array with the values of the enumerable properties of the given object.
\n * See also {@link module:lamb.ownValues|ownValues} to pick only from the own properties of the object.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n *\n * _.values(user) // => [\"john\", \"doe\", 30]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.ownValues|ownValues}\n * @since 0.1.0\n * @param {Object} obj\n * @returns {Array}\n */\n var values = _valuesFrom(enumerables);\n\n lamb.enumerables = enumerables;\n lamb.fromPairs = fromPairs;\n lamb.immutable = immutable;\n lamb.keys = keys;\n lamb.make = make;\n lamb.mapValues = mapValues;\n lamb.mapValuesWith = mapValuesWith;\n lamb.merge = merge;\n lamb.mergeOwn = mergeOwn;\n lamb.ownPairs = ownPairs;\n lamb.ownValues = ownValues;\n lamb.pairs = pairs;\n lamb.pick = pick;\n lamb.pickIf = pickIf;\n lamb.pickKeys = pickKeys;\n lamb.rename = rename;\n lamb.renameKeys = renameKeys;\n lamb.renameWith = renameWith;\n lamb.skip = skip;\n lamb.skipIf = skipIf;\n lamb.skipKeys = skipKeys;\n lamb.tear = tear;\n lamb.tearOwn = tearOwn;\n lamb.values = values;\n\n /**\n * Builds a checker function meant to be used with\n * {@link module:lamb.validate|validate}.
\n * Note that the function accepts multiple keyPaths as a means to\n * compare their values. In other words all the received keyPaths will be\n * passed as arguments to the predicate to run the test.
\n * If you want to run the same single property check with multiple properties, you should build\n * multiple checkers and combine them with {@link module:lamb.validate|validate}.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * username: \"jdoe\",\n * password: \"abc123\",\n * passwordConfirm: \"abc123\"\n * }\n * };\n * var pwdMatch = _.checker(\n * _.areSame,\n * \"Passwords don't match\",\n * [\"login.password\", \"login.passwordConfirm\"]\n * );\n *\n * pwdMatch(user) // => []\n *\n * var newUser = _.setPathIn(user, \"login.passwordConfirm\", \"avc123\");\n *\n * pwdMatch(newUser) // => [\"Passwords don't match\", [\"login.password\", \"login.passwordConfirm\"]]\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.validate|validate}, {@link module:lamb.validateWith|validateWith}\n * @since 0.1.0\n * @param {Function} predicate - The predicate to test the object properties\n * @param {String} message - The error message\n * @param {String[]} keyPaths - The array of keys, or {@link module:lamb.getPathIn|paths}, to test.\n * @param {String} [pathSeparator=\".\"]\n * @returns {Function} A checker function which returns an error in the form\n * [\"message\", [\"propertyA\", \"propertyB\"]] or an empty array.\n */\n function checker (predicate, message, keyPaths, pathSeparator) {\n return function (obj) {\n var getValues = partial(getPathIn, [obj, _, pathSeparator]);\n\n return predicate.apply(obj, map(keyPaths, getValues)) ? [] : [message, keyPaths];\n };\n }\n\n /**\n * Verifies the existence of a property in an object.\n * @example\n * var user1 = {name: \"john\"};\n *\n * _.has(user1, \"name\") // => true\n * _.has(user1, \"surname\") // => false\n * _.has(user1, \"toString\") // => true\n *\n * var user2 = Object.create(null);\n *\n * // not inherited through the prototype chain\n * _.has(user2, \"toString\") // => false\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.hasKey|hasKey}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n function has (obj, key) {\n if (typeof obj !== \"object\" && !isUndefined(obj)) {\n obj = Object(obj);\n }\n\n return key in obj;\n }\n\n /**\n * Curried version of {@link module:lamb.has|has}.
\n * Returns a function expecting the object to check against the given key.\n * @example\n * var user1 = {name: \"john\"};\n * var user2 = {};\n * var hasName = _.hasKey(\"name\");\n *\n * hasName(user1) // => true\n * hasName(user2) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.has|has}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {String} key\n * @returns {Function}\n */\n var hasKey = _curry2(has, true);\n\n /**\n * Builds a predicate expecting an object to check against the given key / value pair.
\n * The value check is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var hasTheCorrectAnswer = _.hasKeyValue(\"answer\", 42);\n *\n * hasTheCorrectAnswer({answer: 2}) // false\n * hasTheCorrectAnswer({answer: 42}) // true\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.hasPathValue|hasPathValue}\n * @since 0.1.0\n * @param {String} key\n * @param {*} value\n * @returns {Function}\n */\n function hasKeyValue (key, value) {\n return function (obj) {\n return isUndefined(value) ? has(obj, key) && obj[key] === value : areSVZ(value, obj[key]);\n };\n }\n\n /**\n * Verifies if an object has the specified property and that the property isn't inherited through\n * the prototype chain.
\n * @example Comparison with has:\n * var user = {name: \"john\"};\n *\n * _.has(user, \"name\") // => true\n * _.has(user, \"surname\") // => false\n * _.has(user, \"toString\") // => true\n *\n * _.hasOwn(user, \"name\") // => true\n * _.hasOwn(user, \"surname\") // => false\n * _.hasOwn(user, \"toString\") // => false\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n var hasOwn = generic(_objectProto.hasOwnProperty);\n\n /**\n * Curried version of {@link module:lamb.hasOwn|hasOwn}.
\n * Returns a function expecting the object to check against the given key.\n * @example\n * var user = {name: \"john\"};\n * var hasOwnName = _.hasOwnKey(\"name\");\n * var hasOwnToString = _.hasOwnToString(\"toString\");\n *\n * hasOwnName(user) // => true\n * hasOwnToString(user) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.hasOwn|hasOwn}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {String} key\n * @returns {Function}\n */\n var hasOwnKey = _curry2(hasOwn, true);\n\n /**\n * Builds a predicate to check if the given path exists in an object and holds the desired value.
\n * The value check is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.
\n * Note that the function will check even non-enumerable properties.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * personal: {\n * age: 25,\n * gender: \"M\"\n * },\n * scores: [\n * {id: 1, value: 10, passed: false},\n * {id: 2, value: 20, passed: false},\n * {id: 3, value: 30, passed: true}\n * ]\n * };\n *\n * var isMale = _.hasPathValue(\"personal.gender\", \"M\");\n * var hasPassedFirstTest = _.hasPathValue(\"scores.0.passed\", true);\n * var hasPassedLastTest = _.hasPathValue(\"scores.-1.passed\", true);\n *\n * isMale(user) // => true\n * hasPassedFirstTest(user) // => false\n * hasPassedLastTest(user) // => true\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.hasKeyValue|hasKeyValue}\n * @since 0.41.0\n * @param {String} path\n * @param {*} value\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function hasPathValue (path, value, separator) {\n return function (obj) {\n var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true);\n\n return pathInfo.isValid && areSVZ(pathInfo.target, value);\n };\n }\n\n /**\n * Builds a predicate to check if the given key satisfies the desired condition\n * on an object.\n * @example\n * var users = [\n * {name: \"John\", age: 25},\n * {name: \"Jane\", age: 15},\n * ];\n * var isAdult = _.keySatisfies(_.isGTE(18), \"age\");\n *\n * isAdult(users[0]) // => true\n * isAdult(users[1]) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pathSatisfies|pathSatisfies}\n * @since 0.45.0\n * @param {Function} predicate\n * @param {String} key\n * @returns {Function}\n */\n function keySatisfies (predicate, key) {\n return function (obj) {\n return predicate.call(this, obj[key]);\n };\n }\n\n /**\n * Builds a partial application of {@link module:lamb.pathExistsIn|pathExistsIn} using the given\n * path and the optional separator. The resulting function expects the object to check.
\n * Note that the function will check even non-enumerable properties.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * address: {\n * city: \"New York\"\n * },\n * scores: [10, 20, 15]\n * };\n *\n * var hasCity = _.pathExists(\"address.city\");\n * var hasCountry = _.pathExists(\"address.country\");\n * var hasAtLeastThreeScores = _.pathExists(\"scores.2\");\n *\n * hasCity(user) // => true\n * hasCountry(user) // => false\n * hasAtLeastThreeScores(user) // => true\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @since 0.43.0\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n var pathExists = _makePartial3(pathExistsIn);\n\n /**\n * Checks if the provided path exists in the given object.
\n * Note that the function will check even non-enumerable properties.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * address: {\n * city: \"New York\"\n * },\n * scores: [10, 20, 15]\n * };\n *\n * _.pathExistsIn(user, \"address.city\") // => true\n * _.pathExistsIn(user, \"address.country\") // => false\n * _.pathExistsIn(user, \"scores.1\") // => true\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pathExists|pathExists}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @since 0.43.0\n * @param {Object} obj\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Boolean}\n */\n function pathExistsIn (obj, path, separator) {\n return _getPathInfo(obj, _toPathParts(path, separator), true).isValid;\n }\n\n /**\n * Builds a predicate that verifies if a condition is satisfied for the given\n * path in an object.
\n * Like the other \"path functions\" you can use integers in the path, even\n * negative ones, to refer to array-like object indexes, but the priority will\n * be given to existing object keys.\n * @example\n * var user = {\n * name: \"John\",\n * performance: {\n * scores: [1, 5, 10]\n * }\n * };\n *\n * var gotAnHighScore = _.pathSatisfies(_.contains(10), \"performance.scores\");\n * var hadAGoodStart = _.pathSatisfies(_.isGT(6), \"performance.scores.0\");\n *\n * gotAnHighScore(user) // => true\n * hadAGoodStart(user) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.keySatisfies|keySatisfies}\n * @since 0.45.0\n * @param {Function} predicate\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function pathSatisfies (predicate, path, separator) {\n return function (obj) {\n var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true);\n\n return predicate.call(this, pathInfo.target);\n };\n }\n\n /**\n * Validates an object with the given list of {@link module:lamb.checker|checker} functions.\n * @example\n * var hasContent = function (s) { return s.trim().length > 0; };\n * var userCheckers = [\n * _.checker(hasContent, \"Name is required\", [\"name\"]),\n * _.checker(hasContent, \"Surname is required\", [\"surname\"]),\n * _.checker(_.isGTE(18), \"Must be at least 18 years old\", [\"age\"])\n * ];\n *\n * var user1 = {name: \"john\", surname: \"doe\", age: 30};\n * var user2 = {name: \"jane\", surname: \"\", age: 15};\n *\n * _.validate(user1, userCheckers) // => []\n * _.validate(user2, userCheckers) // =>\n * // [\n * // [\"Surname is required\", [\"surname\"]],\n * // [\"Must be at least 18 years old\", [\"age\"]]\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.validateWith|validateWith}\n * @see {@link module:lamb.checker|checker}\n * @since 0.1.0\n * @param {Object} obj\n * @param {Function[]} checkers\n * @returns {Array>} An array of errors in the form returned by\n * {@link module:lamb.checker|checker}, or an empty array.\n */\n function validate (obj, checkers) {\n return reduce(checkers, function (errors, _checker) {\n var result = _checker(obj);\n\n result.length && errors.push(result);\n\n return errors;\n }, []);\n }\n\n /**\n * A curried version of {@link module:lamb.validate|validate} accepting a list of\n * {@link module:lamb.checker|checkers} and returning a function expecting the object to validate.\n * @example\n * var hasContent = function (s) { return s.trim().length > 0; };\n * var userCheckers = [\n * _.checker(hasContent, \"Name is required\", [\"name\"]),\n * _.checker(hasContent, \"Surname is required\", [\"surname\"]),\n * _.checker(_.isGTE(18), \"Must be at least 18 years old\", [\"age\"])\n * ];\n * var validateUser = _.validateWith(userCheckers);\n *\n * var user1 = {name: \"john\", surname: \"doe\", age: 30};\n * var user2 = {name: \"jane\", surname: \"\", age: 15};\n *\n * validateUser(user1) // => []\n * validateUser(user2) // =>\n * // [\n * // [\"Surname is required\", [\"surname\"]],\n * // [\"Must be at least 18 years old\", [\"age\"]]\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.validate|validate}\n * @see {@link module:lamb.checker|checker}\n * @since 0.1.0\n * @param {Function[]} checkers\n * @returns {Function}\n */\n var validateWith = _curry2(validate, true);\n\n lamb.checker = checker;\n lamb.has = has;\n lamb.hasKey = hasKey;\n lamb.hasKeyValue = hasKeyValue;\n lamb.hasOwn = hasOwn;\n lamb.hasOwnKey = hasOwnKey;\n lamb.hasPathValue = hasPathValue;\n lamb.keySatisfies = keySatisfies;\n lamb.pathExists = pathExists;\n lamb.pathExistsIn = pathExistsIn;\n lamb.pathSatisfies = pathSatisfies;\n lamb.validate = validate;\n lamb.validateWith = validateWith;\n\n /**\n * Pads a string to the desired length with the given char starting from the beginning of the string.\n * @example\n * _.padLeft(\"foo\", \"-\", 0) // => \"foo\"\n * _.padLeft(\"foo\", \"-\", -1) // => \"foo\"\n * _.padLeft(\"foo\", \"-\", 5) // => \"--foo\"\n * _.padLeft(\"foo\", \"-\", 3) // => \"foo\"\n * _.padLeft(\"foo\", \"ab\", 7) // => \"aaaafoo\"\n * _.padLeft(\"foo\", \"\", 5) // => \"foo\"\n * _.padLeft(\"\", \"-\", 5) // => \"-----\"\n *\n * @memberof module:lamb\n * @category String\n * @see {@link module:lamb.padRight|padRight}\n * @since 0.1.0\n * @param {String} source\n * @param {String} char - The padding char. If a string is passed only the first char is used.\n * @param {Number} len\n * @returns {String}\n */\n function padLeft (source, char, len) {\n return _getPadding(source, char, len) + source;\n }\n\n /**\n * Pads a string to the desired length with the given char starting from the end of the string.\n * @example\n * _.padRight(\"foo\", \"-\", 0) // => \"foo\"\n * _.padRight(\"foo\", \"-\", -1) // => \"foo\"\n * _.padRight(\"foo\", \"-\", 5) // => \"foo--\"\n * _.padRight(\"foo\", \"-\", 3) // => \"foo\"\n * _.padRight(\"foo\", \"ab\", 7) // => \"fooaaaa\"\n * _.padRight(\"foo\", \"\", 5) // => \"foo\"\n * _.padRight(\"\", \"-\", 5) // => \"-----\"\n *\n * @memberof module:lamb\n * @category String\n * @see {@link module:lamb.padLeft|padLeft}\n * @since 0.1.0\n * @param {String} source\n * @param {String} char - The padding char. If a string is passed only the first char is used.\n * @param {Number} len\n * @returns {String}\n */\n function padRight (source, char, len) {\n return source + _getPadding(source, char, len);\n }\n\n /**\n * Builds a new string by repeating the source string the desired amount of times.
\n * Note that unlike the current ES6 proposal for\n * [String.prototype.repeat]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat},\n * this function doesn't throw a RangeError if times is negative,\n * but returns an empty string instead.\n * @example\n * _.repeat(\"Hello\", -1) // => \"\"\n * _.repeat(\"Hello\", 1) // => \"Hello\"\n * _.repeat(\"Hello\", 3) // => \"HelloHelloHello\"\n *\n * @memberof module:lamb\n * @category String\n * @since 0.1.0\n * @param {String} source\n * @param {Number} times\n * @returns {String}\n */\n function repeat (source, times) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"string\");\n }\n\n return _repeat(source, Math.floor(times));\n }\n\n /**\n * Builds a predicate expecting a string to test against the given regular expression pattern.\n * @example\n * var hasNumbersOnly = _.testWith(/^\\d+$/);\n *\n * hasNumbersOnly(\"123\") // => true\n * hasNumbersOnly(\"123 Kg\") // => false\n *\n * @memberof module:lamb\n * @category String\n * @since 0.1.0\n * @param {RegExp} pattern\n * @returns {Function}\n */\n function testWith (pattern) {\n return function (s) {\n return _search(s, pattern) !== -1;\n };\n }\n\n lamb.padLeft = padLeft;\n lamb.padRight = padRight;\n lamb.repeat = repeat;\n lamb.testWith = testWith;\n\n /* istanbul ignore next */\n if (typeof exports === \"object\") {\n module.exports = lamb;\n } else if (typeof define === \"function\" && define.amd) {\n define(function () {\n return lamb;\n });\n } else {\n host.lamb = lamb;\n }\n})(this);\n\n/**\n * @callback AccumulatorCallback\n * @global\n * @param {*} previousValue - The value returned it the last execution of the accumulator or, in the first\n * iteration, the {@link module:lamb.reduce|initialValue} if supplied.\n * @param {*} currentValue - The value being processed in the current iteration.\n * @param {Number} idx - The index of the element being processed.\n * @param {ArrayLike} arrayLike - The list being traversed.\n */\n\n/**\n * The built-in arguments object.\n * @typedef {arguments} arguments\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments|arguments} in Mozilla documentation.\n */\n\n/**\n * The built-in Array object.\n * @typedef {Array} Array\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array|Array} in Mozilla documentation.\n */\n\n/**\n * Any array-like object.\n * @typedef {Array|String|arguments|?} ArrayLike\n * @global\n */\n\n/**\n * The built-in Boolean object.\n * @typedef {Boolean} Boolean\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean|Boolean} in Mozilla documentation.\n */\n\n/**\n * The built-in Date object.\n * @typedef {Date} Date\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date|Date} in Mozilla documentation.\n */\n\n/**\n * The built-in Function object.\n * @typedef {Function} function\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function|Function} and\n * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions|Functions} in Mozilla documentation.\n */\n\n/**\n * @callback ListIteratorCallback\n * @global\n * @param {*} element - The element being evaluated.\n * @param {Number} idx - The index of the element within the list.\n * @param {ArrayLike} arrayLike - The list being traversed.\n */\n\n/**\n * The built-in Number object.\n * @typedef {Number} Number\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number|Number} in Mozilla documentation.\n */\n\n/**\n * The built-in Object object.\n * @typedef {Object} Object\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object|Object} in Mozilla documentation.\n */\n\n/**\n * @callback ObjectIteratorCallback\n * @global\n * @param {*} value - The value of the current property.\n * @param {String} key - The property name.\n * @param {Object} source - The object being traversed.\n */\n\n/**\n * The built-in RegExp object.\n * @typedef {RegExp} RegExp\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp|RegExp} in Mozilla documentation.\n */\n\n/**\n * Represents a sorting criteria used by {@link module:lamb.sortedInsert|sortedInsert},\n * {@link module:lamb.sort|sort} and {@link module:lamb.sortWith|sortWith}, and it's\n * usually built using {@link module:lamb.sorter|sorter} and {@link module:lamb.sorterDesc|sorterDesc}.\n * @typedef {Sorter} Sorter\n * @global\n * @property {Boolean} isDescending\n * @property {Function} compare\n */\n\n/**\n * The built-in String object.\n * @typedef {String} String\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String|String} in Mozilla documentation.\n */\n\n/**\n * The built-in primitive value undefined\n * @typedef {Undefined} Undefined\n * @global\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined|undefined} in Mozilla documentation.\n */\n"]} \ No newline at end of file +{"version":3,"sources":["lamb.js"],"names":["global","factory","exports","module","define","amd","self","lamb","this","__","areSVZ","a","b","binary","fn","call","clamp","n","min","max","NaN","partial","args","Array","isArray","apply","arguments","boundArg","lastIdx","newArgs","argsLen","length","i","len","_makePartial3","shouldAritize","clampWithin","identity","value","compose","MAX_ARRAY_LENGTH","_toArrayLength","forEach","arrayLike","iteratee","generic","Function","bind","isNull","isUndefined","isNil","_curry2","isRightCurry","isSVZ","map","result","mapWith","_makeReducer","step","accumulator","initialValue","nCalls","idx","TypeError","reduce","reduceWith","_toInteger","Math","floor","abs","slice","start","end","begin","upTo","resultLen","sliceAt","objectProtoToString","Object","prototype","toString","type","appendTo","concat","append","isIn","contains","_groupWith","makeValue","element","key","count","countBy","filter","predicate","push","not","uniquesBy","seen","uniques","dropFrom","drop","_getNumConsecutiveHits","_makeArrayChecker","defaultResult","everyIn","every","filterWith","findIndex","find","findWhere","findIndexWhere","flatMap","array","el","arr","v","rLen","flatMapWith","_makeArrayFlattener","isDeep","_flatten","output","j","vLen","flatten","_toNaturalIndex","getIndex","index","getAt","group","groupBy","head","indexBy","init","insert","splice","insertAt","last","_argsToArrayFrom","list","partition","partitionWith","getIn","obj","getKey","pluckKey","pullFrom","values","pull","reduceRight","reduceRightWith","rotate","amount","shift","rotateBy","_setIndex","updater","setAt","aritize","arity","setIndex","shallowFlatten","someIn","some","_compareWith","criteria","criterion","compare","isDescending","_comparer","String","_sorter","reader","comparer","_makeCriterion","_makeCriteria","sorters","sort","sorter","sorterDesc","sortWith","tail","takeFrom","take","transpose","minLen","elementLen","_makeTypeErrorFor","desiredType","toLowerCase","pipe","functions","unionBy","union","updateIndex","zipWithIndex","application","applyTo","_curry","isAutoCurry","_currier","argsHolder","holderLen","newArgsLen","reverse","c","_argsTail","_invoker","boundArgs","methodName","target","method","boundArgsLen","ofs","_checkPredicates","checkAll","predicates","allOf","anyOf","areSame","gt","gte","is","isGT","isGTE","lt","isLT","lte","isLTE","sum","add","subtract","deduct","divide","divideBy","isInteger","multiply","multiplyBy","_forceToNumber","_isOwnEnumerable","propertyIsEnumerable","_safeEnumerables","_isEnumerable","indexOf","_getPathKey","includeNonEnumerables","_getPathInfo","parts","walkNonEnumerables","isValid","_toPathParts","path","separator","split","getPathIn","_unsafeKeyListFrom","getKeys","enumerables","getPath","has","hasKey","hasOwn","hasOwnProperty","hasOwnKey","keys","make","names","valuesLen","mapValues","source","mapValuesWith","_merge","merge","mergeOwn","_keyToPairIn","_pairsFrom","ownPairs","_valuesFrom","ownValues","pairs","pathExistsIn","pathExists","pick","whitelist","pickIf","pickKeys","rename","keysMap","oldKeys","prop","renameKeys","_setIn","setIn","setKey","_setPathIn","partsLen","targetKey","setPathIn","skip","blacklist","props","skipIf","skipKeys","_tearFrom","tear","tearOwn","updateIn","updateKey","updatePathIn","pathInfo","validate","checkers","errors","_checker","validateWith","_repeat","times","_getPadding","char","ceil","_search","search","always","partialRight","difference","other","isNotInOther","dropWhile","intersection","lenA","pluck","sortedInsert","_getInsertionIndex","pivot","takeWhile","updateAt","zip","asPartial","_asPartial","collect","curry","curryable","curryableRight","curryRight","debounce","timespan","timeoutID","debounced","clearTimeout","setTimeout","flip","getArgAt","invoker","invokerOn","mapArgs","mapper","tapArgs","tappers","tappersLen","throttle","lastCall","now","Date","unary","adapter","case","condition","trueFn","falseFn","unless","when","generate","limit","isFinite","isSafeInteger","modulo","randomInt","random","range","remainder","checker","message","keyPaths","pathSeparator","getValues","fromPairs","pairsList","pair","hasKeyValue","hasPathValue","immutable","_immutable","freeze","getOwnPropertyNames","keySatisfies","pathSatisfies","renameWith","setPath","updatePath","padLeft","padRight","repeat","testWith","pattern","s","isInstanceOf","constructor","isType","typeName","defineProperty"],"mappings":";;;;;;;;CAQC,SAAUA,EAAQC,GACI,iBAAZC,SAA0C,oBAAXC,OAAyBF,EAAQC,SACrD,mBAAXE,QAAyBA,OAAOC,IAAMD,OAAO,CAAC,WAAYH,GACvCA,GAAzBD,EAASA,GAAUM,MAAqBC,KAAO,IAHpD,CAIEC,KAAM,SAAUN,GAAW,aAYzB,IAAIO,EAAK,GA0DT,SAASC,EAAQC,EAAGC,GAChB,OAAOD,GAAMA,EAAIC,GAAMA,EAAID,IAAMC,EAmBrC,SAASC,EAAQC,GACb,OAAO,SAAUH,EAAGC,GAChB,OAAOE,EAAGC,KAAKP,KAAMG,EAAGC,IA2BhC,SAASI,EAAOC,EAAGC,EAAKC,GAKpB,OAJAF,GAAKA,GAELE,GAAOA,IADPD,GAAOA,GAIIE,IAEAH,EAAIC,EAAMA,EAAUC,EAAJF,EAAUE,EAAMF,EAiC/C,SAASI,EAASP,EAAIQ,GAClB,OAAO,WACH,IAAKC,MAAMC,QAAQF,GACf,OAAOR,EAAGW,MAAMjB,KAAMkB,WAO1B,IAJA,IAIgBC,EAJZC,EAAU,EACVC,EAAU,GACVC,EAAUR,EAAKS,OAEVC,EAAI,EAAaA,EAAIF,EAASE,IACnCL,EAAWL,EAAKU,GAChBH,EAAQG,GAAKL,IAAalB,EAAKiB,UAAUE,KAAaD,EAG1D,IAAK,IAAIM,EAAMP,UAAUK,OAAQH,EAAUK,EAAKL,IAC5CC,EAAQG,KAAON,UAAUE,GAG7B,OAAOd,EAAGW,MAAMjB,KAAMqB,IAe9B,SAASK,EAAepB,EAAIqB,GACxB,OAAO,SAAUxB,EAAGC,GAGhB,OAAOS,EAFCc,GAAsC,IAArBT,UAAUK,OAAelB,EAAOC,GAAMA,EAE7C,CAACL,EAAIE,EAAGC,KAyBlC,IAAIwB,EAAcF,EAAclB,GAgBhC,SAASqB,EAAUC,GACf,OAAOA,EA6BX,SAASC,EAAS5B,EAAGC,GACjB,OAAOc,UAAUK,OAAS,WACtB,OAAOpB,EAAEI,KAAKP,KAAMI,EAAEa,MAAMjB,KAAMkB,aAClCW,EAGR,IAAIG,EAAmB,WAUvB,SAASC,EAAgBH,GACrB,OAAOtB,EAAMsB,EAAO,EAAGE,KAAsB,EAsBjD,SAASE,EAASC,EAAWC,GACzB,IAAK,IAAIZ,EAAI,EAAGC,EAAMQ,EAAeE,EAAUZ,QAASC,EAAIC,EAAKD,IAC7DY,EAASD,EAAUX,GAAIA,EAAGW,GAuBlC,IAAIE,EAAUC,SAASC,KAAKA,KAAKD,SAAS/B,MAgB1C,SAASiC,EAAQV,GACb,OAAiB,OAAVA,EAiBX,SAASW,EAAaX,GAClB,YAAiB,IAAVA,EAoBX,SAASY,EAAOZ,GACZ,OAAOU,EAAOV,IAAUW,EAAYX,GAUxC,SAASa,EAASrC,EAAIsC,GAClB,OAAO,SAAUzC,GACb,OAAO,SAAUC,GACb,OAAOwC,EAAetC,EAAGC,KAAKP,KAAMI,EAAGD,GAAKG,EAAGC,KAAKP,KAAMG,EAAGC,KAyCzE,IAAIyC,EAAQF,EAAQzC,GAoBpB,SAAS4C,EAAKX,EAAWC,GAIrB,IAHA,IAAIX,EAAMQ,EAAeE,EAAUZ,QAC/BwB,EAAShC,MAAMU,GAEVD,EAAI,EAAGA,EAAIC,EAAKD,IACrBuB,EAAOvB,GAAKY,EAASD,EAAUX,GAAIA,EAAGW,GAG1C,OAAOY,EAqBX,IAAIC,EAAUL,EAAQG,GAAK,GAwE3B,SAASG,EAAcC,GACnB,OAAO,SAAUf,EAAWgB,EAAaC,GACrC,IAEIC,EACAN,EAHAtB,EAAMQ,EAAeE,EAAUZ,QAC/B+B,EAAe,IAATJ,EAAa,EAAIzB,EAAM,EAIjC,GAAyB,IAArBP,UAAUK,OACV8B,EAAS5B,EACTsB,EAASK,MACN,CACH,GAAY,IAAR3B,EACA,MAAM,IAAI8B,UAAU,oDAGxBR,EAASZ,EAAUmB,GACnBA,GAAOJ,EACPG,EAAS5B,EAAM,EAGnB,KAAO4B,IAAUC,GAAOJ,EACpBH,EAASI,EAAYJ,EAAQZ,EAAUmB,GAAMA,EAAKnB,GAGtD,OAAOY,GAsBf,IAAIS,EAASP,EAAa,GAuBtBQ,EAAa/B,EAAc8B,GAAQ,GAQvC,SAASE,EAAY5B,GACjB,IAAIrB,GAAKqB,EAET,OAAIrB,GAAMA,EACC,EACAA,EAAI,GAAM,EACVA,EAEAkD,KAAKC,MAAMD,KAAKE,IAAIpD,KAAOA,EAAI,GAAK,EAAI,GA6BvD,SAASqD,EAAO3B,EAAW4B,EAAOC,GAC9B,IAAIvC,EAAMQ,EAAeE,EAAUZ,QAC/B0C,EAAQP,EAAWK,GACnBG,EAAOR,EAAWM,GAElBC,EAAQ,IACRA,EAAQA,GAASxC,EAAM,EAAIwC,EAAQxC,GAGnCyC,EAAO,EACPA,EAAOA,GAAQzC,EAAM,EAAIyC,EAAOzC,EAClBA,EAAPyC,IACPA,EAAOzC,GAMX,IAHA,IAAI0C,EAAYD,EAAOD,EACnBlB,EAAqB,EAAZoB,EAAgBpD,MAAMoD,GAAa,GAEvC3C,EAAI,EAAGA,EAAI2C,EAAW3C,IAC3BuB,EAAOvB,GAAKW,EAAU8B,EAAQzC,GAGlC,OAAOuB,EA0BX,IAAIqB,EAAU1C,EAAcoC,GAExBO,EAAsBC,OAAOC,UAAUC,SAuB3C,SAASC,EAAM3C,GACX,OAAOuC,EAAoB9D,KAAKuB,GAAOgC,MAAM,GAAI,GAoBrD,SAASY,EAAUvC,EAAWL,GAC1B,OAAOgC,EAAM3B,EAAW,EAAGA,EAAUZ,QAAQoD,OAAO,CAAC7C,IAqBzD,IAAI8C,EAASjC,EAAQ+B,GAAU,GAwB/B,SAASG,EAAM1C,EAAWL,GAGtB,IAFA,IAAIiB,GAAS,EAEJvB,EAAI,EAAGC,EAAMU,EAAUZ,OAAQC,EAAIC,EAAKD,IAC7C,GAAItB,EAAO4B,EAAOK,EAAUX,IAAK,CAC7BuB,GAAS,EACT,MAIR,OAAOA,EAqBX,IAAI+B,EAAWnC,EAAQkC,GAAM,GAQ7B,SAASE,EAAYC,GACjB,OAAO,SAAU7C,EAAWC,GAIxB,IAHA,IAGgB6C,EAASC,EAHrBnC,EAAS,GACTtB,EAAMU,EAAUZ,OAEXC,EAAI,EAAiBA,EAAIC,EAAKD,IAGnCuB,EADAmC,EAAM9C,EADN6C,EAAU9C,EAAUX,GACIA,EAAGW,IACb6C,EAAUjC,EAAOmC,GAAMD,GAGzC,OAAOlC,GA6Bf,IAAIoC,EAAQJ,EAAW,SAAU5E,GAC7B,OAAOA,IAAMA,EAAI,IA4BjBiF,EAAUzC,EAAQwC,GAAO,GAsB7B,SAASE,EAAQlD,EAAWmD,GAIxB,IAHA,IAAI7D,EAAMU,EAAUZ,OAChBwB,EAAS,GAEJvB,EAAI,EAAGA,EAAIC,EAAKD,IACrB8D,EAAUnD,EAAUX,GAAIA,EAAGW,IAAcY,EAAOwC,KAAKpD,EAAUX,IAGnE,OAAOuB,EAkBX,SAASyC,EAAKF,GACV,OAAO,WACH,OAAQA,EAAUrE,MAAMjB,KAAMkB,YAgCtC,SAASuE,EAAWrD,GAChB,OAAO,SAAUD,GAGb,IAFA,IAEmDL,EAF/CiB,EAAS,GAEJvB,EAAI,EAAGC,EAAMU,EAAUZ,OAAQmE,EAAO,GAAWlE,EAAIC,EAAKD,IAG1DqD,EAAKa,EAFV5D,EAAQM,EAASD,EAAUX,GAAIA,EAAGW,MAG9BuD,EAAKH,KAAKzD,GACViB,EAAOwC,KAAKpD,EAAUX,KAI9B,OAAOuB,GAuBf,IAAI4C,EAAUF,EAAU5D,GAoDxB,SAAS+D,EAAUzD,EAAW1B,GAC1B,OAAOqD,EAAM3B,EAAW1B,EAAG0B,EAAUZ,QAuBzC,IAAIsE,EAAOlD,EAAQiD,GAAU,GAS7B,SAASE,EAAwB3D,EAAWmD,GAIxC,IAHA,IAAIhC,EAAM,EACN7B,EAAMU,EAAUZ,OAEb+B,EAAM7B,GAAO6D,EAAUnD,EAAUmB,GAAMA,EAAKnB,IAC/CmB,IAGJ,OAAOA,EAmCX,SAASyC,EAAmBC,GACxB,OAAO,SAAU7D,EAAWmD,GACxB,IAAK,IAAI9D,EAAI,EAAGC,EAAMU,EAAUZ,OAAQC,EAAIC,EAAKD,IAC7C,GAAIwE,IAAkBV,EAAUnD,EAAUX,GAAIA,EAAGW,GAC7C,OAAQ6D,EAIhB,OAAOA,GA2Cf,IAAIC,EAAUF,GAAkB,GAuB5BG,EAAQvD,EAAQsD,GAAS,GAsBzBE,EAAaxD,EAAQ0C,GAAQ,GAyBjC,SAASe,EAAWjE,EAAWmD,GAG3B,IAFA,IAAIvC,GAAU,EAELvB,EAAI,EAAGC,EAAMU,EAAUZ,OAAQC,EAAIC,EAAKD,IAC7C,GAAI8D,EAAUnD,EAAUX,GAAIA,EAAGW,GAAY,CACvCY,EAASvB,EACT,MAIR,OAAOuB,EA0BX,SAASsD,EAAMlE,EAAWmD,GACtB,IAAIhC,EAAM8C,EAAUjE,EAAWmD,GAE/B,OAAgB,IAAThC,OAAa,EAASnB,EAAUmB,GAsB3C,IAAIgD,EAAY3D,EAAQ0D,GAAM,GAqB1BE,EAAiB5D,EAAQyD,GAAW,GAqBxC,SAASI,EAASC,EAAOrE,GACrB,OAAOoB,EAAOiD,EAAO,SAAU1D,EAAQ2D,EAAIpD,EAAKqD,GAC5C,IAAIC,EAAIxE,EAASsE,EAAIpD,EAAKqD,GAErB5F,MAAMC,QAAQ4F,KACfA,EAAI,CAACA,IAGT,IAAK,IAAIpF,EAAI,EAAGC,EAAMmF,EAAErF,OAAQsF,EAAO9D,EAAOxB,OAAQC,EAAIC,EAAKD,IAC3DuB,EAAO8D,EAAOrF,GAAKoF,EAAEpF,GAGzB,OAAOuB,GACR,IAqBP,IAAI+D,EAAcnE,EAAQ6D,GAAS,GAyCnC,IAAIO,EAAsBpE,EAAQ,SAAUqE,EAAQP,GAChD,OAAO1F,MAAMC,QAAQyF,GA/BzB,SAASQ,EAAUR,EAAOO,EAAQE,EAAQ5D,GACtC,IAAK,IAA+BxB,EAAOqF,EAAGC,EAArC5F,EAAI,EAAGC,EAAMgF,EAAMlF,OAAwBC,EAAIC,EAAKD,IAGzD,GAFAM,EAAQ2E,EAAMjF,GAETT,MAAMC,QAAQc,GAEZ,GAAIkF,EACPC,EAASnF,GAAO,EAAMoF,EAAQ5D,GAC9BA,EAAM4D,EAAO3F,YAKb,IAHA6F,EAAOtF,EAAMP,OACb2F,EAAO3F,QAAU6F,EAEZD,EAAI,EAAGA,EAAIC,EAAMD,IAClBD,EAAO5D,KAASxB,EAAMqF,QAT1BD,EAAO5D,KAASxB,EAcxB,OAAOoF,EAYuBD,CAASR,EAAOO,EAAQ,GAAI,GAAKlD,EAAM2C,EAAO,EAAGA,EAAMlF,UAmBrF8F,GAAUN,GAAoB,GAWlC,SAASO,GAAiBhE,EAAK7B,GAG3B,OAAeA,IAFf6B,EAAMI,EAAWJ,KAEKA,EAAM7B,EAAM6B,EAAM,EAAIA,EAAM7B,EAAM6B,EAAM1C,IA0BlE,SAAS2G,GAAUpF,EAAWqF,GAC1B,IAAIlE,EAAMgE,GAAgBE,EAAOvF,EAAeE,EAAUZ,SAE1D,OAAO+B,GAAQA,EAAMnB,EAAUmB,QAAO,EA2B1C,IAAImE,GAAQ9E,EAAQ4E,IAAU,GA4D1BG,GAAQ3C,EAAW,SAAU5E,EAAGC,GAChC,OAAKD,GAILA,EAAEA,EAAEoB,QAAUnB,EAEPD,GALI,CAACC,KA8CZuH,GAAUhF,EAAQ+E,IAAO,GAmBzBE,GAAOH,GAAM,GAqDbD,GAAQzC,EAAW,SAAU5E,EAAGC,GAChC,OAAOA,IAiCPyH,GAAUlF,EAAQ6E,IAAO,GAkBzBM,GAAOjH,EAAQiD,EAAO,CAAC7D,EAAI,GAAI,IA4BnC,SAAS8H,GAAQ5F,EAAWqF,EAAOvC,GAC/B,IAAIlC,EAASe,EAAM3B,EAAW,EAAGA,EAAUZ,QAI3C,OAFAwB,EAAOiF,OAAOR,EAAO,EAAGvC,GAEjBlC,EAyBX,IAAIkF,GAAWvG,EAAcqG,IAsD7B,IAAIG,GAAOT,IAAO,GAYlB,SAASU,GAAkB7E,GACvB,OAAO,WAKH,IAJA,IACI7B,GADUP,UAAUK,QAAU+B,GACdA,EAChBP,EAAShC,MAAMU,GAEVD,EAAI,EAAGA,EAAIC,EAAKD,IACrBuB,EAAOvB,GAAKN,UAAUM,EAAI8B,GAG9B,OAAOP,GAiBf,IAAIqF,GAAOD,GAAiB,GAmB5B,SAASE,GAAWlG,EAAWmD,GAI3B,IAHA,IAGgBoB,EAHZ3D,EAAS,CAAC,GAAI,IACdtB,EAAMU,EAAUZ,OAEXC,EAAI,EAAOA,EAAIC,EAAKD,IAEzBuB,EAAOuC,EADPoB,EAAKvE,EAAUX,GACMA,EAAGW,GAAa,EAAI,GAAGoD,KAAKmB,GAGrD,OAAO3D,EAiCX,IAAIuF,GAAgB3F,EAAQ0F,IAAW,GAmBvC,SAASE,GAAOC,EAAKtD,GACjB,OAAOsD,EAAItD,GAwBf,IAAIuD,GAAS9F,EAAQ4F,IAAO,GAwD5B,IAAIG,GAAW3G,EAAQiB,EAASyF,IA2BhC,SAASE,GAAUxG,EAAWyG,GAC1B,OAAOA,EAASvD,EAAOlD,EAAW,SAAU8C,GACxC,OAAQJ,EAAK+D,EAAQ3D,KACpBnB,EAAM3B,EAAW,EAAGA,EAAUZ,QA2BvC,IAAIsH,GAAOlG,EAAQgG,IAAU,GAiBzBG,GAAc7F,GAAc,GAuB5B8F,GAAkBrH,EAAcoH,IAAa,GA8CjD,SAASE,GAAQ7G,EAAW8G,GACxB,IAAIxH,EAAMU,EAAUZ,OAChB2H,EAAQD,EAASxH,EAErB,OAAOqC,EAAM3B,GAAY+G,EAAOzH,GAAKkD,OAAOb,EAAM3B,EAAW,GAAI+G,IAoBrE,IAAIC,GAAWxG,EAAQqG,IAAQ,GAa/B,SAASI,GAAWjH,EAAWmB,EAAKxB,EAAOuH,GACvC,IAAItG,EAASe,EAAM3B,EAAW,EAAGA,EAAUZ,QACvCd,EAAI6G,GAAgBhE,EAAKP,EAAOxB,QAMpC,OAJId,GAAMA,IACNsC,EAAOtC,GAA0B,IAArBS,UAAUK,OAAe8H,EAAQlH,EAAU1B,IAAMqB,GAG1DiB,EA8BX,IAAIuG,GAAQ5H,EAAc0H,IAqB1B,SAASG,GAASjJ,EAAIkJ,GAClB,OAAO,WAIH,IAHA,IAAI/I,EAAIiD,EAAW8F,GACf1I,EAAOsH,GAAKnH,MAAM,KAAMC,WAAW4C,MAAM,EAAGrD,GAEvCe,EAAIV,EAAKS,OAAQC,EAAIf,EAAGe,IAC7BV,EAAKU,QAAK,EAGd,OAAOlB,EAAGW,MAAMjB,KAAMc,IA2B9B,IAAI2I,GAAWF,GAAQH,GAAW,GAkB9BM,GAAiB3C,GAAoB,GAsCrC4C,GAAS5D,GAAkB,GAuB3B6D,GAAOjH,EAAQgH,IAAQ,GAS3B,SAASE,GAAcC,GACnB,OAAO,SAAU3J,EAAGC,GAKhB,IAJA,IAAIqB,EAAMqI,EAASvI,OACfwI,EAAYD,EAAS,GACrB/G,EAASgH,EAAUC,QAAQ7J,EAAE2B,MAAO1B,EAAE0B,OAEjCN,EAAI,EAAc,IAAXuB,GAAgBvB,EAAIC,EAAKD,IAErCuB,GADAgH,EAAYD,EAAStI,IACFwI,QAAQ7J,EAAE2B,MAAO1B,EAAE0B,OAO1C,OAJe,IAAXiB,IACAA,EAAS5C,EAAEqH,MAAQpH,EAAEoH,OAGlBuC,EAAUE,cAAgBlH,EAASA,GAclD,SAASmH,GAAW/J,EAAGC,GACnB,IAAI2C,EAAS,EAYb,cAVW5C,UAAaC,IACpBD,EAAIgK,OAAOhK,GACXC,EAAI+J,OAAO/J,IAGVF,EAAOC,EAAGC,KAEX2C,EAAa3C,EAAJD,GAASA,GAAMA,EAAI,GAAK,GAG9B4C,EAYX,SAASqH,GAASC,EAAQJ,EAAcK,GASpC,MARsB,mBAAXD,GAAyBA,IAAWxI,IAC3CwI,EAAS,MAGW,mBAAbC,IACPA,EAAWJ,IAGR,CACHD,cAA+B,IAAjBA,EACdD,QAAS,SAAU7J,EAAGC,GAMlB,OALIiK,IACAlK,EAAIkK,EAAOlK,GACXC,EAAIiK,EAAOjK,IAGRkK,EAASnK,EAAGC,KAW/B,SAASmK,GAAgBR,GACrB,OAAOA,GAA0C,mBAAtBA,EAAUC,QAAyBD,EAAYK,GAAQL,GAUtF,SAASS,GAAeC,GACpB,OAAOA,GAAWA,EAAQlJ,OAASuB,EAAI2H,EAASF,IAAkB,CAACH,MAgEvE,SAASM,GAAMvI,EAAWsI,GAKtB,IAJA,IAAIX,EAAWU,GAAcC,GACzBhJ,EAAMQ,EAAeE,EAAUZ,QAC/BwB,EAAShC,MAAMU,GAEVD,EAAI,EAAGA,EAAIC,EAAKD,IACrBuB,EAAOvB,GAAK,CAAEM,MAAOK,EAAUX,GAAIgG,MAAOhG,GAK9C,IAFAuB,EAAO2H,KAAKb,GAAaC,IAEpBtI,EAAI,EAAGA,EAAIC,EAAKD,IACjBuB,EAAOvB,GAAKuB,EAAOvB,GAAGM,MAG1B,OAAOiB,EAmHX,IAAI4H,GAAS9J,EAAQuJ,GAAS,CAACnK,GAAI,EAAOA,IAoBtC2K,GAAa/J,EAAQuJ,GAAS,CAACnK,GAAI,EAAMA,IA0BzC4K,GAAWlI,EAAQ+H,IAAM,GAkBzBI,GAAOjF,EAAK,GAuBhB,SAASkF,GAAU5I,EAAW1B,GAC1B,OAAOqD,EAAM3B,EAAW,EAAG1B,GAuB/B,IAAIuK,GAAOrI,EAAQoI,IAAU,GAuD7B,SAASE,GAAW9I,GAChB,IAAI+I,EAASlJ,EACTP,EAAMQ,EAAeE,EAAUZ,QAEnC,GAAY,IAARE,EACA,MAAO,GAGX,IAAK,IAAW0J,EAAPhE,EAAI,EAAeA,EAAI1F,EAAK0F,KACjCgE,EAAalJ,EAAeE,EAAUgF,GAAG5F,SAExB2J,IACbA,EAASC,GAMjB,IAFA,IAEgBzE,EAFZ3D,EAAShC,MAAMmK,GAEV1J,EAAI,EAAOA,EAAI0J,EAAQ1J,IAG5B,IAFAkF,EAAK3D,EAAOvB,GAAKT,MAAMU,GAElB0F,EAAI,EAAGA,EAAI1F,EAAK0F,IACjBT,EAAGS,GAAKhF,EAAUgF,GAAG3F,GAI7B,OAAOuB,EAWX,SAASqI,GAAmBtJ,EAAOuJ,GAC/B,OAAO,IAAI9H,UAAU,kBAAoBkB,EAAK3C,GAAOwJ,cAAgB,OAASD,GAoBlF,SAASE,GAAMC,GACX,IAAKzK,MAAMC,QAAQwK,GACf,MAAMJ,GAAkBI,EAAW,SAGvC,IAAI/J,EAAM+J,EAAUjK,OAEpB,OAAOE,EAAM,WAGT,IAFA,IAAIsB,EAASyI,EAAU,GAAGvK,MAAMjB,KAAMkB,WAE7BM,EAAI,EAAGA,EAAIC,EAAKD,IACrBuB,EAASyI,EAAUhK,GAAGjB,KAAKP,KAAM+C,GAGrC,OAAOA,GACPlB,EAyBR,SAAS4J,GAASrJ,GACd,OAAOmJ,GAAK,CAAClL,EAAO+H,IAAOtB,EAAYjB,EAAK,IAAKJ,EAAUrD,KA0B/D,IAAIsJ,GAAQD,GAAQ5J,GAsDpB,IAAI8J,GAAc9K,EAAQuI,GAAW,CAACnJ,EAAIA,EAAI,KAAMA,IAwCpD,IAAI2L,GAAe5I,EAAQ3C,EAAO+H,KAelC,SAASyD,GAAavL,EAAIQ,GACtB,OAAOR,EAAGW,MAAMjB,KAAMsE,OAAOxD,IAmBjC,IAAIG,GAAQ0B,EAAQkJ,IAoBhBC,GAAUnJ,EAAQkJ,IAAa,GAiMnC,SAASE,GAAQzL,EAAIkJ,EAAO5G,EAAcoJ,GAKtC,OAJIxC,IAAU,IAAMA,IAChBA,EAAQlJ,EAAGiB,QAGXyK,GAAuB,EAARxC,GAAqB,EAARA,EA1DpC,SAASyC,EAAU3L,EAAIkJ,EAAO5G,EAAcoJ,EAAaE,GACrD,OAAO,WAMH,IALA,IAAIC,EAAYD,EAAW3K,OACvBD,EAAUJ,UAAUK,OACpB6K,EAAaD,GAAuB,EAAV7K,GAAe0K,EAAc1K,EAAU,GACjED,EAAUN,MAAMqL,GAEX5K,EAAI,EAAGA,EAAI2K,EAAW3K,IAC3BH,EAAQG,GAAK0K,EAAW1K,GAG5B,KAAOA,EAAI4K,EAAY5K,IACnBH,EAAQG,GAAKN,UAAUM,EAAI2K,GAG/B,OAAkB3C,GAAd4C,EACO9L,EAAGW,MAAMjB,KAAM4C,EAAevB,EAAQgL,UAAYhL,GAElD4K,EAAS3L,EAAIkJ,EAAO5G,EAAcoJ,EAAa3K,IAyCnD4K,CAAS3L,EAAIkJ,EAAO5G,EAAcoJ,EAAa,IACrC,IAAVxC,EACA7G,EAAQrC,EAAIsC,GACF,IAAV4G,GAhCGlJ,EAiCKA,EAjCDsC,EAiCKA,EAhChB,SAAUzC,GACb,OAAO,SAAUC,GACb,OAAO,SAAUkM,GACb,OAAO1J,EAAetC,EAAGC,KAAKP,KAAMsM,EAAGlM,EAAGD,GAAKG,EAAGC,KAAKP,KAAMG,EAAGC,EAAGkM,OA+BpEhM,EAnCf,IAAkBA,EAAIsC,EAiPtB,IAAI2J,GAAYpE,GAAiB,GAejC,SAASqE,GAAUC,EAAWC,EAAYC,GACtC,IAAIC,EAASD,EAAOD,GAEpB,GAAsB,mBAAXE,EAAX,CASA,IALA,IAAIC,EAAeJ,EAAUlL,OACzBuL,EAAM,EAAID,EACVpL,EAAMP,UAAUK,OAASuL,EACzBhM,EAAOC,MAAMU,GAERD,EAAI,EAAGA,EAAIqL,EAAcrL,IAC9BV,EAAKU,GAAKiL,EAAUjL,GAGxB,KAAOA,EAAIC,EAAKD,IACZV,EAAKU,GAAKN,UAAUM,EAAIsL,GAG5B,OAAOF,EAAO3L,MAAM0L,EAAQ7L,IAkPhC,SAASiM,GAAkBC,GACvB,OAAO,SAAUC,GACb,IAAKlM,MAAMC,QAAQiM,GACf,MAAM7B,GAAkB6B,EAAY,SAGxC,OAAO,WACH,IAAK,IAAoClK,EAAhCvB,EAAI,EAAGC,EAAMwL,EAAW1L,OAAgBC,EAAIC,EAAKD,IAAK,CAG3D,GAFAuB,EAASkK,EAAWzL,GAAGP,MAAMjB,KAAMkB,WAE/B8L,IAAajK,EACb,OAAO,EACJ,IAAKiK,GAAYjK,EACpB,OAAO,EAIf,OAAOiK,IAyBnB,IAAIE,GAAQH,IAAiB,GA2BzBI,GAAQJ,IAAiB,GA+B7B,SAASK,GAASjN,EAAGC,GACjB,OAAa,IAAND,GAAiB,IAANC,EAAU,EAAID,GAAM,EAAIC,EAAIF,EAAOC,EAAGC,GA6F5D,SAASiN,GAAIlN,EAAGC,GACZ,OAAWA,EAAJD,EAyBX,SAASmN,GAAKnN,EAAGC,GACb,OAAYA,GAALD,EAuCX,IAAIoN,GAAK5K,EAAQyK,IAwBbI,GAAO7K,EAAQ0K,IAAI,GAyBnBI,GAAQ9K,EAAQ2K,IAAK,GA8BzB,SAASI,GAAIvN,EAAGC,GACZ,OAAOD,EAAIC,EAyBf,IAAIuN,GAAOhL,EAAQ+K,IAAI,GAwBvB,SAASE,GAAKzN,EAAGC,GACb,OAAOD,GAAKC,EA0BhB,IAAIyN,GAAQlL,EAAQiL,IAAK,GA6EzB,SAASE,GAAK3N,EAAGC,GACb,OAAOD,EAAIC,EAmBf,IAAI2N,GAAMpL,EAAQmL,IAAK,GAevB,SAASE,GAAU7N,EAAGC,GAClB,OAAOD,EAAIC,EAoBf,IAAI6N,GAAStL,EAAQqL,IAAU,GAe/B,SAASE,GAAQ/N,EAAGC,GAChB,OAAOD,EAAIC,EAoBf,IAAI+N,GAAWxL,EAAQuL,IAAQ,GA0E/B,SAASE,GAAWtM,GAChB,MAAuB,WAAhB2C,EAAK3C,IAAuBA,EAAQ,GAAM,EAwErD,SAASuM,GAAUlO,EAAGC,GAClB,OAAOD,EAAIC,EAkBf,IAAIkO,GAAa3L,EAAQ0L,IAAU,GA6BnC,SAASE,GAAgBzM,GACrB,IAAIrB,GAAKqB,EAET,OAAOrB,GAAMA,EAAIA,EAAI,EA+EzB,IAAI+N,GAAmBnM,EAAQiC,OAAOC,UAAUkK,sBAShD,SAASC,GAAkBlG,GACvB,IAAIzF,EAAS,GAEb,IAAK,IAAImC,KAAOsD,EACZzF,EAAOwC,KAAKL,GAGhB,OAAOnC,EAUX,SAAS4L,GAAenG,EAAKtD,GACzB,OAAOA,KAAOZ,OAAOkE,KAASgG,GAAiBhG,EAAKtD,KAASwJ,GAAiBlG,GAAKoG,QAAQ1J,IAW/F,SAAS2J,GAAalC,EAAQzH,EAAK4J,GAC/B,GAAIA,GAAyB5J,KAAOZ,OAAOqI,IAAWgC,GAAchC,EAAQzH,GACxE,OAAOA,EAGX,IAAIzE,GAAKyE,EACLzD,EAAMkL,GAAUA,EAAOpL,OAE3B,OAAaE,GAANhB,GAAaA,EAAIgB,EAAMhB,EAAI,EAAIA,EAAIgB,EAAMhB,OAAI,EAWxD,SAASsO,GAAcvG,EAAKwG,EAAOC,GAC/B,GAAIvM,EAAM8F,GACN,MAAM4C,GAAkB5C,EAAK,UAQjC,IALA,IAGItD,EAHAyH,EAASnE,EACThH,GAAK,EACLC,EAAMuN,EAAMzN,SAGPC,EAAIC,IAGLgB,EAFJyC,EAAM2J,GAAYlC,EAAQqC,EAAMxN,GAAIyN,KAMpCtC,EAASA,EAAOzH,GAGpB,OAAO1D,IAAMC,EAAM,CAAEyN,SAAS,EAAMvC,OAAQA,GAAW,CAAEuC,SAAS,EAAOvC,YAAQ,GAWrF,SAASwC,GAAcC,EAAMC,GACzB,OAAOlF,OAAOiF,GAAME,MAAMD,GAAa,KAsD3C,SAASE,GAAW/G,EAAK4G,EAAMC,GAC3B,OAAON,GAAavG,EAAK2G,GAAaC,EAAMC,IAAY,GAAM1C,OA2DlE,IAAI6C,GAAqB7M,EAAQ,SAAU8M,EAASjH,GAChD,GAAI9F,EAAM8F,GACN,MAAM4C,GAAkB5C,EAAK,UAGjC,OAAOiH,EAAQjH,KAuBfkH,GAAcF,GAAmBd,IAyDrC,IAAIiB,GAAUjO,EAAc6N,IA0B5B,SAASK,GAAKpH,EAAKtD,GAKf,MAJmB,iBAARsD,GAAqB/F,EAAY+F,KACxCA,EAAMlE,OAAOkE,IAGVtD,KAAOsD,EAwBlB,IAAIqH,GAASlN,EAAQiN,IAAK,GA2BtBE,GAASzN,EAAQiC,OAAOC,UAAUwL,gBAuBlCC,GAAYrN,EAAQmN,IAAQ,GAsIhC,IA2BIG,GAAOT,GA3BKzN,EAAQuC,OAAO2L,KAAM3L,SA4ErC,SAAS4L,GAAMC,EAAOvH,GAIlB,IAHA,IAAI7F,EAAS,GACTqN,EAAYxH,EAAOrH,OAEdC,EAAI,EAAGC,EAAM0O,EAAM5O,OAAQC,EAAIC,EAAKD,IACzCuB,EAAOoN,EAAM3O,IAAMA,EAAI4O,EAAYxH,EAAOpH,QAAK,EAGnD,OAAOuB,EAsBX,SAASsN,GAAWC,EAAQhQ,GACxB,GAAIoC,EAAM4N,GACN,MAAMlF,GAAkBkF,EAAQ,UAGpC,IAAIvN,EAAS,GAEb,IAAK,IAAImC,KAAOoL,EACZvN,EAAOmC,GAAO5E,EAAGgQ,EAAOpL,GAAMA,EAAKoL,GAGvC,OAAOvN,EAyBX,IAAIwN,GAAgB5N,EAAQ0N,IAAW,GAUvC,SAASG,GAAQf,EAAStP,EAAGC,GACzB,OAAOoD,EAAO,CAACrD,EAAGC,GAAI,SAAU2C,EAAQuN,GAKpC,OAJApO,EAAQuN,EAAQa,GAAS,SAAUpL,GAC/BnC,EAAOmC,GAAOoL,EAAOpL,KAGlBnC,GACR,IA0BP,IAAI0N,GAAQ5P,EAAQ2P,GAAQ,CAACd,KAiCzBgB,GAAW7P,EAAQ2P,GAAQ,CAACP,KAU5BU,GAAehO,EAAQ,SAAU6F,EAAKtD,GACtC,MAAO,CAACA,EAAKsD,EAAItD,MAWjB0L,GAAajO,EAAQ,SAAU8M,EAASjH,GACxC,OAAO1F,EAAI2M,EAAQjH,GAAMmI,GAAanI,MAyBtCqI,GAAWD,GAAWX,IAUtBa,GAAcnO,EAAQ,SAAU8M,EAASjH,GACzC,OAAO1F,EAAI2M,EAAQjH,GAAM,SAAUtD,GAC/B,OAAOsD,EAAItD,OAwBf6L,GAAYD,GAAYb,IAkBxBe,GAAQJ,GAAWlB,IA8BvB,SAASuB,GAAczI,EAAK4G,EAAMC,GAC9B,OAAON,GAAavG,EAAK2G,GAAaC,EAAMC,IAAY,GAAMH,QAoClE,IAAIgC,GAAaxP,EAAcuP,IAyD/B,SAASE,GAAMb,EAAQc,GAGnB,IAFA,IAEwClM,EAFpCnC,EAAS,GAEJvB,EAAI,EAAGC,EAAM2P,EAAU7P,OAAaC,EAAIC,EAAKD,IAG9CoO,GAAIU,EAFRpL,EAAMkM,EAAU5P,MAGZuB,EAAOmC,GAAOoL,EAAOpL,IAI7B,OAAOnC,EAsBX,SAASsO,GAAQ/L,GACb,OAAO,SAAUgL,GACb,GAAI5N,EAAM4N,GACN,MAAMlF,GAAkBkF,EAAQ,UAGpC,IAAIvN,EAAS,GAEb,IAAK,IAAImC,KAAOoL,EACRhL,EAAUgL,EAAOpL,GAAMA,EAAKoL,KAC5BvN,EAAOmC,GAAOoL,EAAOpL,IAI7B,OAAOnC,GAyCf,IAAIuO,GAAW3O,EAAQwO,IAAM,GAwB7B,SAASI,GAAQjB,EAAQkB,GACrBA,EAAUlN,OAAOkN,GACjB,IAAIzO,EAAS,GACT0O,EAAU/B,GAAYY,GAE1B,IAAK,IAAIoB,KAAQF,GACRC,EAAQ7C,QAAQ8C,KACjB3O,EAAOyO,EAAQE,IAASpB,EAAOoB,IAIvC,IAAK,IAAiCxM,EAA7B1D,EAAI,EAAGC,EAAMgQ,EAAQlQ,OAAaC,EAAIC,EAAKD,KAChD0D,EAAMuM,EAAQjQ,MAEDgQ,GAAWtM,KAAOnC,IAC3BA,EAAOmC,GAAOoL,EAAOpL,IAI7B,OAAOnC,EAgCX,IAAI4O,GAAahP,EAAQ4O,IAAQ,GAsCjC,SAASK,GAAQtB,EAAQpL,EAAKpD,GAC1B,IAAIiB,EAAS,GAEb,IAAK,IAAI2O,KAAQpB,EACbvN,EAAO2O,GAAQpB,EAAOoB,GAK1B,OAFA3O,EAAOmC,GAAOpD,EAEPiB,EAgCX,SAAS8O,GAAOvB,EAAQpL,EAAKpD,GACzB,GAAIY,EAAM4N,GACN,MAAMlF,GAAkBkF,EAAQ,UAGpC,OAAOsB,GAAOtB,EAAQpL,EAAKpD,GA2B/B,IAAIgQ,GAASpQ,EAAcmQ,IAyB3B,SAASE,GAAYvJ,EAAKwG,EAAOlN,GAC7B,IAEI8E,EAlBgB+F,EAAQzH,EACxBzE,EAeAyE,EAAM8J,EAAM,GACZgD,EAAWhD,EAAMzN,OAGrB,GAAiB,IAAbyQ,EACApL,EAAI9E,MACD,CACH,IAAImQ,EAAYpD,GAAYrG,EAAKtD,GAAK,GAEtC0B,EAAImL,GACAtP,EAAYwP,GAAaA,EAAYzJ,EAAIyJ,GACzCnO,EAAMkL,EAAO,EAAGgD,GAChBlQ,GAIR,OAhCoB6K,EAgCCnE,EA/BjB/H,IADwByE,EAgCFA,IA7BnBnE,MAAMC,QAAQ2L,IAAWlM,EAAI,GAAM,GAAOA,EAAI,GAAKkO,GAAchC,EAAQzH,GA6BtB0M,GAAOpJ,EAAKtD,EAAK0B,GAA1CwC,GAAUZ,EAAKtD,EAAK0B,GAwDzD,SAASsL,GAAW5B,EAAQlB,EAAMtN,EAAOuN,GACrC,GAAI3M,EAAM4N,GACN,MAAMlF,GAAkBkF,EAAQ,UAGpC,OAAOyB,GAAWzB,EAAQnB,GAAaC,EAAMC,GAAYvN,GA+C7D,SAASqQ,GAAM7B,EAAQ8B,GACnB,GAAI1P,EAAM4N,GACN,MAAMlF,GAAkBkF,EAAQ,UAGpC,IAAIvN,EAAS,GACTsP,EAAQnC,GAAKkC,EAAW,IAE5B,IAAK,IAAIlN,KAAOoL,EACNpL,KAAOmN,IACTtP,EAAOmC,GAAOoL,EAAOpL,IAI7B,OAAOnC,EAuBX,IAAIuP,GAASvQ,EAAQsP,GAAQ7L,GAuCzB+M,GAAW5P,EAAQwP,IAAM,GAYzBK,GAAY7P,EAAQ,SAAU8M,EAASjH,GACvC,OAAOhF,EAAOiM,EAAQjH,GAAM,SAAUzF,EAAQmC,GAI1C,OAHAnC,EAAO,GAAGwC,KAAKL,GACfnC,EAAO,GAAGwC,KAAKiD,EAAItD,IAEZnC,GACR,CAAC,GAAI,OAoBR0P,GAAOD,GAAU9C,IAuBjBgD,GAAUF,GAAUvC,IA8BxB,SAAS0C,GAAUrC,EAAQpL,EAAKmE,GAC5B,OAAOsF,GAAc2B,EAAQpL,GACzB0M,GAAOtB,EAAQpL,EAAKmE,EAAQiH,EAAOpL,KACnCsL,GAAOd,GAAaY,EAAQ,IAyBpC,IAAIsC,GAAYlR,EAAciR,IAgD9B,SAASE,GAAcvC,EAAQlB,EAAM/F,EAASgG,GAC1C,IAAIL,EAAQG,GAAaC,EAAMC,GAC3ByD,EAAW/D,GAAauB,EAAQtB,GAAO,GAE3C,OAAI8D,EAAS5D,QACF6C,GAAWzB,EAAQtB,EAAO3F,EAAQyJ,EAASnG,SAE3C5L,MAAMC,QAAQsP,GAAUxM,EAAMwM,EAAQ,EAAGA,EAAO/O,QAAUiP,GAAOd,GAAaY,EAAQ,IAgErG,SAASyC,GAAUvK,EAAKwK,GACpB,OAAOxP,EAAOwP,EAAU,SAAUC,EAAQC,GACtC,IAAInQ,EAASmQ,EAAS1K,GAItB,OAFAzF,EAAOxB,QAAU0R,EAAO1N,KAAKxC,GAEtBkQ,GACR,IAkCP,IAAIE,GAAexQ,EAAQoQ,IAAU,GAkBjCnK,GAASkI,GAAYpB,IASzB,SAAS0D,GAAS9C,EAAQ+C,GAGtB,IAFA,IAAItQ,EAAS,GAEJvB,EAAI,EAAGA,EAAI6R,EAAO7R,IACvBuB,GAAUuN,EAGd,OAAOvN,EAWX,SAASuQ,GAAahD,EAAQiD,EAAM9R,GAKhC,OAJKiB,EAAM4N,IAA4B,WAAjB7L,EAAK6L,KACvBA,EAASnG,OAAOmG,IAGb8C,GAAQjJ,OAAOoJ,GAAM,IAAM,GAAI5P,KAAK6P,KAAK/R,EAAM6O,EAAO/O,SAqFjE,IAAIkS,GAAUpR,EAAQ8H,OAAO5F,UAAUmP,QA8EvChU,EAAQO,GAAKA,EACbP,EAAQiU,OAhhNR,SAAiB7R,GACb,OAAO,WACH,OAAOA,IA+gNfpC,EAAQQ,OAASA,EACjBR,EAAQW,OAASA,EACjBX,EAAQc,MAAQA,EAChBd,EAAQkC,YAAcA,EACtBlC,EAAQqC,QAAUA,EAClBrC,EAAQwC,QAAUA,EAClBxC,EAAQ2C,QAAUA,EAClB3C,EAAQmC,SAAWA,EACnBnC,EAAQgD,MAAQA,EAChBhD,EAAQ8C,OAASA,EACjB9C,EAAQmD,MAAQA,EAChBnD,EAAQ+C,YAAcA,EACtB/C,EAAQoD,IAAMA,EACdpD,EAAQsD,QAAUA,EAClBtD,EAAQmB,QAAUA,EAClBnB,EAAQkU,aAtjMR,SAAuBtT,EAAIQ,GACvB,OAAO,WACH,IAAKC,MAAMC,QAAQF,GACf,OAAOR,EAAGW,MAAMjB,KAAMkB,WAQ1B,IALA,IAK0BC,EALtBC,EAAUF,UAAUK,OAAS,EAC7BD,EAAUR,EAAKS,OACfkL,EAAY1L,MAAMO,GAClBD,EAAU,GAELG,EAAIF,EAAU,GAAkB,EAALE,EAAQA,IACxCL,EAAWL,EAAKU,GAChBiL,EAAUjL,GAAKL,IAAalB,EAAKiB,UAAUE,KAAaD,EAG5D,IAAKK,EAAI,EAAGA,GAAKJ,EAASI,IACtBH,EAAQG,GAAKN,UAAUM,GAG3B,IAAK,IAAI2F,EAAI,EAAGA,EAAI7F,EAAS6F,IACzB9F,EAAQG,KAAOiL,EAAUtF,GAG7B,OAAO7G,EAAGW,MAAMjB,KAAMqB,KA+hM9B3B,EAAQ8D,OAASA,EACjB9D,EAAQ+D,WAAaA,EACrB/D,EAAQoE,MAAQA,EAChBpE,EAAQ0E,QAAUA,EAClB1E,EAAQ+E,KAAOA,EACf/E,EAAQkF,OAASA,EACjBlF,EAAQgF,SAAWA,EACnBhF,EAAQoF,SAAWA,EACnBpF,EAAQyF,MAAQA,EAChBzF,EAAQ0F,QAAUA,EAClB1F,EAAQmU,WApiLR,SAAqB1R,EAAW2R,GAC5B,IAAIC,EAAelT,EAAQ2E,EAAIX,GAAO,CAACiP,IAEvC,OAAOnO,EAAQN,EAAOlD,EAAW4R,KAkiLrCrU,EAAQmG,KAAOA,EACfnG,EAAQkG,SAAWA,EACnBlG,EAAQsU,UA78KR,SAAoB1O,GAChB,OAAO,SAAUnD,GACb,OAAO2B,EAAM3B,EAAW2D,EAAuB3D,EAAWmD,GAAYnD,EAAUZ,UA48KxF7B,EAAQwG,MAAQA,EAChBxG,EAAQuG,QAAUA,EAClBvG,EAAQ2F,OAASA,EACjB3F,EAAQyG,WAAaA,EACrBzG,EAAQ2G,KAAOA,EACf3G,EAAQ0G,UAAYA,EACpB1G,EAAQ4G,UAAYA,EACpB5G,EAAQ6G,eAAiBA,EACzB7G,EAAQ8G,QAAUA,EAClB9G,EAAQoH,YAAcA,EACtBpH,EAAQ2H,QAAUA,GAClB3H,EAAQ+H,MAAQA,GAChB/H,EAAQ6H,SAAWA,GACnB7H,EAAQgI,MAAQA,GAChBhI,EAAQiI,QAAUA,GAClBjI,EAAQkI,KAAOA,GACflI,EAAQ8H,MAAQA,GAChB9H,EAAQmI,QAAUA,GAClBnI,EAAQoI,KAAOA,GACfpI,EAAQqI,OAASA,GACjBrI,EAAQuI,SAAWA,GACnBvI,EAAQuU,aAvxJR,SAAuB9T,EAAGC,GACtB,IAAI2C,EAAS,GACTmR,EAAO/T,EAAEoB,OAEb,GAAI2S,GAAQ9T,EAAEmB,OACV,IAAK,IAAIC,EAAI,EAAGA,EAAI0S,EAAM1S,KACrBqD,EAAK9B,EAAQ5C,EAAEqB,KAAOqD,EAAKzE,EAAGD,EAAEqB,KAAOuB,EAAOwC,KAAKpF,EAAEqB,IAI9D,OAAOuB,GA8wJXrD,EAAQmF,KAAOA,EACfnF,EAAQwI,KAAOA,GACfxI,EAAQ0I,KAAOA,GACf1I,EAAQ2I,UAAYA,GACpB3I,EAAQ4I,cAAgBA,GACxB5I,EAAQyU,MAjlJR,SAAgBhS,EAAW+C,GACvB,OAAOpC,EAAIX,EAAWsG,GAAOvD,KAilJjCxF,EAAQgJ,SAAWA,GACnBhJ,EAAQmJ,KAAOA,GACfnJ,EAAQiJ,SAAWA,GACnBjJ,EAAQoJ,YAAcA,GACtBpJ,EAAQqJ,gBAAkBA,GAC1BrJ,EAAQ2M,QA38IR,SAAkBlK,GAId,IAHA,IAAIV,EAAMQ,EAAeE,EAAUZ,QAC/BwB,EAAShC,MAAMU,GAEVD,EAAI,EAAGsL,EAAMrL,EAAM,EAAGD,EAAIC,EAAKD,IACpCuB,EAAOvB,GAAKW,EAAU2K,EAAMtL,GAGhC,OAAOuB,GAo8IXrD,EAAQsJ,OAASA,GACjBtJ,EAAQyJ,SAAWA,GACnBzJ,EAAQ4J,MAAQA,GAChB5J,EAAQ+J,SAAWA,GACnB/J,EAAQgK,eAAiBA,GACzBhK,EAAQkK,KAAOA,GACflK,EAAQiK,OAASA,GACjBjK,EAAQgL,KAAOA,GACfhL,EAAQ0U,aA59HR,SAAuBjS,EAAW8C,EAASwF,GACvC,IAAI1H,EAASe,EAAM3B,EAAW,EAAGA,EAAUZ,QAE3C,GAAyB,IAArBL,UAAUK,OACV,OAAOwB,EAGX,IACIO,EA5ER,SAAS+Q,EAAoB5N,EAAOxB,EAASqF,EAAUvG,EAAOC,GAC1D,GAAqB,IAAjByC,EAAMlF,OACN,OAAO,EAGX,IAAI+S,EAASvQ,EAAQC,GAAQ,EACzBjB,EAASuH,EACT,CAAExI,MAAOmD,EAASuC,MAAO8M,GACzB,CAAExS,MAAO2E,EAAM6N,GAAQ9M,MAAO8M,IAGlC,OAAItQ,EAAMD,GAAS,EACRhB,EAAS,EAAIuR,EAAQA,EAAQ,EAC7BvR,EAAS,EACTsR,EAAmB5N,EAAOxB,EAASqF,EAAUvG,EAAOuQ,GACzC,IAAXvR,EACAuR,EAAQ,EAERD,EAAmB5N,EAAOxB,EAASqF,EAAUgK,EAAOtQ,GA0DrDqQ,CAAmBtR,EAAQkC,EAAS4E,GAD/BW,GAAcC,IACyC,EAAG1H,EAAOxB,QAIhF,OAFAwB,EAAOiF,OAAO1E,EAAK,EAAG2B,GAEflC,GAi9HXrD,EAAQiL,OAASA,GACjBjL,EAAQkL,WAAaA,GACrBlL,EAAQmL,SAAWA,GACnBnL,EAAQoL,KAAOA,GACfpL,EAAQsL,KAAOA,GACftL,EAAQqL,SAAWA,GACnBrL,EAAQ6U,UA9zHR,SAAoBjP,GAChB,OAAO,SAAUnD,GACb,OAAO2B,EAAM3B,EAAW,EAAG2D,EAAuB3D,EAAWmD,MA6zHrE5F,EAAQuL,UAAYA,GACpBvL,EAAQgM,MAAQA,GAChBhM,EAAQ+L,QAAUA,GAClB/L,EAAQiG,QAAUA,EAClBjG,EAAQ+F,UAAYA,EACpB/F,EAAQ8U,SA7oHR,SAAmBhN,EAAO6B,GACtB,OAAO,SAAUlH,GACb,OAAOiH,GAAUjH,EAAWqF,EAAO,KAAM6B,KA4oHjD3J,EAAQiM,YAAcA,GACtBjM,EAAQ+U,IA1lHR,SAActU,EAAGC,GACb,OAAO6K,GAAU,CAAC9K,EAAGC,KA0lHzBV,EAAQkM,aAAeA,GACvBlM,EAAQmM,YAAcA,GACtBnM,EAAQuB,MAAQA,GAChBvB,EAAQoM,QAAUA,GAClBpM,EAAQgV,UA/7GR,SAAoBpU,GAChB,OA5EJ,SAASqU,EAAYrU,EAAI4L,GACrB,OAAO,WAKH,IAJA,IAIyC/K,EAJrCG,EAAUJ,UAAUK,OACpBH,EAAU,EACVC,EAAU,GAELG,EAAI,EAAGC,EAAMyK,EAAW3K,OAAkBC,EAAIC,EAAKD,IACxDL,EAAW+K,EAAW1K,GACtBH,EAAQG,GAAKL,IAAalB,GAAMmB,EAAUE,EAAUJ,UAAUE,KAAaD,EAG/E,KAAOC,EAAUE,GACbD,EAAQG,KAAON,UAAUE,KAG7B,IAAKI,EAAI,EAAGA,EAAIF,EAASE,IACrB,GAAIN,UAAUM,KAAOvB,EACjB,OAAO0U,EAAWrU,EAAIe,GAI9B,IAAKG,EAAI,EAAGC,EAAMJ,EAAQE,OAAQC,EAAIC,EAAKD,IACnCH,EAAQG,KAAOvB,IACfoB,EAAQG,QAAK,GAIrB,OAAOlB,EAAGW,MAAMjB,KAAMqB,IAiDnBsT,CAAWrU,EAAI,KA+7G1BZ,EAAQ6J,QAAUA,GAClB7J,EAAQkV,QAl6GR,SAAkBpJ,GACd,IAAKzK,MAAMC,QAAQwK,GACf,MAAMJ,GAAkBI,EAAW,SAGvC,OAAO,WACH,OAAO1I,EAAI0I,EAAWM,GAAQ5K,cA65GtCxB,EAAQmV,MAjzGR,SAAgBvU,EAAIkJ,GAChB,OAAOuC,GAAOzL,EAAIkJ,GAAO,IAizG7B9J,EAAQoV,UArxGR,SAAoBxU,EAAIkJ,GACpB,OAAOuC,GAAOzL,EAAIkJ,GAAO,GAAO,IAqxGpC9J,EAAQqV,eA7vGR,SAAyBzU,EAAIkJ,GACzB,OAAOuC,GAAOzL,EAAIkJ,GAAO,GAAM,IA6vGnC9J,EAAQsV,WAtuGR,SAAqB1U,EAAIkJ,GACrB,OAAOuC,GAAOzL,EAAIkJ,GAAO,IAsuG7B9J,EAAQuV,SA3sGR,SAAmB3U,EAAI4U,GACnB,IAAIC,EAEJ,OAAO,WACH,IAAIrU,EAAOI,UACPkU,EAAY,WACZD,EAAY,KACZ7U,EAAGW,MAAMjB,KAAMc,IACjByB,KAAKvC,MAEPqV,aAAaF,GACbA,EAAYG,WAAWF,EAAWF,KAisG1CxV,EAAQ6V,KAjrGR,SAAejV,GACX,OAAO,WACH,IAAIQ,EAAOsH,GAAKnH,MAAM,KAAMC,WAAWmL,UAEvC,OAAO/L,EAAGW,MAAMjB,KAAMc,KA8qG9BpB,EAAQ8V,SAppGR,SAAmBlS,GACf,OAAO,WACH,OAAOpC,UAAUoG,GAAgBhE,EAAKpC,UAAUK,WAmpGxD7B,EAAQ+V,QAlkGR,SAAkB/I,GACd,OAAO7L,EAAQ2L,GAAU,CAACD,GAAUtL,MAAM,KAAMC,WAAYwL,KAkkGhEhN,EAAQgW,UA3iGR,SAAoB/I,GAChB,OAAO9L,EAAQ2L,GAAU,CAAC,GAAIvM,EAAI0M,KA2iGtCjN,EAAQiW,QAjhGR,SAAkBrV,EAAIsV,GAClB,OAAOrK,GAAK,CAACnD,GAAMpF,EAAQ4S,GAAS3U,GAAMX,MAihG9CZ,EAAQ6L,KAAOA,GACf7L,EAAQmW,QA5/FR,SAAkBvV,EAAIwV,GAClB,OAAO,WAKH,IAJA,IAAIrU,EAAMP,UAAUK,OAChBwU,EAAaD,EAAQvU,OACrBT,EAAO,GAEFU,EAAI,EAAGA,EAAIC,EAAKD,IACrBV,EAAKyE,KAAK/D,EAAIuU,EAAaD,EAAQtU,GAAGN,UAAUM,IAAMN,UAAUM,IAGpE,OAAOlB,EAAGW,MAAMjB,KAAMc,KAm/F9BpB,EAAQsW,SA39FR,SAAmB1V,EAAI4U,GACnB,IAAInS,EACAkT,EAAW,EAEf,OAAO,WACH,IAAIC,EAAMC,KAAKD,MAOf,OALsBhB,GAAlBgB,EAAMD,IACNA,EAAWC,EACXnT,EAASzC,EAAGW,MAAMjB,KAAMkB,YAGrB6B,IAg9FfrD,EAAQ0W,MA37FR,SAAgB9V,GACZ,OAAO,SAAUH,GACb,OAAOG,EAAGC,KAAKP,KAAMG,KA07F7BT,EAAQ2W,QAr5FR,SAAkB7K,GACd,IAAKzK,MAAMC,QAAQwK,GACf,MAAMJ,GAAkBI,EAAW,SAGvC,OAAO,WAIH,IAHA,IACIzI,EADAtB,EAAM+J,EAAUjK,OAGXC,EAAI,EAAGA,EAAIC,GAGXgB,EAFLM,EAASyI,EAAUhK,GAAGP,MAAMjB,KAAMkB,YADbM,KAQzB,OAAOuB,IAq4FfrD,EAAQwN,MAAQA,GAChBxN,EAAQyN,MAAQA,GAChBzN,EAAQ0N,QAAUA,GAClB1N,EAAQ4W,KA5vFR,SAAgBhR,EAAWhF,GACvB,OAAO,WACH,OAAOgF,EAAUrE,MAAMjB,KAAMkB,WAAaZ,EAAGW,MAAMjB,KAAMkB,gBAAa,IA2vF9ExB,EAAQ6W,UA7tFR,SAAoBjR,EAAWkR,EAAQC,GACnC,OAAO,WACH,OAAQnR,EAAUrE,MAAMjB,KAAMkB,WAAasV,EAASC,GAASxV,MAAMjB,KAAMkB,aA4tFjFxB,EAAQ2N,GAAKA,GACb3N,EAAQ4N,IAAMA,GACd5N,EAAQ6N,GAAKA,GACb7N,EAAQ8N,KAAOA,GACf9N,EAAQ+N,MAAQA,GAChB/N,EAAQiO,KAAOA,GACfjO,EAAQmO,MAAQA,GAChBnO,EAAQgO,GAAKA,GACbhO,EAAQkO,IAAMA,GACdlO,EAAQ8F,IAAMA,EACd9F,EAAQgX,OA78ER,SAAiBpR,EAAWhF,GACxB,OAAO,SAAUwB,GACb,OAAOwD,EAAU/E,KAAKP,KAAM8B,GAASA,EAAQxB,EAAGC,KAAKP,KAAM8B,KA48EnEpC,EAAQiX,KA/6ER,SAAerR,EAAWhF,GACtB,OAAO,SAAUwB,GACb,OAAOwD,EAAU/E,KAAKP,KAAM8B,GAASxB,EAAGC,KAAKP,KAAM8B,GAASA,IA86EpEpC,EAAQqO,IAAMA,GACdrO,EAAQuO,OAASA,GACjBvO,EAAQwO,OAASA,GACjBxO,EAAQyO,SAAWA,GACnBzO,EAAQkX,SAhzER,SAAmB7S,EAAOtC,EAAKW,GAG3B,IAFA,IAAIW,EAAS,CAACgB,GAELvC,EAAI,EAAGqV,EAAQpV,EAAM,EAAGD,EAAIqV,EAAOrV,IACxCuB,EAAOwC,KAAKnD,EAASW,EAAOvB,GAAIA,EAAGuB,IAGvC,OAAOA,GA0yEXrD,EAAQoX,SApxER,SAAoBhV,GAChB,MAAuB,WAAhB2C,EAAK3C,IAAuBgV,SAAShV,IAoxEhDpC,EAAQ0O,UAAYA,GACpB1O,EAAQqX,cAhuER,SAAwBjV,GACpB,OAAOsM,GAAUtM,IAAU6B,KAAKE,IAAI/B,IA1sIjB,kBA06MvBpC,EAAQsX,OAvsER,SAAiB7W,EAAGC,GAChB,OAAOD,EAAKC,EAAIuD,KAAKC,MAAMzD,EAAIC,IAusEnCV,EAAQ2O,SAAWA,GACnB3O,EAAQ4O,WAAaA,GACrB5O,EAAQuX,UAppER,SAAoBvW,EAAKC,GACrB,OAAOgD,KAAKC,MAAMD,KAAKuT,UAAYvW,EAAMD,EAAM,GAAKA,IAopExDhB,EAAQyX,MA3mER,SAAgBpT,EAAO8S,EAAO3T,GAK1B,GAJAa,EAAQwK,GAAexK,GACvB8S,EAAQtI,GAAesI,GAGV,KAFb3T,EAA4B,IAArBhC,UAAUK,OAAegN,GAAerL,GAAQ,GAGnD,OAAO2T,IAAU9S,EAAQ,GAAK,CAACA,GAMnC,IAHA,IAAItC,EAAMkC,KAAKhD,IAAIgD,KAAK6P,MAAMqD,EAAQ9S,GAASb,GAAO,GAClDH,EAAShC,MAAMU,GAEVD,EAAI,EAAG0G,EAAOnE,EAAOvC,EAAIC,EAAKD,IACnCuB,EAAOvB,GAAK0G,EACZA,GAAQhF,EAGZ,OAAOH,GA2lEXrD,EAAQ0X,UArkER,SAAoBjX,EAAGC,GACnB,OAAOD,EAAIC,GAqkEfV,EAAQsO,SAAWA,GACnBtO,EAAQoO,IAAMA,GACdpO,EAAQ2X,QAh4DR,SAAkB/R,EAAWgS,EAASC,EAAUC,GAC5C,OAAO,SAAUhP,GACb,IAAIiP,EAAY5W,EAAQ0O,GAAW,CAAC/G,EAAKvI,EAAIuX,IAE7C,OAAOlS,EAAUrE,MAAMuH,EAAK1F,EAAIyU,EAAUE,IAAc,GAAK,CAACH,EAASC,KA63D/E7X,EAAQgQ,YAAcA,GACtBhQ,EAAQgY,UAr0DR,SAAoBC,GAChB,IAAI5U,EAAS,GAMb,OAJAb,EAAQyV,EAAW,SAAUC,GACzB7U,EAAO6U,EAAK,IAAMA,EAAK,KAGpB7U,GA+zDXrD,EAAQ6I,MAAQA,GAChB7I,EAAQ+I,OAASA,GACjB/I,EAAQiQ,QAAUA,GAClBjQ,EAAQ6P,UAAYA,GACpB7P,EAAQkQ,IAAMA,GACdlQ,EAAQmQ,OAASA,GACjBnQ,EAAQoQ,OAASA,GACjBpQ,EAAQsQ,UAAYA,GACpBtQ,EAAQmY,YA3qDR,SAAsB3S,EAAKpD,GACvB,OAAO,SAAU0G,GACb,OAAO/F,EAAYX,GAAS8N,GAAIpH,EAAKtD,IAAQsD,EAAItD,KAASpD,EAAQ5B,EAAO4B,EAAO0G,EAAItD,MA0qD5FxF,EAAQoY,aAloDR,SAAuB1I,EAAMtN,EAAOuN,GAChC,OAAO,SAAU7G,GACb,IAAIsK,EAAW/D,GAAavG,EAAK2G,GAAaC,EAAMC,IAAY,GAEhE,OAAOyD,EAAS5D,SAAWhP,EAAO4S,EAASnG,OAAQ7K,KA+nD3DpC,EAAQqY,UArkDR,SAAoBvP,GAChB,OA/CJ,SAASwP,EAAYxP,EAAK9C,GAatB,OAZ2B,IAAvBA,EAAKkJ,QAAQpG,KACb9C,EAAKH,KAAKjB,OAAO2T,OAAOzP,IAExBtG,EAAQoC,OAAO4T,oBAAoB1P,GAAM,SAAUtD,GAC/C,IAAIpD,EAAQ0G,EAAItD,GAEK,iBAAVpD,GAAuBU,EAAOV,IACrCkW,EAAWlW,EAAO4D,MAKvB8C,EAkCAwP,CAAWxP,EAAK,KAqkD3B9I,EAAQuQ,KAAOA,GACfvQ,EAAQyY,aA1gDR,SAAuB7S,EAAWJ,GAC9B,OAAO,SAAUsD,GACb,OAAOlD,EAAU/E,KAAKP,KAAMwI,EAAItD,MAygDxCxF,EAAQwQ,KAAOA,GACfxQ,EAAQ2Q,UAAYA,GACpB3Q,EAAQ6Q,cAAgBA,GACxB7Q,EAAQ+Q,MAAQA,GAChB/Q,EAAQgR,SAAWA,GACnBhR,EAAQmR,SAAWA,GACnBnR,EAAQqR,UAAYA,GACpBrR,EAAQsR,MAAQA,GAChBtR,EAAQwR,WAAaA,GACrBxR,EAAQuR,aAAeA,GACvBvR,EAAQ0Y,cArqCR,SAAwB9S,EAAW8J,EAAMC,GACrC,OAAO,SAAU7G,GACb,IAAIsK,EAAW/D,GAAavG,EAAK2G,GAAaC,EAAMC,IAAY,GAEhE,OAAO/J,EAAU/E,KAAKP,KAAM8S,EAASnG,UAkqC7CjN,EAAQyR,KAAOA,GACfzR,EAAQ2R,OAASA,GACjB3R,EAAQ4R,SAAWA,GACnB5R,EAAQ6R,OAASA,GACjB7R,EAAQiS,WAAaA,GACrBjS,EAAQ2Y,WAt9BR,SAAqB/X,GACjB,OAAO,SAAUgQ,GACb,OAAOiB,GAAOjB,EAAQhQ,EAAGgQ,MAq9BjC5Q,EAAQmS,MAAQA,GAChBnS,EAAQoS,OAASA,GACjBpS,EAAQ4Y,QApwBR,SAAkBlJ,EAAMtN,EAAOuN,GAC3B,OAAO,SAAUiB,GACb,OAAO4B,GAAU5B,EAAQlB,EAAMtN,EAAOuN,KAmwB9C3P,EAAQwS,UAAYA,GACpBxS,EAAQyS,KAAOA,GACfzS,EAAQ4S,OAASA,GACjB5S,EAAQ6S,SAAWA,GACnB7S,EAAQ+S,KAAOA,GACf/S,EAAQgT,QAAUA,GAClBhT,EAAQiT,SAAWA,GACnBjT,EAAQkT,UAAYA,GACpBlT,EAAQ6Y,WA/dR,SAAqBnJ,EAAM/F,EAASgG,GAChC,OAAO,SAAUiB,GACb,OAAOuC,GAAavC,EAAQlB,EAAM/F,EAASgG,KA8dnD3P,EAAQmT,aAAeA,GACvBnT,EAAQqT,SAAWA,GACnBrT,EAAQyT,aAAeA,GACvBzT,EAAQkJ,OAASA,GACjBlJ,EAAQ8Y,QA9UR,SAAkBlI,EAAQiD,EAAM9R,GAC5B,OAAO6R,GAAYhD,EAAQiD,EAAM9R,GAAO6O,GA8U5C5Q,EAAQ+Y,SAvTR,SAAmBnI,EAAQiD,EAAM9R,GAC7B,OAAO6O,EAASgD,GAAYhD,EAAQiD,EAAM9R,IAuT9C/B,EAAQgZ,OAlSR,SAAiBpI,EAAQ+C,GACrB,GAAI3Q,EAAM4N,GACN,MAAMlF,GAAkBkF,EAAQ,UAGpC,OAAO8C,GAAQ9C,EAAQ3M,KAAKC,MAAMyP,KA8RtC3T,EAAQiZ,SAnQR,SAAmBC,GACf,OAAO,SAAUC,GACb,OAAgC,IAAzBpF,GAAQoF,EAAGD,KAkQ1BlZ,EAAQoZ,aAjOR,SAAuBC,GACnB,OAAO,SAAUvQ,GACb,OAAOA,aAAeuQ,IAgO9BrZ,EAAQsZ,OA7MR,SAAiBC,GACb,OAAO,SAAUnX,GACb,OAAO2C,EAAK3C,KAAWmX,IA6M/B3U,OAAO4U,eAAexZ,EAAS,aAAc,CAAEoC,OAAO","file":"lamb.min.js","sourcesContent":["/**\n* @overview lamb - A lightweight, and docile, JavaScript library to help embracing functional programming.\n* @author Andrea Scartabelli \n* @version 0.57.0-rc.2\n* @module lamb\n* @license MIT\n* @preserve\n*/\n(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n typeof define === 'function' && define.amd ? define(['exports'], factory) :\n (global = global || self, factory(global.lamb = {}));\n}(this, function (exports) { 'use strict';\n\n /**\n * The placeholder object used in partial application.\n * @memberof module:lamb\n * @alias module:lamb.__\n * @category Special properties\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.57.0\n * @type {Object}\n */\n var __ = {};\n\n /**\n * Builds a function that returns a constant value.\n * It's actually the simplest form of the K combinator or Kestrel.\n * @example\n * var truth = _.always(true);\n *\n * truth() // => true\n * truth(false) // => true\n * truth(1, 2) // => true\n *\n * // the value being returned is actually the\n * // very same value passed to the function\n * var foo = {bar: \"baz\"};\n * var alwaysFoo = _.always(foo);\n *\n * alwaysFoo() === foo // => true\n *\n * @memberof module:lamb\n * @category Function\n * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus}\n * @since 0.1.0\n * @param {*} value\n * @returns {Function}\n */\n function always (value) {\n return function () {\n return value;\n };\n }\n\n /**\n * Verifies that the two supplied values are the same value using the \"SameValueZero\" comparison.
\n * With this comparison NaN is equal to itself, but 0 and -0 are\n * considered the same value.
\n * See also {@link module:lamb.isSVZ|isSVZ} for a curried version building a predicate and\n * {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} to perform a \"SameValue\" comparison.\n * @example\n * var testObject = {};\n *\n * _.areSVZ({}, testObject) // => false\n * _.areSVZ(testObject, testObject) // => true\n * _.areSVZ(\"foo\", \"foo\") // => true\n * _.areSVZ(0, -0) // => true\n * _.areSVZ(0 / 0, NaN) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.isSVZ|isSVZ}\n * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.50.0\n * @param {*} a\n * @param {*} b\n * @returns {Boolean}\n */\n function areSVZ (a, b) {\n return a !== a ? b !== b : a === b; // eslint-disable-line no-self-compare\n }\n\n /**\n * Builds a function that passes only two arguments to the given function.
\n * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize},\n * exposed for convenience.\n * @example\n * _.list(1, 2, 3, 4, 5) // => [1, 2, 3, 4, 5]\n * _.binary(_.list)(1, 2, 3, 4, 5) // => [1, 2]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.aritize|aritize}\n * @see {@link module:lamb.unary|unary}\n * @since 0.10.0\n * @param {Function} fn\n * @returns {Function}\n */\n function binary (fn) {\n return function (a, b) {\n return fn.call(this, a, b);\n };\n }\n\n /**\n * \"Clamps\" a number within the given limits, both included.
\n * The function will convert to number all its parameters before starting any\n * evaluation, and will return NaN if min is greater\n * than max.\n * @example\n * _.clamp(-5, 0, 10) // => 0\n * _.clamp(5, 0, 10) // => 5\n * _.clamp(15, 0, 10) // => 10\n * _.clamp(0, 0, 10) // => 0\n * _.clamp(10, 0, 10) // => 10\n * _.is(_.clamp(-0, 0, 10), -0) // => true\n * _.clamp(10, 20, 15) // => NaN\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.clampWithin|clampWithin}\n * @since 0.13.0\n * @param {Number} n\n * @param {Number} min\n * @param {Number} max\n * @returns {Number}\n */\n function clamp (n, min, max) {\n n = +n;\n min = +min;\n max = +max;\n\n if (min > max) {\n return NaN;\n } else {\n return n < min ? min : n > max ? max : n;\n }\n }\n\n /**\n * Builds a partially applied function.
\n * The {@link module:lamb.__|__} object can be used as a placeholder for arguments.
\n * @example\n * var __ = _.__;\n * var users = [\n * {id: 1, name: \"John\", active: true, confirmedMail: true},\n * {id: 2, name: \"Jane\", active: true, confirmedMail: false},\n * {id: 3, name: \"Mario\", active: false, confirmedMail: false}\n * ];\n * var isKeyTrue = _.partial(_.hasKeyValue, [__, true]);\n * var isActive = isKeyTrue(\"active\");\n * var hasConfirmedMail = isKeyTrue(\"confirmedMail\");\n *\n * _.map(users, isActive) // => [true, true, false]\n * _.map(users, hasConfirmedMail) // => [true, false, false]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.__|__} The placeholder object.\n * @since 0.1.0\n * @param {Function} fn\n * @param {Array} args\n * @returns {Function}\n */\n function partial (fn, args) {\n return function () {\n if (!Array.isArray(args)) {\n return fn.apply(this, arguments);\n }\n\n var lastIdx = 0;\n var newArgs = [];\n var argsLen = args.length;\n\n for (var i = 0, boundArg; i < argsLen; i++) {\n boundArg = args[i];\n newArgs[i] = boundArg === __ ? arguments[lastIdx++] : boundArg;\n }\n\n for (var len = arguments.length; lastIdx < len; lastIdx++) {\n newArgs[i++] = arguments[lastIdx];\n }\n\n return fn.apply(this, newArgs);\n };\n }\n\n /**\n * Builds a partial application of a ternary function so that its first parameter\n * is expected as the last one.
\n * The shouldAritize parameter is for the \"reduce\" functions, where\n * the absence of the initialValue transforms a \"fold\" operation into a\n * \"reduce\" one.\n * @private\n * @param {Function} fn\n * @param {Boolean} shouldAritize\n * @returns {Function}\n */\n function _makePartial3 (fn, shouldAritize) {\n return function (a, b) {\n var f = shouldAritize && arguments.length !== 2 ? binary(fn) : fn;\n\n return partial(f, [__, a, b]);\n };\n }\n\n /**\n * A curried version of {@link module:lamb.clamp|clamp}, expecting a min\n * and a max value, that builds a function waiting for the number to clamp.\n * @example\n * _.clampWithin(0, 10)(-5) // => 0\n * _.clampWithin(0, 10)(5) // => 5\n * _.clampWithin(0, 10)(15) // => 10\n * _.clampWithin(0, 10)(0) // => 0\n * _.clampWithin(0, 10)(10) // => 10\n * _.is(_.clampWithin(0, 10)(-0), -0) // => true\n * _.clampWithin(20, 15)(10) // => NaN\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.clamp|clamp}\n * @since 0.47.0\n * @param {Number} min\n * @param {Number} max\n * @returns {Function}\n */\n var clampWithin = _makePartial3(clamp);\n\n /**\n * The I combinator. Any value passed to the function is simply returned as it is.\n * @example\n * var foo = {bar: \"baz\"};\n *\n * _.identity(foo) === foo // true\n *\n * @memberof module:lamb\n * @category Function\n * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus}\n * @since 0.1.0\n * @param {*} value\n * @returns {*} The value passed as parameter.\n */\n function identity (value) {\n return value;\n }\n\n /**\n * Returns a function that is the composition of the functions given as parameters.\n * The first function consumes the result of the function that follows.\n * @example\n * var sayHi = function (name) { return \"Hi, \" + name; };\n * var capitalize = function (s) {\n * return s[0].toUpperCase() + s.substr(1).toLowerCase();\n * };\n * var fixNameAndSayHi = _.compose(sayHi, capitalize);\n *\n * sayHi(\"bOb\") // => \"Hi, bOb\"\n * fixNameAndSayHi(\"bOb\") // \"Hi, Bob\"\n *\n * var users = [{name: \"fred\"}, {name: \"bOb\"}];\n * var sayHiToUser = _.compose(fixNameAndSayHi, _.getKey(\"name\"));\n *\n * _.map(users, sayHiToUser) // [\"Hi, Fred\", \"Hi, Bob\"]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.pipe|pipe}\n * @since 0.1.0\n * @param {Function} a\n * @param {Function} b\n * @returns {Function}\n */\n function compose (a, b) {\n return arguments.length ? function () {\n return a.call(this, b.apply(this, arguments));\n } : identity;\n }\n\n var MAX_ARRAY_LENGTH = 4294967295;\n var MAX_SAFE_INTEGER = 9007199254740991;\n\n /**\n * Converts a value to a valid array length, thus an integer within\n * 0 and 232 - 1 (both included).\n * @private\n * @param {*} value\n * @returns {Number}\n */\n function _toArrayLength (value) {\n return clamp(value, 0, MAX_ARRAY_LENGTH) >>> 0;\n }\n\n /**\n * Executes the provided iteratee for each element of the given array-like object.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example Adding a CSS class to all elements of a NodeList in a browser environment:\n * var addClass = _.curry(function (className, element) {\n * element.classList.add(className);\n * });\n * var paragraphs = document.querySelectorAll(\"#some-container p\");\n *\n * _.forEach(paragraphs, addClass(\"main\"));\n * // each \"p\" element in the container will have the \"main\" class now\n *\n * @memberof module:lamb\n * @category Array\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Undefined}\n */\n function forEach (arrayLike, iteratee) {\n for (var i = 0, len = _toArrayLength(arrayLike.length); i < len; i++) {\n iteratee(arrayLike[i], i, arrayLike);\n }\n }\n\n /**\n * Creates generic functions out of methods.\n * @author A very little change on a great idea by [Irakli Gozalishvili]{@link https://github.com/Gozala/}.\n * Thanks for this *beautiful* one-liner (never liked your \"unbind\" naming choice, though).\n * @memberof module:lamb\n * @category Function\n * @function\n * @example\n * var join = _.generic(Array.prototype.join);\n *\n * join([1, 2, 3, 4, 5], \"-\") // => \"1-2-3-4-5\"\n *\n * // the function will work with any array-like object\n * join(\"hello\", \"-\") // => \"h-e-l-l-o\"\n *\n * @since 0.1.0\n * @param {Function} method\n * @returns {Function}\n */\n var generic = Function.bind.bind(Function.call);\n\n /**\n * Verifies if a value is null.\n * @example\n * _.isNull(null) // => true\n * _.isNull(void 0) // => false\n * _.isNull(false) // => false\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isNil|isNil} if you want to check for undefined too.\n * @since 0.1.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isNull (value) {\n return value === null;\n }\n\n /**\n * Verifies if a value is undefined.\n * @example\n * _.isUndefined(null) // => false\n * _.isUndefined(void 0) // => true\n * _.isUndefined(false) // => false\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isNil|isNil} if you want to check for null too.\n * @since 0.1.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isUndefined (value) {\n return value === void 0;\n }\n\n /**\n * Verifies if a value is null or undefined.\n * @example\n * _.isNil(NaN) // => false\n * _.isNil({}) // => false\n * _.isNil(null) // => true\n * _.isNil(void 0) // => true\n * _.isNil() // => true\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isNull|isNull}\n * @see {@link module:lamb.isUndefined|isUndefined}\n * @since 0.1.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isNil (value) {\n return isNull(value) || isUndefined(value);\n }\n\n /**\n * Curries a function of arity 2.\n * @private\n * @param {Function} fn\n * @param {Boolean} [isRightCurry=false]\n * @returns {Function}\n */\n function _curry2 (fn, isRightCurry) {\n return function (a) {\n return function (b) {\n return isRightCurry ? fn.call(this, b, a) : fn.call(this, a, b);\n };\n };\n }\n\n /**\n * A curried version of {@link module:lamb.areSVZ|areSVZ}.
\n * Accepts a value and builds a predicate that checks whether the value\n * and the one received by the predicate are the same using the \"SameValueZero\"\n * comparison.
\n * See also {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is}\n * to perform a \"SameValue\" comparison.\n * @example\n * var john = {name: \"John\", surname: \"Doe\"};\n * var isJohn = _.isSVZ(john);\n * var isZero = _.isSVZ(0);\n * var isReallyNaN = _.isSVZ(NaN);\n *\n * isJohn(john) // => true\n * isJohn({name: \"John\", surname: \"Doe\"}) // => false\n *\n * isZero(0) // => true\n * isZero(-0) // => true\n *\n * isNaN(NaN) // => true\n * isNaN(\"foo\") // => true\n *\n * isReallyNaN(NaN) // => true\n * isReallyNaN(\"foo\") // => false\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.areSVZ|areSVZ}\n * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.1.0\n * @param {*} value\n * @returns {Function}\n */\n var isSVZ = _curry2(areSVZ);\n\n /**\n * Builds a new array by applying the iteratee function to each element of the\n * received array-like object.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example\n * _.map([\"Joe\", \"Mario\", \"Jane\"], _.invoker(\"toUpperCase\")) // => [\"JOE\", \"MARIO\", \"JANE\"]\n *\n * _.map([4, 9, 16], Math.sqrt); // => [2, 3, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.mapWith|mapWith}\n * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Array}\n */\n function map (arrayLike, iteratee) {\n var len = _toArrayLength(arrayLike.length);\n var result = Array(len);\n\n for (var i = 0; i < len; i++) {\n result[i] = iteratee(arrayLike[i], i, arrayLike);\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.map|map} that uses the provided iteratee to\n * build a function expecting the array-like object to act upon.\n * @example\n * var square = function (n) { return n * n; };\n * var getSquares = _.mapWith(square);\n *\n * getSquares([1, 2, 3, 4, 5]) // => [1, 4, 9, 16, 25]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.map|map}\n * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith}\n * @since 0.1.0\n * @param {ListIteratorCallback} iteratee\n * @returns {function}\n */\n var mapWith = _curry2(map, true);\n\n /**\n * Like {@link module:lamb.partial|partial} will build a partially applied function and\n * it will accept placeholders.
\n * The difference is that the bound arguments will be appended to the ones received by\n * the resulting function.\n * @example\n * Explaining the difference with partial:\n * var f1 = _.partial(_.list, [\"a\", \"b\", \"c\"]);\n * var f2 = _.partialRight(_.list, [\"a\", \"b\", \"c\"]);\n *\n * f1(\"d\", \"e\") // => [\"a\", \"b\", \"c\", \"d\", \"e\"]\n * f2(\"d\", \"e\") // => [\"d\", \"e\", \"a\", \"b\", \"c\"]\n *\n * @example\n * Explaining placeholder substitutions:\n * var __ = _.__;\n * var f1 = _.partial(_.list, [\"a\", __, __, \"d\"]);\n * var f2 = _.partialRight(_.list, [\"a\", __, __, \"d\"]);\n *\n * f1(\"b\", \"c\", \"e\") // => [\"a\", \"b\", \"c\", \"d\", \"e\"]\n * f2(\"b\", \"c\", \"e\") // => [\"b\", \"a\", \"c\", \"e\", \"d\"]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.partial|partial}\n * @see {@link module:lamb.asPartial|asPartial}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.__|__} The placeholder object.\n * @param {Function} fn\n * @param {Array} args\n * @since 0.52.0\n * @returns {Function}\n */\n function partialRight (fn, args) {\n return function () {\n if (!Array.isArray(args)) {\n return fn.apply(this, arguments);\n }\n\n var lastIdx = arguments.length - 1;\n var argsLen = args.length;\n var boundArgs = Array(argsLen);\n var newArgs = [];\n\n for (var i = argsLen - 1, boundArg; i > -1; i--) {\n boundArg = args[i];\n boundArgs[i] = boundArg === __ ? arguments[lastIdx--] : boundArg;\n }\n\n for (i = 0; i <= lastIdx; i++) {\n newArgs[i] = arguments[i];\n }\n\n for (var j = 0; j < argsLen; j++) {\n newArgs[i++] = boundArgs[j];\n }\n\n return fn.apply(this, newArgs);\n };\n }\n\n /**\n * Builds a reduce function. The step parameter must be 1\n * to build {@link module:lamb.reduce|reduce} and -1 to build\n * {@link module:lamb.reduceRight|reduceRight}.\n * @private\n * @param {Number} step\n * @returns {Function}\n */\n function _makeReducer (step) {\n return function (arrayLike, accumulator, initialValue) {\n var len = _toArrayLength(arrayLike.length);\n var idx = step === 1 ? 0 : len - 1;\n var nCalls;\n var result;\n\n if (arguments.length === 3) {\n nCalls = len;\n result = initialValue;\n } else {\n if (len === 0) {\n throw new TypeError(\"Reduce of empty array-like with no initial value\");\n }\n\n result = arrayLike[idx];\n idx += step;\n nCalls = len - 1;\n }\n\n for (; nCalls--; idx += step) {\n result = accumulator(result, arrayLike[idx], idx, arrayLike);\n }\n\n return result;\n };\n }\n\n /**\n * Reduces (or folds) the values of an array-like object, starting from the first, to a new\n * value using the provided accumulator function.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example\n * _.reduce([1, 2, 3, 4], _.sum) // => 10\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduceRight|reduceRight}\n * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {*}\n */\n var reduce = _makeReducer(1);\n\n /**\n * A partial application of {@link module:lamb.reduce|reduce} that uses the\n * provided accumulator and the optional initialValue to\n * build a function expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.reduceWith(_.sum)(arr) // => 15\n * _.reduceWith(_.subtract)(arr) // => -13\n * _.reduceWith(_.subtract, 0)(arr) // => -15\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduceRightWith|reduceRightWith}\n * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight}\n * @since 0.27.0\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {Function}\n */\n var reduceWith = _makePartial3(reduce, true);\n\n /**\n * Converts a value to an integer.\n * @private\n * @param {*} value\n * @returns {Number}\n */\n function _toInteger (value) {\n var n = +value;\n\n if (n !== n) { // eslint-disable-line no-self-compare\n return 0;\n } else if (n % 1 === 0) {\n return n;\n } else {\n return Math.floor(Math.abs(n)) * (n < 0 ? -1 : 1);\n }\n }\n\n /**\n * Builds an array by extracting a portion of an array-like object.
\n * Note that unlike the native array method this function ensures that dense\n * arrays are returned.
\n * Also, unlike the native method, the start and end\n * parameters aren't optional and will be simply converted to integer.
\n * See {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a\n * slice to the end of the array-like.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.slice(arr, 0, 2) // => [1, 2]\n * _.slice(arr, 2, -1) // => [3, 4]\n * _.slice(arr, -3, 5) // => [3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.sliceAt|sliceAt}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike - Any array like object.\n * @param {Number} start - Index at which to begin extraction.\n * @param {Number} end - Index at which to end extraction. Extracts up to but not including end.\n * @returns {Array}\n */\n function slice (arrayLike, start, end) {\n var len = _toArrayLength(arrayLike.length);\n var begin = _toInteger(start);\n var upTo = _toInteger(end);\n\n if (begin < 0) {\n begin = begin < -len ? 0 : begin + len;\n }\n\n if (upTo < 0) {\n upTo = upTo < -len ? 0 : upTo + len;\n } else if (upTo > len) {\n upTo = len;\n }\n\n var resultLen = upTo - begin;\n var result = resultLen > 0 ? Array(resultLen) : [];\n\n for (var i = 0; i < resultLen; i++) {\n result[i] = arrayLike[begin + i];\n }\n\n return result;\n }\n\n /**\n * Given the start and end bounds, builds a partial application\n * of {@link module:lamb.slice|slice} expecting the array-like object to slice.
\n * See also {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a\n * slice to the end of the array-like.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n * var s = \"hello\";\n * var dropFirstAndLast = _.sliceAt(1, -1);\n *\n * dropFirstAndLast(arr) // => [2, 3, 4]\n * dropFirstAndLast(s) // => [\"e\", \"l\", \"l\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.slice|slice}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @since 0.48.0\n * @param {Number} start - Index at which to begin extraction.\n * @param {Number} end - Index at which to end extraction. Extracts up to but not including end.\n * @returns {Function}\n */\n var sliceAt = _makePartial3(slice);\n\n var objectProtoToString = Object.prototype.toString;\n\n /**\n * Retrieves the \"type tag\" from the given value.\n * @example\n * var x = 5;\n * var y = new Number(5);\n *\n * typeof x // => \"number\"\n * typeof y // => \"object\"\n * _.type(x) // => \"Number\"\n * _.type(y) // => \"Number\"\n *\n * _.type(Object.prototype.toString) // => \"Function\"\n * _.type(/a/) // => \"RegExp\"\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isType|isType}\n * @since 0.9.0\n * @param {*} value\n * @returns {String}\n */\n function type (value) {\n return objectProtoToString.call(value).slice(8, -1);\n }\n\n /**\n * Appends the given value at the end of a copy of the provided array-like object.\n * @example\n * var arr = [1, 2, 3, 4];\n *\n * _.appendTo(arr, 5) // => [1, 2, 3, 4, 5]\n * _.appendTo(arr, [5]) // => [1, 2, 3, 4, [5]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.append|append}\n * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt}\n * @since 0.44.0\n * @param {ArrayLike} arrayLike\n * @param {*} value\n * @returns {Array}\n */\n function appendTo (arrayLike, value) {\n return slice(arrayLike, 0, arrayLike.length).concat([value]);\n }\n\n /**\n * A curried version of {@link module:lamb.appendTo|appendTo} that uses the value to append\n * to build a function expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4];\n *\n * _.append(5)(arr) // => [1, 2, 3, 4, 5]\n * _.append([5])(arr) // => [1, 2, 3, 4, [5]]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.appendTo|appendTo}\n * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt}\n * @since 0.44.0\n * @param {*} value\n * @returns {Function}\n */\n var append = _curry2(appendTo, true);\n\n /**\n * Checks if an array-like object contains the given value.
\n * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can\n * check for NaN, but 0 and -0 are the same value.
\n * See also {@link module:lamb.contains|contains} for a curried version building a predicate.\n * @example\n * var numbers = [0, 1, 2, 3, NaN];\n *\n * _.isIn(numbers, 1) // => true\n * _.isIn(numbers, 0) // => true\n * _.isIn(numbers, -0) // => true\n * _.isIn(numbers, NaN) // => true\n * _.isIn(numbers, 5) // => false\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.contains|contains}\n * @since 0.13.0\n * @param {ArrayLike} arrayLike\n * @param {*} value\n * @returns {Boolean}\n */\n function isIn (arrayLike, value) {\n var result = false;\n\n for (var i = 0, len = arrayLike.length; i < len; i++) {\n if (areSVZ(value, arrayLike[i])) {\n result = true;\n break;\n }\n }\n\n return result;\n }\n\n /**\n * Builds a predicate to check if an array-like object contains the given value.
\n * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can\n * check for NaN, but 0 and -0 are the same value.
\n * See also {@link module:lamb.isIn|isIn} for an uncurried version.\n * @example\n * var containsNaN = _.contains(NaN);\n *\n * containsNaN([0, 1, 2, 3, NaN]) // => true\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.isIn|isIn}\n * @since 0.13.0\n * @param {*} value\n * @returns {Function}\n */\n var contains = _curry2(isIn, true);\n\n /**\n * Builds a \"grouping function\" for an array-like object.\n * @private\n * @param {Function} makeValue\n * @returns {Function}\n */\n function _groupWith (makeValue) {\n return function (arrayLike, iteratee) {\n var result = {};\n var len = arrayLike.length;\n\n for (var i = 0, element, key; i < len; i++) {\n element = arrayLike[i];\n key = iteratee(element, i, arrayLike);\n result[key] = makeValue(result[key], element);\n }\n\n return result;\n };\n }\n\n /**\n * Transforms an array-like object in a lookup table with the keys generated by the provided\n * iteratee, having as values the count of matches for the key.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12},\n * {\"name\": \"John\", \"age\": 40},\n * {\"name\": \"Mario\", \"age\": 17},\n * {\"name\": \"Paolo\", \"age\": 15}\n * ];\n * var getAgeStatus = function (person) { return person.age >= 18 ? \"adult\" : \"minor\"; };\n *\n * _.count(persons, getAgeStatus) // => {\"adult\": 1, \"minor\": 3}\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.21.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Object}\n */\n var count = _groupWith(function (a) {\n return a ? ++a : 1;\n });\n\n /**\n * A curried version of {@link module:lamb.count|count} that uses the provided iteratee to\n * build a function expecting the array-like object to act upon.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"city\": \"New York\"},\n * {\"name\": \"John\", \"city\": \"New York\"},\n * {\"name\": \"Mario\", \"city\": \"Rome\"},\n * {\"name\": \"Paolo\"}\n * ];\n * var getCityOrUnknown = _.adapter([_.getKey(\"city\"), _.always(\"Unknown\")]);\n * var countByCity = _.countBy(getCityOrUnknown);\n *\n * countByCity(persons) // => {\"New York\": 2, \"Rome\": 1, \"Unknown\": 1}\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.count|count}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.21.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var countBy = _curry2(count, true);\n\n /**\n * Builds an array comprised of all values of the array-like object passing the predicate\n * test.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @example\n * var isLowerCase = function (s) { return s.toLowerCase() === s; };\n *\n * _.filter([\"Foo\", \"bar\", \"baZ\"], isLowerCase) // => [\"bar\"]\n *\n * // the function will work with any array-like object\n * _.filter(\"fooBAR\", isLowerCase) // => [\"f\", \"o\", \"o\"]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.filterWith|filterWith}\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @since 0.1.0\n * @returns {Array}\n */\n function filter (arrayLike, predicate) {\n var len = arrayLike.length;\n var result = [];\n\n for (var i = 0; i < len; i++) {\n predicate(arrayLike[i], i, arrayLike) && result.push(arrayLike[i]);\n }\n\n return result;\n }\n\n /**\n * Returns a predicate that negates the given one.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var isOdd = _.not(isEven);\n *\n * isOdd(5) // => true\n * isOdd(4) // => false\n *\n * @memberof module:lamb\n * @category Logic\n * @since 0.1.0\n * @param {Function} predicate\n * @returns {Function}\n */\n function not (predicate) {\n return function () {\n return !predicate.apply(this, arguments);\n };\n }\n\n /**\n * Using the provided iteratee, builds a function that will return an array comprised of the\n * unique elements of an array-like object. The values being compared are the ones returned by\n * the iteratee.
\n * The equality test is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.uniques|uniques} if you don't need to transform your values before the\n * comparison.\n * @example\n * var data = [\n * {id: \"1\", name: \"John\"},\n * {id: \"4\", name: \"Jane\"},\n * {id: \"5\", name: \"Joe\"},\n * {id: \"1\", name: \"Mario\"},\n * {id: \"5\", name: \"Paolo\"},\n * ];\n * var uniquesById = _.uniquesBy(_.getKey(\"id\"));\n *\n * uniquesById(data) // => [{id: \"1\", name: \"John\"}, {id: \"4\", name: \"Jane\"}, {id: \"5\", name: \"Joe\"}]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.uniques|uniques}\n * @since 0.51.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n function uniquesBy (iteratee) {\n return function (arrayLike) {\n var result = [];\n\n for (var i = 0, len = arrayLike.length, seen = [], value; i < len; i++) {\n value = iteratee(arrayLike[i], i, arrayLike);\n\n if (!isIn(seen, value)) {\n seen.push(value);\n result.push(arrayLike[i]);\n }\n }\n\n return result;\n };\n }\n\n /**\n * Returns an array comprised of the unique elements of the given array-like object.
\n * Note that this function uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}\n * to test the equality of values.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.uniquesBy|uniquesBy} if you need to transform your values before\n * the comparison or if you have to extract them from complex ones.\n * @example\n * _.uniques([-0, 1, 2, 0, 2, 3, 4, 3, 5, 1]) // => [-0, 1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.uniquesBy|uniquesBy}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n var uniques = uniquesBy(identity);\n\n /**\n * Returns an array of unique items present only in the first of the two given\n * array-like objects. To determine uniqueness the function uses the\n * [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var a1 = [1, 2, 1, 3, 4];\n * var a2 = [2, 4, 5, 6];\n * var a3 = [3, 4, 5, 2, 1];\n *\n * _.difference(a1, a2) // => [1, 3]\n * _.difference(a2, a3) // => [6]\n * _.difference(a1, a3) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.intersection|intersection}\n * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy}\n * @see {@link module:lamb.pull|pull}, {@link module:lamb.pullFrom|pullFrom}\n * @since 0.6.0\n * @param {ArrayLike} arrayLike\n * @param {ArrayLike} other\n * @returns {Array}\n */\n function difference (arrayLike, other) {\n var isNotInOther = partial(not(isIn), [other]);\n\n return uniques(filter(arrayLike, isNotInOther));\n }\n\n /**\n * Builds an array without the first n elements of the given array or array-like object.\n * Note that, being this only a shortcut for a specific use case of {@link module:lamb.slice|slice},\n * n can be a negative number.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.dropFrom(arr, 2) // => [3, 4, 5]\n * _.dropFrom(arr, -1) // => [5]\n * _.dropFrom(arr, -10) // => [1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @since 0.51.0\n * @param {ArrayLike} arrayLike\n * @param {Number} n\n * @returns {Array}\n */\n function dropFrom (arrayLike, n) {\n return slice(arrayLike, n, arrayLike.length);\n }\n\n /**\n * A curried version of {@link module:lamb.dropFrom|dropFrom} that expects the number of elements\n * to drop to build a function waiting for the list to take the elements from.
\n * See the note and examples for {@link module:lamb.dropFrom|dropFrom} about passing a\n * negative n.\n * @example\n * var drop2 = _.drop(2);\n *\n * drop2([1, 2, 3, 4, 5]) // => [3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @since 0.5.0\n * @see {@link module:lamb.dropFrom|dropFrom}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @param {Number} n\n * @returns {Function}\n */\n var drop = _curry2(dropFrom, true);\n\n /**\n * Gets the number of consecutive elements satisfying a predicate in an array-like object.\n * @private\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Number}\n */\n function _getNumConsecutiveHits (arrayLike, predicate) {\n var idx = 0;\n var len = arrayLike.length;\n\n while (idx < len && predicate(arrayLike[idx], idx, arrayLike)) {\n idx++;\n }\n\n return idx;\n }\n\n /**\n * Builds a function that drops the first n elements satisfying a predicate\n * from an array or array-like object.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var dropWhileIsEven = _.dropWhile(isEven);\n *\n * dropWhileIsEven([2, 4, 6, 8]) // => []\n * dropWhileIsEven([2, 4, 7, 8]) // => [7, 8]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.takeWhile|takeWhile}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @since 0.5.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n function dropWhile (predicate) {\n return function (arrayLike) {\n return slice(arrayLike, _getNumConsecutiveHits(arrayLike, predicate), arrayLike.length);\n };\n }\n\n /**\n * Helper to build the {@link module:lamb.everyIn|everyIn} or the\n * {@link module:lamb.someIn|someIn} function.\n * @private\n * @param {Boolean} defaultResult\n * @returns {Function}\n */\n function _makeArrayChecker (defaultResult) {\n return function (arrayLike, predicate) {\n for (var i = 0, len = arrayLike.length; i < len; i++) {\n if (defaultResult ^ !!predicate(arrayLike[i], i, arrayLike)) {\n return !defaultResult;\n }\n }\n\n return defaultResult;\n };\n }\n\n /**\n * Checks if all the elements in an array-like object satisfy the given predicate.
\n * The function will stop calling the predicate as soon as it returns a falsy value.
\n * Note that an empty array-like will always produce a true result regardless of the\n * predicate because of [vacuous truth]{@link https://en.wikipedia.org/wiki/Vacuous_truth}.
\n * Also note that unlike the native\n * [Array.prototype.every]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every},\n * this function won't skip deleted or unassigned indexes.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12, active: true},\n * {\"name\": \"John\", \"age\": 40, active: true},\n * {\"name\": \"Mario\", \"age\": 17, active: true},\n * {\"name\": \"Paolo\", \"age\": 15, active: true}\n * ];\n * var isAdult = _.keySatisfies(_.isGTE(18), \"age\");\n * var isActive = _.hasKeyValue(\"active\", true);\n *\n * _.everyIn(persons, isAdult) // => false\n * _.everyIn(persons, isActive) // => true\n *\n * @example Showing the difference with Array.prototype.every:\n * var isDefined = _.not(_.isUndefined);\n * var arr = new Array(5);\n * arr[3] = 99;\n *\n * arr.every(isDefined) // => true\n * _.everyIn(arr, isDefined) // => false\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.every|every}\n * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn}\n * @since 0.39.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Boolean}\n */\n var everyIn = _makeArrayChecker(true);\n\n /**\n * A curried version of {@link module:lamb.everyIn|everyIn} that expects a predicate\n * to build a function waiting for the array-like to act upon.\n * @example\n * var data = [2, 3, 5, 6, 8];\n * var isEven = function (n) { return n % 2 === 0; };\n * var allEvens = _.every(isEven);\n * var allIntegers = _.every(_.isInteger);\n *\n * allEvens(data) // => false\n * allIntegers(data) // => true\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.everyIn|everyIn}\n * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn}\n * @since 0.39.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var every = _curry2(everyIn, true);\n\n /**\n * A curried version of {@link module:lamb.filter|filter} that uses the given predicate\n * to build a function expecting the array-like object to act upon.\n * @example\n * var isLowerCase = function (s) { return s.toLowerCase() === s; };\n * var getLowerCaseEntries = _.filterWith(isLowerCase);\n *\n * getLowerCaseEntries([\"Foo\", \"bar\", \"baZ\"]) // => [\"bar\"]\n *\n * // array-like objects can be used as well\n * getLowerCaseEntries(\"fooBAR\") // => [\"f\", \"o\", \"o\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.filter|filter}\n * @since 0.9.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var filterWith = _curry2(filter, true);\n\n /**\n * Searches for an element satisfying the predicate in the given array-like object and returns its\n * index if the search is successful. Returns -1 otherwise.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 40}\n * ];\n *\n * _.findIndex(persons, _.hasKeyValue(\"age\", 40)) // => 1\n * _.findIndex(persons, _.hasKeyValue(\"age\", 41)) // => -1\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.findIndexWhere|findIndexWhere}\n * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere}\n * @since 0.7.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Number}\n */\n function findIndex (arrayLike, predicate) {\n var result = -1;\n\n for (var i = 0, len = arrayLike.length; i < len; i++) {\n if (predicate(arrayLike[i], i, arrayLike)) {\n result = i;\n break;\n }\n }\n\n return result;\n }\n\n /**\n * Searches for an element satisfying the predicate in the given array-like object and returns it if\n * the search is successful. Returns undefined otherwise.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 40}\n * ];\n *\n * _.find(persons, _.hasKeyValue(\"age\", 40)) // => {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40}\n * _.find(persons, _.hasKeyValue(\"age\", 41)) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.findWhere|findWhere}\n * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere}\n * @since 0.7.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {*}\n */\n function find (arrayLike, predicate) {\n var idx = findIndex(arrayLike, predicate);\n\n return idx === -1 ? void 0 : arrayLike[idx];\n }\n\n /**\n * A curried version of {@link module:lamb.find|find} expecting the array-like object\n * to search.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var findEven = _.findWhere(isEven);\n *\n * findEven([1, 3, 4, 5, 7]) // => 4\n * findEven([1, 3, 5, 7]) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.find|find}\n * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere}\n * @since 0.41.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var findWhere = _curry2(find, true);\n\n /**\n * A curried version of {@link module:lamb.findIndex|findIndex} that uses the given predicate\n * to build a function expecting the array-like object to search.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var findEvenIdx = _.findIndexWhere(isEven);\n *\n * findEvenIdx([1, 3, 4, 5, 7]) // => 2\n * findEvenIdx([1, 3, 5, 7]) // => -1\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.findIndex|findIndex}\n * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere}\n * @since 0.41.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var findIndexWhere = _curry2(findIndex, true);\n\n /**\n * Similar to {@link module:lamb.map|map}, but if the mapping function returns an array this will\n * be concatenated, rather than pushed, to the final result.\n * @example Showing the difference with map:\n * var words = [\"foo\", \"bar\"];\n * var toCharArray = function (s) { return s.split(\"\"); };\n *\n * _.map(words, toCharArray) // => [[\"f\", \"o\", \"o\"], [\"b\", \"a\", \"r\"]]\n * _.flatMap(words, toCharArray) // => [\"f\", \"o\", \"o\", \"b\", \"a\", \"r\"]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.flatMapWith|flatMapWith}\n * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith}\n * @since 0.1.0\n * @param {Array} array\n * @param {ListIteratorCallback} iteratee\n * @returns {Array}\n */\n function flatMap (array, iteratee) {\n return reduce(array, function (result, el, idx, arr) {\n var v = iteratee(el, idx, arr);\n\n if (!Array.isArray(v)) {\n v = [v];\n }\n\n for (var i = 0, len = v.length, rLen = result.length; i < len; i++) {\n result[rLen + i] = v[i];\n }\n\n return result;\n }, []);\n }\n\n /**\n * A curried version of {@link module:lamb.flatMap|flatMap} that uses provided iteratee\n * to build a function expecting the array to act upon.\n * @example\n * var toCharArray = function (s) { return s.split(\"\"); };\n * var wordsToCharArray = _.flatMapWith(toCharArray);\n *\n * wordsToCharArray([\"foo\", \"bar\"]) // => [\"f\", \"o\", \"o\", \"b\", \"a\", \"r\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.flatMap|flatMap}\n * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith}\n * @since 0.11.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var flatMapWith = _curry2(flatMap, true);\n\n /**\n * Flattens an array.\n * @private\n * @param {Array} array - The source array\n * @param {Boolean} isDeep - Whether to perform a deep flattening or not\n * @param {Array} output - An array to collect the result\n * @param {Number} idx - The next index to be filled in the output\n * @returns {Array} The output array filled with the results\n */\n function _flatten (array, isDeep, output, idx) {\n for (var i = 0, len = array.length, value, j, vLen; i < len; i++) {\n value = array[i];\n\n if (!Array.isArray(value)) {\n output[idx++] = value;\n } else if (isDeep) {\n _flatten(value, true, output, idx);\n idx = output.length;\n } else {\n vLen = value.length;\n output.length += vLen;\n\n for (j = 0; j < vLen; j++) {\n output[idx++] = value[j];\n }\n }\n }\n\n return output;\n }\n\n /**\n * Helper to build the {@link module:lamb.flatten|flatten} and\n * {@link module:lamb.shallowFlatten|shallowFlatten} functions.\n * @private\n * @function\n * @param {Boolean} isDeep\n * @returns {Function}\n */\n var _makeArrayFlattener = _curry2(function (isDeep, array) {\n return Array.isArray(array) ? _flatten(array, isDeep, [], 0) : slice(array, 0, array.length);\n });\n\n /**\n * Flattens an array.\n * @example Showing the difference with shallowFlatten:\n * var arr = [1, 2, [3, 4, [5, 6]], 7, 8];\n *\n * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8]\n * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.shallowFlatten|shallowFlatten}\n * @since 0.1.0\n * @param {Array} array\n * @returns {Array}\n */\n var flatten = _makeArrayFlattener(true);\n\n /**\n * Checks if the given number, even negative, represents an array-like index\n * within the provided length. If so returns its natural number equivalent.
\n * Returns NaN otherwise.\n * @private\n * @param {Number} idx\n * @param {Number} len\n * @returns {Number}\n */\n function _toNaturalIndex (idx, len) {\n idx = _toInteger(idx);\n\n return idx >= -len && idx < len ? idx < 0 ? idx + len : idx : NaN;\n }\n\n /**\n * Retrieves the element at the given index in an array-like object.
\n * Like {@link module:lamb.slice|slice} the index can be negative.
\n * If the index isn't supplied, or if its value isn't an integer within the array-like bounds,\n * the function will return undefined.
\n * getIndex will throw an exception when receives null or\n * undefined in place of an array-like object, but returns undefined\n * for any other value.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.getIndex(arr, 1) // => 2\n * _.getIndex(arr, -1) // => 5\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.getAt|getAt}\n * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts.\n * @since 0.23.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @returns {*}\n */\n function getIndex (arrayLike, index) {\n var idx = _toNaturalIndex(index, _toArrayLength(arrayLike.length));\n\n return idx === idx ? arrayLike[idx] : void 0; // eslint-disable-line no-self-compare\n }\n\n /**\n * A curried version of {@link module:lamb.getIndex|getIndex} that uses the provided index\n * to build a function expecting the array-like object holding the element we want to retrieve.\n * @example\n * var getFifthElement = _.getAt(4);\n *\n * getFifthElement([1, 2, 3, 4, 5]) // => 5\n * getFifthElement(\"foo bar\") // => \"b\"\n * getFifthElement([]) // => undefined\n * getFifthElement(\"foo\") // => undefined\n *\n * @example Using negative indexes:\n * _.getAt(-2)([1, 2, 3]) // => 2\n * _.getAt(-3)(\"foo\") // => \"f\"\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @since 0.16.0\n * @see {@link module:lamb.getIndex|getIndex}\n * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts.\n * @param {Number} index\n * @returns {Function}\n */\n var getAt = _curry2(getIndex, true);\n\n /**\n * Transforms an array-like object into a lookup table using the provided iteratee as a grouping\n * criterion to generate keys and values.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"city\": \"New York\"},\n * {\"name\": \"John\", \"city\": \"New York\"},\n * {\"name\": \"Mario\", \"city\": \"Rome\"},\n * {\"name\": \"Paolo\"}\n * ];\n * var getCity = _.getKey(\"city\");\n * var personsByCity = _.group(persons, getCity);\n *\n * // \"personsByCity\" holds:\n * // {\n * // \"New York\": [\n * // {\"name\": \"Jane\", \"city\": \"New York\"},\n * // {\"name\": \"John\", \"city\": \"New York\"}\n * // ],\n * // \"Rome\": [\n * // {\"name\": \"Mario\", \"city\": \"Rome\"}\n * // ],\n * // \"undefined\": [\n * // {\"name\": \"Paolo\"}\n * // ]\n * // }\n *\n * @example Adding a custom value for missing keys:\n *\n * var getCityOrUnknown = _.adapter([getCity, _.always(\"Unknown\")]);\n *\n * var personsByCity = _.group(persons, getCityOrUnknown);\n *\n * // \"personsByCity\" holds:\n * // {\n * // \"New York\": [\n * // {\"name\": \"Jane\", \"city\": \"New York\"},\n * // {\"name\": \"John\", \"city\": \"New York\"}\n * // ],\n * // \"Rome\": [\n * // {\"name\": \"Mario\", \"city\": \"Rome\"}\n * // ],\n * // \"Unknown\": [\n * // {\"name\": \"Paolo\"}\n * // ]\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.groupBy|groupBy}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.7.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Object}\n */\n var group = _groupWith(function (a, b) {\n if (!a) {\n return [b];\n }\n\n a[a.length] = b;\n\n return a;\n });\n\n /**\n * A curried version of {@link module:lamb.group|group} that uses the provided iteratee\n * to build a function expecting the array-like object to act upon.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12},\n * {\"name\": \"John\", \"age\": 40},\n * {\"name\": \"Mario\", \"age\": 18},\n * {\"name\": \"Paolo\", \"age\": 15}\n * ];\n *\n * var getAgeStatus = function (person) { return person.age > 20 ? \"over 20\" : \"under 20\"; };\n * var groupByAgeStatus = _.groupBy(getAgeStatus);\n *\n * var personsByAgeStatus = groupByAgeStatus(persons);\n *\n * // \"personsByAgeStatus\" holds:\n * // {\n * // \"under 20\": [\n * // {\"name\": \"Jane\", \"age\": 12},\n * // {\"name\": \"Mario\", \"age\": 18},\n * // {\"name\": \"Paolo\", \"age\": 15}\n * // ],\n * // \"over 20\": [\n * // {\"name\": \"John\", \"age\": 40}\n * // ]\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.group|group}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy}\n * @since 0.7.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var groupBy = _curry2(group, true);\n\n /**\n * Retrieves the first element of an array-like object.
\n * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience.\n * @example\n * _.head([1, 2, 3]) // => 1\n * _.head(\"hello\") // => \"h\"\n * _.head([]) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.last|last}\n * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {*}\n */\n var head = getAt(0);\n\n /**\n * Similar to {@link module:lamb.group|group}, but the generated lookup table will have\n * only one element of the original array-like object for each value.
\n * Should be used only when you're sure that your iteratee won't produce\n * duplicate keys, otherwise only the last evaluated element will be in the result.\n * @example\n * var users = [\n * {id: 1, name: \"John\"},\n * {id: 2, name: \"Jane\"},\n * {id: 3, name: \"Mario\"},\n * {id: 4, name: \"John\"}\n * ];\n *\n * var indexedUsers = _.index(users, _.getKey(\"id\"));\n *\n * // \"indexedUsers\" holds:\n * // {\n * // \"1\": {id: 1, name: \"John\"},\n * // \"2\": {id: 2, name: \"Jane\"},\n * // \"3\": {id: 3, name: \"Mario\"},\n * // \"4\": {id: 4, name: \"John\"}\n * // }\n *\n * @example Result of an iteratee producing a duplicate key:\n * var users = [\n * {id: 1, name: \"John\"},\n * {id: 2, name: \"Jane\"},\n * {id: 3, name: \"Mario\"},\n * {id: 4, name: \"John\"}\n * ];\n *\n * var indexedUsers = _.index(users, _.getKey(\"name\"));\n *\n * // \"indexedUsers\" holds:\n * // {\n * // \"John\": {\"id\": 4, \"name\": \"John\"},\n * // \"Jane\": {\"id\": 2, \"name\": \"Jane\"},\n * // \"Mario\": {\"id\": 3, \"name\": \"Mario\"}\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.indexBy|indexBy}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @since 0.21.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} iteratee\n * @returns {Object}\n */\n var index = _groupWith(function (a, b) {\n return b;\n });\n\n /**\n * A curried version of {@link module:lamb.index|index} that uses the provided iteratee\n * to build a function expecting the array-like object to act upon.\n * @example\n * var users = [\n * {id: 1, name: \"John\"},\n * {id: 2, name: \"Jane\"},\n * {id: 3, name: \"Mario\"}\n * ];\n * var indexByID = _.indexBy(_.getKey(\"id\"));\n *\n * var indexedUsers = indexByID(users);\n *\n * // \"indexedUsers\" holds:\n * // {\n * // \"1\": {id: 1, name: \"John\"},\n * // \"2\": {id: 2, name: \"Jane\"},\n * // \"3\": {id: 3, name: \"Mario\"}\n * // }\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.index|index}\n * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy}\n * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy}\n * @since 0.21.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n var indexBy = _curry2(index, true);\n\n /**\n * Returns a copy of the given array-like object without the last element.\n * @example\n * _.init([1, 2, 3, 4]) // => [1, 2, 3]\n * _.init([1]) // => []\n * _.init([]) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.tail|tail}\n * @see {@link module:lamb.head|head}, {@link module:lamb.last|last}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n var init = partial(slice, [__, 0, -1]);\n\n /**\n * Inserts the provided element in a copy of an array-like object at the\n * specified index.
\n * If the index is greater than the length of the array-like, the element\n * will be appended at the end.
\n * Negative indexes are allowed; when a negative index is out of bounds\n * the element will be inserted at the start of the resulting array.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.insert(arr, 3, 99) // => [1, 2, 3, 99, 4, 5]\n * _.insert(arr, -2, 99) // => [1, 2, 3, 99, 4, 5]\n * _.insert(arr, 10, 99) // => [1, 2, 3, 4, 5, 99]\n * _.insert(arr, -10, 99) // => [99, 1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.insertAt|insertAt}\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @param {*} element\n * @returns {Array}\n */\n function insert (arrayLike, index, element) {\n var result = slice(arrayLike, 0, arrayLike.length);\n\n result.splice(index, 0, element);\n\n return result;\n }\n\n /**\n * Builds a partial application of {@link module:lamb.insert|insert}\n * expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.insertAt(3, 99)(arr) // => [1, 2, 3, 99, 4, 5]\n * _.insertAt(-2, 99)(arr) // => [1, 2, 3, 99, 4, 5]\n * _.insertAt(10, 99)(arr) // => [1, 2, 3, 4, 5, 99]\n * _.insertAt(-10, 99)(arr) // => [99, 1, 2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.insert|insert}\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo}\n * @since 0.27.0\n * @param {Number} index\n * @param {*} element\n * @returns {Function}\n */\n var insertAt = _makePartial3(insert);\n\n /**\n * Returns an array of every unique item that is included in all two given arrays\n * or array-like objects.
\n * Note that this function uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var a1 = [1, 2, 3, 4];\n * var a2 = [2, 5, 4, 2, 6];\n * var a3 = [5, 6, 7];\n *\n * _.intersection(a1, a2) // => [2, 4]\n * _.intersection(a2, a3) // => [5, 6]\n * _.intersection(a1, a3) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.difference|difference}\n * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy}\n * @since 0.5.0\n * @param {ArrayLike} a\n * @param {ArrayLike} b\n * @returns {Array}\n */\n function intersection (a, b) {\n var result = [];\n var lenA = a.length;\n\n if (lenA && b.length) {\n for (var i = 0; i < lenA; i++) {\n !isIn(result, a[i]) && isIn(b, a[i]) && result.push(a[i]);\n }\n }\n\n return result;\n }\n\n /**\n * Retrieves the last element of an array-like object.
\n * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience.\n * @example\n * _.last([1, 2, 3]) // => 3\n * _.last(\"hello\") // => \"o\"\n * _.last([]) // => undefined\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.head|head}\n * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {*}\n */\n var last = getAt(-1);\n\n /**\n * Builds helper functions to extract portions of the arguments\n * object rather efficiently without having to write for loops\n * manually for each case.
\n * The arguments object needs to be passed to the apply method\n * of the generated function.\n * @private\n * @param {Number} idx\n * @returns {Function}\n */\n function _argsToArrayFrom (idx) {\n return function () {\n var argsLen = arguments.length || idx;\n var len = argsLen - idx;\n var result = Array(len);\n\n for (var i = 0; i < len; i++) {\n result[i] = arguments[i + idx];\n }\n\n return result;\n };\n }\n\n /**\n * Generates an array with the values passed as arguments.
\n * Behaves like ES6's [Array.of]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of}.\n * @example\n * _.list(1, 2, 3) // => [1, 2, 3]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @since 0.1.0\n * @param {...*} value\n * @returns {Array}\n */\n var list = _argsToArrayFrom(0);\n\n /**\n * Splits an array-like object in two lists: the first with the elements satisfying the given predicate,\n * the others with the remaining elements.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n *\n * _.partition(numbers, isEven) // => [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.partitionWith|partitionWith}\n * @since 0.11.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Array}\n */\n function partition (arrayLike, predicate) {\n var result = [[], []];\n var len = arrayLike.length;\n\n for (var i = 0, el; i < len; i++) {\n el = arrayLike[i];\n result[predicate(el, i, arrayLike) ? 0 : 1].push(el);\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.partition|partition} that uses the provided\n * predicate to build a function expecting the array-like object to act upon.\n * @example\n * var users = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"active\": false},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"active\": true},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"active\": true},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"active\": false}\n * ];\n * var isActive = _.hasKeyValue(\"active\", true);\n * var splitByActiveStatus = _.partitionWith(isActive);\n *\n * splitByActiveStatus(users) // =>\n * // [[\n * // {\"name\": \"John\", \"surname\": \"Doe\", \"active\": true},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\", \"active\": true}\n * // ], [\n * // {\"name\": \"Jane\", \"surname\": \"Doe\", \"active\": false},\n * // {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"active\": false}\n * // ]]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.partition|partition}\n * @since 0.11.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var partitionWith = _curry2(partition, true);\n\n /**\n * Returns the value of the object property with the given key.\n * @example\n * var user = {name: \"John\"};\n *\n * _.getIn(user, \"name\") // => \"John\";\n * _.getIn(user, \"surname\") // => undefined\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.getKey|getKey}\n * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn}\n * @since 0.18.0\n * @param {Object} obj\n * @param {String} key\n * @returns {*}\n */\n function getIn (obj, key) {\n return obj[key];\n }\n\n /**\n * A curried version of {@link module:lamb.getIn|getIn}.
\n * Receives a property name and builds a function expecting the object from which we want to retrieve\n * the property.\n * @example\n * var user1 = {name: \"john\"};\n * var user2 = {name: \"jane\"};\n * var getName = _.getKey(\"name\");\n *\n * getName(user1) // => \"john\"\n * getName(user2) // => \"jane\"\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.getIn|getIn}\n * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn}\n * @since 0.1.0\n * @param {String} key\n * @returns {Function}\n */\n var getKey = _curry2(getIn, true);\n\n /**\n * \"Plucks\" the values of the specified key from a list of objects.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 15}\n * ];\n *\n * _.pluck(persons, \"age\") // => [12, 40, 18, 15]\n *\n * var lists = [\n * [1, 2],\n * [3, 4, 5],\n * [6]\n * ];\n *\n * _.pluck(lists, \"length\") // => [2, 3, 1]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.pluckKey|pluckKey}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {String} key\n * @returns {Array}\n */\n function pluck (arrayLike, key) {\n return map(arrayLike, getKey(key));\n }\n\n /**\n * A curried version of {@link module:lamb.pluck|pluck} expecting the key to retrieve to\n * build a function waiting for the array-like object to act upon.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"surname\": \"Doe\", \"age\": 12},\n * {\"name\": \"John\", \"surname\": \"Doe\", \"age\": 40},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\", \"age\": 18},\n * {\"name\": \"Paolo\", \"surname\": \"Bianchi\", \"age\": 15}\n * ];\n * var getAges = _.pluckKey(\"age\");\n *\n * getAges(persons) // => [12, 40, 18, 15]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.pluck|pluck}\n * @since 0.12.0\n * @param {String} key\n * @returns {Function}\n */\n var pluckKey = compose(mapWith, getKey);\n\n /**\n * Creates an array copy of the given array-like object without the\n * specified values.
\n * The equality test is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.pullFrom(arr, [2, 5]) // => [1, 3, 4]\n *\n * @example It's not the same as {@link module:lamb.difference|difference}:\n *\n * var arr = [1,1,2,3,4,4,5];\n *\n * _.pullFrom(arr, [1, 2]) // => [3, 4, 4, 5]\n * _.difference(arr, [1, 2]) // => [3, 4, 5]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.pull|pull}\n * @see {@link module:lamb.difference|difference}\n * @since 0.45.0\n * @param {ArrayLike} arrayLike\n * @param {ArrayLike} values\n * @returns {Array}\n */\n function pullFrom (arrayLike, values) {\n return values ? filter(arrayLike, function (element) {\n return !isIn(values, element);\n }) : slice(arrayLike, 0, arrayLike.length);\n }\n\n /**\n * A curried version of {@link module:lamb.pullFrom|pullFrom} expecting\n * a list of values to build a function waiting for an array-like object.
\n * The new function will create an array copy of the array-like without\n * the specified values.
\n * The equality test is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.
\n * See examples in {@link module:lamb.pullFrom|pullFrom} about the\n * relationship with {@link module:lamb.difference|difference}.\n * @example\n * var scores = [40, 20, 30, 10];\n * var newScores = [30, 10];\n * var pullNewScores = _.pull(newScores);\n *\n * pullNewScores(scores) // => [40, 20]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.pullFrom|pullFrom}\n * @see {@link module:lamb.difference|difference}\n * @since 0.45.0\n * @param {ArrayLike} values\n * @returns {Function}\n */\n var pull = _curry2(pullFrom, true);\n\n /**\n * Same as {@link module:lamb.reduce|reduce}, but starts the fold operation from the last\n * element instead.
\n * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes.\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduce|reduce}\n * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith}\n * @since 0.1.0\n * @param {ArrayLike} arrayLike\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {*}\n */\n var reduceRight = _makeReducer(-1);\n\n /**\n * A partial application of {@link module:lamb.reduce|reduceRight} that uses the\n * provided accumulator and the optional initialValue to\n * build a function expecting the array-like object to act upon.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.reduceRightWith(_.sum)(arr) // => 15\n * _.reduceRightWith(_.subtract)(arr) // => -5\n * _.reduceRightWith(_.subtract, 0)(arr) // => -15\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.reduceWith|reduceWith}\n * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight}\n * @since 0.27.0\n * @param {AccumulatorCallback} accumulator\n * @param {*} [initialValue]\n * @returns {Function}\n */\n var reduceRightWith = _makePartial3(reduceRight, true);\n\n /**\n * Reverses a copy of the given array-like object.\n * @example\n * var arr = [1, 2, 3];\n *\n * _.reverse(arr) // => [3, 2, 1];\n *\n * // `arr` still is [1, 2, 3]\n *\n * @memberof module:lamb\n * @category Array\n * @since 0.19.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n function reverse (arrayLike) {\n var len = _toArrayLength(arrayLike.length);\n var result = Array(len);\n\n for (var i = 0, ofs = len - 1; i < len; i++) {\n result[i] = arrayLike[ofs - i];\n }\n\n return result;\n }\n\n /**\n * Returns a copy of the given array-like with the element rotated by the desired amount.\n * Negative indexes are allowed.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.rotate(arr, 3) // => [3, 4, 5, 1, 2]\n * _.rotate(arr, -3) // => [4, 5, 1, 2, 3]\n * _.rotate(arr, 11) // => [5, 1, 2, 3, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.rotateBy|rotateBy}\n * @since 0.55.0\n * @param {ArrayLike} arrayLike\n * @param {Number} amount\n * @returns {Array}\n */\n function rotate (arrayLike, amount) {\n var len = arrayLike.length;\n var shift = amount % len;\n\n return slice(arrayLike, -shift, len).concat(slice(arrayLike, 0, -shift));\n }\n\n /**\n * A curried version of {@link module:lamb.rotate|rotate}.
\n * Uses the given amount to build a function expecting the array to rotate by that amount.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n * var rotateByTwo = _.rotateBy(2);\n *\n * rotateByTwo(arr) // => [4, 5, 1, 2, 3]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.rotate|rotate}\n * @since 0.55.0\n * @param {Number} amount\n * @returns {Function}\n */\n var rotateBy = _curry2(rotate, true);\n\n /**\n * Sets an index in an array-like object.
\n * If provided with an updater function it will use it to update the current value,\n * otherwise sets the index to the specified value.\n * @private\n * @param {ArrayLike} arrayLike\n * @param {Number} idx\n * @param {*} [value]\n * @param {Function} [updater]\n * @returns {Array}\n */\n function _setIndex (arrayLike, idx, value, updater) {\n var result = slice(arrayLike, 0, arrayLike.length);\n var n = _toNaturalIndex(idx, result.length);\n\n if (n === n) { // eslint-disable-line no-self-compare\n result[n] = arguments.length === 4 ? updater(arrayLike[n]) : value;\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.setIndex|setIndex} that builds\n * a function that creates a copy of an array-like object with the given\n * index changed to the desired value.
\n * If the index is not an integer or if it's out of bounds, the function\n * will return a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.setAt(2, 99)(arr) // => [1, 2, 99, 4, 5]\n * arr // => [1, 2, 3, 4, 5]\n *\n * _.setAt(10, 99)(arr) // => [1, 2, 3, 4, 5] (not a reference to `arr`)\n *\n * @example Using negative indexes:\n * _.setAt(-1, 99)(arr) // => [1, 2, 3, 4, 99]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.setIndex|setIndex}\n * @since 0.17.0\n * @param {Number} index\n * @param {*} value\n * @returns {Function}\n */\n var setAt = _makePartial3(_setIndex);\n\n /**\n * Builds a new function that passes only the specified amount of arguments to the original one.
\n * As {@link module:lamb.slice|slice} is used to extract the arguments, you can also\n * pass a negative arity.\n * @example\n * Math.max(10, 11, 45, 99) // => 99\n * _.aritize(Math.max, 2)(10, 11, 45, 99) // => 11\n *\n * @example Using a negative arity:\n * _.aritize(Math.max, -1)(10, 11, 45, 99) // => 45\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.binary|binary}, {@link module:lamb.unary|unary} for common use cases shortcuts\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} arity\n * @returns {Function}\n */\n function aritize (fn, arity) {\n return function () {\n var n = _toInteger(arity);\n var args = list.apply(null, arguments).slice(0, n);\n\n for (var i = args.length; i < n; i++) {\n args[i] = void 0;\n }\n\n return fn.apply(this, args);\n };\n }\n\n /**\n * Creates a copy of an array-like object with the given index changed to\n * the desired value.
\n * If the index is not an integer or if it's out of bounds, the function\n * will return a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [1, 2, 3];\n *\n * _.setIndex(arr, 1, 99) // => [1, 99, 3]\n * _.setIndex(arr, -1, 99) // => [1, 2, 99]\n * _.setIndex(arr, 10, 99) // => [1, 2, 3] (not a reference to `arr`)\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.setAt|setAt}\n * @since 0.23.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @param {*} value\n * @returns {Array}\n */\n var setIndex = aritize(_setIndex, 3);\n\n /**\n * Flattens the \"first level\" of an array.\n * @example Showing the difference with flatten:\n * var arr = [1, 2, [3, 4, [5, 6]], 7, 8];\n *\n * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8]\n * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.flatten|flatten}\n * @since 0.9.0\n * @param {Array} array\n * @returns {Array}\n */\n var shallowFlatten = _makeArrayFlattener(false);\n\n /**\n * Checks if at least one element in an array-like object satisfies the given predicate.
\n * The function will stop calling the predicate as soon as it returns a truthy value.
\n * Note that unlike the native\n * [Array.prototype.some]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some},\n * this function won't skip deleted or unassigned indexes.\n * @example\n * var persons = [\n * {\"name\": \"Jane\", \"age\": 12, active: false},\n * {\"name\": \"John\", \"age\": 40, active: false},\n * {\"name\": \"Mario\", \"age\": 17, active: false},\n * {\"name\": \"Paolo\", \"age\": 15, active: false}\n * ];\n * var isAdult = _.keySatisfies(_.isGTE(18), \"age\");\n * var isActive = _.hasKeyValue(\"active\", true);\n *\n * _.someIn(persons, isAdult) // => true\n * _.someIn(persons, isActive) // => false\n *\n * @example Showing the difference with Array.prototype.some:\n * var arr = new Array(5);\n * arr[3] = 99;\n *\n * arr.some(_.isUndefined) // => false\n * _.someIn(arr, _.isUndefined) // => true\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.some|some}\n * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn}\n * @since 0.39.0\n * @param {ArrayLike} arrayLike\n * @param {ListIteratorCallback} predicate\n * @returns {Boolean}\n */\n var someIn = _makeArrayChecker(false);\n\n /**\n * A curried version of {@link module:lamb.someIn|someIn} that uses the given predicate to\n * build a function waiting for the array-like to act upon.\n * @example\n * var data = [1, 3, 5, 6, 7, 8];\n * var isEven = function (n) { return n % 2 === 0; };\n * var containsEvens = _.some(isEven);\n * var containsStrings = _.some(_.isType(\"String\"));\n *\n * containsEvens(data) // => true\n * containsStrings(data) // => false\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.someIn|someIn}\n * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn}\n * @since 0.39.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n var some = _curry2(someIn, true);\n\n /**\n * Accepts a list of sorting criteria with at least one element\n * and builds a function that compares two values with such criteria.\n * @private\n * @param {Sorter[]} criteria\n * @returns {Function}\n */\n function _compareWith (criteria) {\n return function (a, b) {\n var len = criteria.length;\n var criterion = criteria[0];\n var result = criterion.compare(a.value, b.value);\n\n for (var i = 1; result === 0 && i < len; i++) {\n criterion = criteria[i];\n result = criterion.compare(a.value, b.value);\n }\n\n if (result === 0) {\n result = a.index - b.index;\n }\n\n return criterion.isDescending ? -result : result;\n };\n }\n\n /**\n * The default comparer for sorting functions.
\n * If the given values are of different types they\n * will be both converted to strings.
\n * Uses the SameValueZero comparison.\n * @private\n * @param {*} a\n * @param {*} b\n * @returns {Number} -1 | 0 | 1\n */\n function _comparer (a, b) {\n var result = 0;\n\n if (typeof a !== typeof b) {\n a = String(a);\n b = String(b);\n }\n\n if (!areSVZ(a, b)) {\n // eslint-disable-next-line no-self-compare\n result = a > b || a !== a ? 1 : -1;\n }\n\n return result;\n }\n\n /**\n * Builds a sorting criterion. If the comparer function is missing, the default\n * comparer will be used instead.\n * @private\n * @param {Function} reader\n * @param {Boolean} isDescending\n * @param {Function} [comparer]\n * @returns {Sorter}\n */\n function _sorter (reader, isDescending, comparer) {\n if (typeof reader !== \"function\" || reader === identity) {\n reader = null;\n }\n\n if (typeof comparer !== \"function\") {\n comparer = _comparer;\n }\n\n return {\n isDescending: isDescending === true,\n compare: function (a, b) {\n if (reader) {\n a = reader(a);\n b = reader(b);\n }\n\n return comparer(a, b);\n }\n };\n }\n\n /**\n * Converts a sorting function to a sorting criterion if necessary.\n * @private\n * @param {Function} criterion\n * @returns {Sorter}\n */\n function _makeCriterion (criterion) {\n return criterion && typeof criterion.compare === \"function\" ? criterion : _sorter(criterion);\n }\n\n /**\n * Builds a list of sorting criteria from a list of sorter functions. Returns a list containing\n * a single default sorting criterion if the sorter list is empty.\n * @private\n * @param {Function[]} sorters\n * @returns {Sorter[]}\n */\n function _makeCriteria (sorters) {\n return sorters && sorters.length ? map(sorters, _makeCriterion) : [_sorter()];\n }\n\n /**\n * Returns a [stably]{@link https://en.wikipedia.org/wiki/Sorting_algorithm#Stability} sorted\n * copy of an array-like object using the given criteria.
\n * Sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, but you\n * can also pass simple \"reader\" functions and default ascending sorters will be built for you.
\n * A \"reader\" is a function that evaluates the array element and supplies the value to be used\n * in the comparison.
\n * Please note that if the arguments received by the default comparer aren't of the same type,\n * they will be compared as strings.\n *\n * @example Stable sort:\n * var persons = [\n * {\"name\": \"John\", \"surname\" :\"Doe\"},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\"},\n * {\"name\": \"John\", \"surname\" :\"Moe\"},\n * {\"name\": \"Jane\", \"surname\": \"Foe\"}\n * ];\n *\n * var personsByName = _.sort(persons, [_.getKey(\"name\")]);\n *\n * // personsByName holds:\n * // [\n * // {\"name\": \"Jane\", \"surname\": \"Foe\"},\n * // {\"name\": \"John\", \"surname\" :\"Doe\"},\n * // {\"name\": \"John\", \"surname\" :\"Moe\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @example Stable multi-sort:\n * var personsByNameAscSurnameDesc = _.sort(persons, [\n * _.getKey(\"name\"),\n * _.sorterDesc(_.getKey(\"surname\"))\n * ]);\n *\n * // personsByNameAscSurnameDesc holds:\n * // [\n * // {\"name\": \"Jane\", \"surname\": \"Foe\"},\n * // {\"name\": \"John\", \"surname\" :\"Moe\"},\n * // {\"name\": \"John\", \"surname\" :\"Doe\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @example Using custom comparers:\n * var localeSorter = new Intl.Collator(\"it\");\n * var chars = [\"a\", \"è\", \"à\", \"é\", \"c\", \"b\", \"e\"];\n *\n * _.sort(chars, [localeSorter]) // => [\"a\", \"à\", \"b\", \"c\", \"e\", \"é\", \"è\"]\n *\n * var localeSorterDesc = _.sorterDesc(_.identity, localeSorter.compare);\n *\n * _.sort(chars, [localeSorterDesc]) // => [\"è\", \"é\", \"e\", \"c\", \"b\", \"à\", \"a\"]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc}\n * @since 0.15.0\n * @param {ArrayLike} arrayLike\n * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]]\n * @returns {Array}\n */\n function sort (arrayLike, sorters) {\n var criteria = _makeCriteria(sorters);\n var len = _toArrayLength(arrayLike.length);\n var result = Array(len);\n\n for (var i = 0; i < len; i++) {\n result[i] = { value: arrayLike[i], index: i };\n }\n\n result.sort(_compareWith(criteria));\n\n for (i = 0; i < len; i++) {\n result[i] = result[i].value;\n }\n\n return result;\n }\n\n /**\n * Establishes at which index an element should be inserted in a sorted array to respect\n * the array order. Needs the comparer used to sort the array.\n * @private\n * @param {Array} array\n * @param {*} element\n * @param {Function} comparer\n * @param {Number} start\n * @param {Number} end\n * @returns {Number}\n */\n function _getInsertionIndex (array, element, comparer, start, end) {\n if (array.length === 0) {\n return 0;\n }\n\n var pivot = (start + end) >> 1;\n var result = comparer(\n { value: element, index: pivot },\n { value: array[pivot], index: pivot }\n );\n\n if (end - start <= 1) {\n return result < 0 ? pivot : pivot + 1;\n } else if (result < 0) {\n return _getInsertionIndex(array, element, comparer, start, pivot);\n } else if (result === 0) {\n return pivot + 1;\n } else {\n return _getInsertionIndex(array, element, comparer, pivot, end);\n }\n }\n\n /**\n * Inserts an element in a copy of a sorted array respecting the sort order.\n * @example With simple values:\n * _.sortedInsert([], 1) // => [1]\n * _.sortedInsert([2, 4, 6], 5) // => [2, 4, 5, 6]\n * _.sortedInsert([4, 2, 1], 3, _.sorterDesc()) // => [4, 3, 2, 1]\n *\n * @example With complex values:\n * var persons = [\n * {\"name\": \"jane\", \"surname\": \"doe\"},\n * {\"name\": \"John\", \"surname\": \"Doe\"},\n * {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * ];\n *\n * var getLowerCaseName = _.compose(\n * _.invoker(\"toLowerCase\"),\n * _.getKey(\"name\")\n * );\n *\n * var result = _.sortedInsert(\n * persons,\n * {\"name\": \"marco\", \"surname\": \"Rossi\"},\n * getLowerCaseName\n * );\n *\n * // `result` holds:\n * // [\n * // {\"name\": \"jane\", \"surname\": \"doe\"},\n * // {\"name\": \"John\", \"surname\": \"Doe\"},\n * // {\"name\": \"marco\", \"surname\": \"Rossi\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc}\n * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} to insert the element\n * at a specific index\n * @since 0.27.0\n * @param {ArrayLike} arrayLike\n * @param {*} element\n * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - The sorting criteria\n * used to sort the array.\n * @returns {Array}\n */\n function sortedInsert (arrayLike, element, sorters) {\n var result = slice(arrayLike, 0, arrayLike.length);\n\n if (arguments.length === 1) {\n return result;\n }\n\n var criteria = _makeCriteria(sorters);\n var idx = _getInsertionIndex(result, element, _compareWith(criteria), 0, result.length);\n\n result.splice(idx, 0, element);\n\n return result;\n }\n\n /**\n * Creates an ascending sort criterion with the provided reader and\n * comparer.
\n * See {@link module:lamb.sort|sort} for various examples.\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorterDesc|sorterDesc}\n * @since 0.1.0\n * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a\n * simple value from a complex one. The function should evaluate the array element and supply the\n * value to be passed to the comparer.\n * @param {Function} [comparer] An optional custom comparer function.\n * @returns {Sorter}\n */\n var sorter = partial(_sorter, [__, false, __]);\n\n /**\n * Creates a descending sort criterion with the provided reader and\n * comparer.
\n * See {@link module:lamb.sort|sort} for various examples.\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.sortedInsert|sortedInsert}\n * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith}\n * @see {@link module:lamb.sorter|sorter}\n * @since 0.15.0\n * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a\n * simple value from a complex one. The function should evaluate the array element and supply the\n * value to be passed to the comparer.\n * @param {Function} [comparer] An optional custom comparer function.\n * @returns {Sorter}\n */\n var sorterDesc = partial(_sorter, [__, true, __]);\n\n /**\n * Builds a partial application of {@link module:lamb.sort|sort} using the provided criteria.\n * The returned function expects the array-like object to sort.\n * As usual, sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function,\n * but you can also pass simple \"reader\" functions and default ascending sorters will be built.
\n * A \"reader\" is a function that evaluates the array element and supplies the value to be used in\n * the comparison.
\n * See {@link module:lamb.sort|sort} for more examples.\n *\n * @example\n * var sortAsNumbers = _.sortWith([parseFloat]);\n * var weights = [\"2 Kg\", \"10 Kg\", \"1 Kg\", \"7 Kg\"];\n *\n * sortAsNumbers(weights) // => [\"1 Kg\", \"2 Kg\", \"7 Kg\", \"10 Kg\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.sort|sort}\n * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc}\n * @since 0.15.0\n * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]]\n * @returns {Function}\n */\n var sortWith = _curry2(sort, true);\n\n /**\n * Returns a copy of the given array-like object without the first element.\n * @example\n * _.tail([1, 2, 3, 4]) // => [2, 3, 4]\n * _.tail([1]) // => []\n * _.tail([]) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.init|init}\n * @see {@link module:lamb.head|head}, {@link module:lamb.last|last}\n * @since 0.16.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n var tail = drop(1);\n\n /**\n * Retrieves the first n elements from an array or array-like object.
\n * Note that, being this a shortcut for a common use case of {@link module:lamb.slice|slice},\n * n can be a negative number.\n * @example\n * var arr = [1, 2, 3, 4, 5];\n *\n * _.takeFrom(arr, 3) // => [1, 2, 3]\n * _.takeFrom(arr, -1) // => [1, 2, 3, 4]\n * _.takeFrom(arr, -10) // => []\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.take|take}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @since 0.51.0\n * @param {ArrayLike} arrayLike\n * @param {Number} n\n * @returns {Array}\n */\n function takeFrom (arrayLike, n) {\n return slice(arrayLike, 0, n);\n }\n\n /**\n * A curried version of {@link module:lamb.takeFrom|takeFrom} that expects the number of elements\n * to retrieve to build a function waiting for the list to take the elements from.
\n * See the note and examples for {@link module:lamb.takeFrom|takeFrom} about passing a\n * negative n.\n * @example\n * var take2 = _.take(2);\n *\n * take2([1, 2, 3, 4, 5]) // => [1, 2]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.takeFrom|takeFrom}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile}\n * @since 0.5.0\n * @param {Number} n\n * @returns {Function}\n */\n var take = _curry2(takeFrom, true);\n\n /**\n * Builds a function that takes the first n elements satisfying a predicate from\n * an array or array-like object.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var takeWhileIsEven = _.takeWhile(isEven);\n *\n * takeWhileIsEven([1, 2, 4, 6, 8]) // => []\n * takeWhileIsEven([2, 4, 7, 8]) // => [2, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.dropWhile|dropWhile}\n * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take}\n * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop}\n * @since 0.5.0\n * @param {ListIteratorCallback} predicate\n * @returns {Function}\n */\n function takeWhile (predicate) {\n return function (arrayLike) {\n return slice(arrayLike, 0, _getNumConsecutiveHits(arrayLike, predicate));\n };\n }\n\n /**\n * Transposes a matrix. Can also be used to reverse a {@link module:lamb.zip|zip} operation.
\n * Just like {@link module:lamb.zip|zip}, the received array-like objects will be truncated to the\n * shortest length.\n * @example Transposing a matrix:\n * _.transpose([\n * [1, 2, 3],\n * [4, 5, 6],\n * [7, 8, 9]\n * ]) // =>\n * // [\n * // [1, 4, 7],\n * // [2, 5, 8],\n * // [3, 6, 9]\n * // ]\n *\n * @example Showing the relationship with zip:\n * var zipped = _.zip([\"a\", \"b\", \"c\"], [1, 2, 3]); // => [[\"a\", 1], [\"b\", 2], [\"c\", 3]]\n *\n * _.transpose(zipped) // => [[\"a\", \"b\", \"c\"], [1, 2, 3]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.zip|zip}\n * @since 0.14.0\n * @param {ArrayLike} arrayLike\n * @returns {Array}\n */\n function transpose (arrayLike) {\n var minLen = MAX_ARRAY_LENGTH;\n var len = _toArrayLength(arrayLike.length);\n\n if (len === 0) {\n return [];\n }\n\n for (var j = 0, elementLen; j < len; j++) {\n elementLen = _toArrayLength(arrayLike[j].length);\n\n if (elementLen < minLen) {\n minLen = elementLen;\n }\n }\n\n var result = Array(minLen);\n\n for (var i = 0, el; i < minLen; i++) {\n el = result[i] = Array(len);\n\n for (j = 0; j < len; j++) {\n el[j] = arrayLike[j][i];\n }\n }\n\n return result;\n }\n\n /**\n * Builds a TypeError stating that it's not possible to convert the given value to the\n * desired type.\n * @private\n * @param {*} value\n * @param {String} desiredType\n * @returns {TypeError}\n */\n function _makeTypeErrorFor (value, desiredType) {\n return new TypeError(\"Cannot convert \" + type(value).toLowerCase() + \" to \" + desiredType);\n }\n\n /**\n * Creates a pipeline of functions, where each function consumes the result of the previous one.\n * @example\n * var __ = _.__;\n * var square = _.partial(Math.pow, [__, 2]);\n * var getMaxAndSquare = _.pipe([Math.max, square]);\n *\n * getMaxAndSquare(3, 5) // => 25\n *\n * @memberof module:lamb\n * @category Function\n * @function\n * @see {@link module:lamb.compose|compose}\n * @since 0.1.0\n * @param {Function[]} functions\n * @returns {Function}\n */\n function pipe (functions) {\n if (!Array.isArray(functions)) {\n throw _makeTypeErrorFor(functions, \"array\");\n }\n\n var len = functions.length;\n\n return len ? function () {\n var result = functions[0].apply(this, arguments);\n\n for (var i = 1; i < len; i++) {\n result = functions[i].call(this, result);\n }\n\n return result;\n } : identity;\n }\n\n /**\n * Using the provided iteratee to transform values, builds a function that will\n * return an array of the unique elements in the two provided array-like objects.
\n * Uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}\n * to test the equality of values.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.union|union} if you don't need to compare transformed values.\n * @example\n * var unionByFloor = _.unionBy(Math.floor);\n *\n * unionByFloor([2.8, 3.2, 1.5], [3.5, 1.2, 4]) // => [2.8, 3.2, 1.5, 4]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.union|union}\n * @see {@link module:lamb.difference|difference}\n * @see {@link module:lamb.intersection|intersection}\n * @since 0.51.0\n * @param {ListIteratorCallback} iteratee\n * @returns {Function}\n */\n function unionBy (iteratee) {\n return pipe([binary(list), flatMapWith(drop(0)), uniquesBy(iteratee)]);\n }\n\n /**\n * Returns a list of every unique element present in the two given array-like objects.
\n * Uses the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}\n * to test the equality of values.
\n * When two values are considered equal, the first occurence will be the one included\n * in the result array.
\n * See also {@link module:lamb.unionBy|unionBy} if you need to transform the values before\n * the comparison or if you have to extract them from complex ones.\n * @example\n * _.union([1, 2, 3, 2], [2, 3, 4]) // => [1, 2, 3, 4]\n * _.union(\"abc\", \"bcd\") // => [\"a\", \"b\", \"c\", \"d\"]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.unionBy|unionBy}\n * @see {@link module:lamb.difference|difference}\n * @see {@link module:lamb.intersection|intersection}\n * @since 0.5.0\n * @param {ArrayLike} a\n * @param {ArrayLike} b\n * @returns {Array}\n */\n var union = unionBy(identity);\n\n /**\n * Builds a function that creates a copy of an array-like object with the given index\n * changed by applying the provided function to its value.
\n * If the index is not an integer or if it's out of bounds, the function will return\n * a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [\"a\", \"b\", \"c\"];\n * var toUpperCase = _.invoker(\"toUpperCase\");\n *\n * _.updateAt(1, toUpperCase)(arr) // => [\"a\", \"B\", \"c\"]\n * _.updateAt(-1, toUpperCase)(arr) // => [\"a\", \"b\", \"C\"]\n * _.updateAt(10, toUpperCase)(arr) // => [\"a\", \"b\", \"c\"] (not a reference to `arr`)\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.updateIndex|updateIndex}\n * @since 0.22.0\n * @param {Number} index\n * @param {Function} updater\n * @returns {Function}\n */\n function updateAt (index, updater) {\n return function (arrayLike) {\n return _setIndex(arrayLike, index, null, updater);\n };\n }\n\n /**\n * Creates a copy of an array-like object with the given index changed by applying the\n * provided function to its value.
\n * If the index is not an integer or if it's out of bounds, the function will return\n * a copy of the original array.
\n * Negative indexes are allowed.\n * @example\n * var arr = [\"a\", \"b\", \"c\"];\n * var toUpperCase = _.invoker(\"toUpperCase\");\n *\n * _.updateIndex(arr, 1, toUpperCase) // => [\"a\", \"B\", \"c\"]\n * _.updateIndex(arr, -1, toUpperCase) // => [\"a\", \"b\", \"C\"]\n * _.updateIndex(arr, 10, toUpperCase) // => [\"a\", \"b\", \"c\"] (not a reference to `arr`)\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.updateAt|updateAt}\n * @since 0.23.0\n * @param {ArrayLike} arrayLike\n * @param {Number} index\n * @param {Function} updater\n * @returns {Array}\n */\n var updateIndex = partial(_setIndex, [__, __, null, __]);\n\n /**\n * Builds a list of arrays out of the two given array-like objects by pairing items with\n * the same index.
\n * The received array-like objects will be truncated to the shortest length.\n * @example\n * _.zip(\n * [\"a\", \"b\", \"c\"],\n * [1, 2, 3]\n * ) // => [[\"a\", 1], [\"b\", 2], [\"c\", 3]]\n *\n * _.zip([1, 2, 3, 4], [5, 6, 7]) // => [[1, 5], [2, 6], [3, 7]]\n *\n * @memberof module:lamb\n * @category Array\n * @see {@link module:lamb.transpose|transpose} for the reverse operation\n * @see {@link module:lamb.zipWithIndex|zipWithIndex}\n * @since 0.14.0\n * @param {ArrayLike} a\n * @param {ArrayLike} b\n * @returns {Array}\n */\n function zip (a, b) {\n return transpose([a, b]);\n }\n\n /**\n * \"{@link module:lamb.zip|Zips}\" an array-like object by pairing its values with their index.\n * @example\n * _.zipWithIndex([\"a\", \"b\", \"c\"]) // => [[\"a\", 0], [\"b\", 1], [\"c\", 2]]\n *\n * @memberof module:lamb\n * @category Array\n * @function\n * @see {@link module:lamb.zip|zip}\n * @since 0.14.0\n * @param {ArrayLike} arrayLike\n * @returns {Array>}\n */\n var zipWithIndex = mapWith(binary(list));\n\n /**\n * Applies the given function to a list of arguments.\n * @example\n * _.application(_.sum, [3, 4]) // => 7\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.apply|apply}, {@link module:lamb.applyTo|applyTo}\n * @since 0.47.0\n * @param {Function} fn\n * @param {ArrayLike} args\n * @returns {*}\n */\n function application (fn, args) {\n return fn.apply(this, Object(args));\n }\n\n /**\n * A left-curried version of {@link module:lamb.application|application}. Expects the function\n * to apply and builds a function waiting for the arguments array.\n * @example\n * var arrayMax = _.apply(Math.max);\n *\n * arrayMax([4, 5, 2, 6, 1]) // => 6\n *\n * @memberof module:lamb\n * @category Function\n * @function\n * @see {@link module:lamb.application|application}, {@link module:lamb.applyTo|applyTo}\n * @since 0.1.0\n * @param {Function} fn\n * @returns {Function}\n */\n var apply = _curry2(application);\n\n /**\n * A right-curried version of {@link module:lamb.application|application}. Expects an array-like\n * object to use as arguments and builds a function waiting for the target of the application.\n * @example\n * var data = [3, 4];\n * var applyToData = _.applyTo(data);\n *\n * applyToData(_.sum) // => 7\n * applyToData(_.multiply) // => 12\n *\n * @memberof module:lamb\n * @category Function\n * @function\n * @see {@link module:lamb.application|application}, {@link module:lamb.apply|apply}\n * @since 0.47.0\n * @param {ArrayLike} args\n * @returns {Function}\n */\n var applyTo = _curry2(application, true);\n\n /**\n * Keeps building a partial application of the received function as long\n * as it's called with placeholders; applies the original function to\n * the collected parameters otherwise.
\n * The function checks only the public placeholder to gain a little performance\n * as no function in Lamb is built with {@link module:lamb.asPartial|asPartial}.\n * @private\n * @param {Function} fn\n * @param {Array} argsHolder\n * @returns {Function|*}\n */\n function _asPartial (fn, argsHolder) {\n return function () {\n var argsLen = arguments.length;\n var lastIdx = 0;\n var newArgs = [];\n\n for (var i = 0, len = argsHolder.length, boundArg; i < len; i++) {\n boundArg = argsHolder[i];\n newArgs[i] = boundArg === __ && lastIdx < argsLen ? arguments[lastIdx++] : boundArg;\n }\n\n while (lastIdx < argsLen) {\n newArgs[i++] = arguments[lastIdx++];\n }\n\n for (i = 0; i < argsLen; i++) {\n if (arguments[i] === __) {\n return _asPartial(fn, newArgs);\n }\n }\n\n for (i = 0, len = newArgs.length; i < len; i++) {\n if (newArgs[i] === __) {\n newArgs[i] = void 0;\n }\n }\n\n return fn.apply(this, newArgs);\n };\n }\n\n /**\n * Decorates the received function so that it can be called with\n * placeholders to build a partial application of it.
\n * The difference with {@link module:lamb.partial|partial} is that, as long as\n * you call the generated function with placeholders, another partial application\n * of the original function will be built.
\n * The final application will happen when one of the generated functions is\n * invoked without placeholders, using the parameters collected so far.
\n * This function comes in handy when you need to build different specialized\n * functions starting from a basic one, but it's also useful when dealing with\n * optional parameters as you can decide to apply the function even if its arity\n * hasn't been entirely consumed.\n * @example Explaining the function's behaviour:\n * var __ = _.__;\n * var f = _.asPartial(function (a, b, c) {\n * return a + b + c;\n * });\n *\n * f(4, 3, 2) // => 9\n * f(4, __, 2)(3) // => 9\n * f(__, 3, __)(4, __)(2) // => 9\n *\n * @example Exploiting optional parameters:\n * var __ = _.__;\n * var f = _.asPartial(function (a, b, c) {\n * return a + b + (c || 0);\n * });\n *\n * var addFive = f(5, __);\n * addFive(2) // => 7\n *\n * var addNine = addFive(4, __);\n * addNine(11) // => 20\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.__|__} The placeholder object.\n * @since 0.36.0\n * @param {Function} fn\n * @returns {Function}\n */\n function asPartial (fn) {\n return _asPartial(fn, []);\n }\n\n /**\n * Accepts a series of functions and builds a new function. The functions in the series\n * will then be applied, in order, with the values received by the function built with\n * collect.
\n * The collected results will be returned in an array.\n * @example\n * var user = {\n * id: \"jdoe\",\n * name: \"John\",\n * surname: \"Doe\",\n * scores: [2, 4, 7]\n * };\n * var getIDAndLastScore = _.collect([_.getKey(\"id\"), _.getPath(\"scores.-1\")]);\n *\n * getIDAndLastScore(user) // => [\"jdoe\", 7]\n *\n * @example\n * var minAndMax = _.collect([Math.min, Math.max]);\n *\n * minAndMax(3, 1, -2, 5, 4, -1) // => [-2, 5]\n *\n * @memberof module:lamb\n * @category Function\n * @since 0.35.0\n * @param {Function[]} functions\n * @returns {Function}\n */\n function collect (functions) {\n if (!Array.isArray(functions)) {\n throw _makeTypeErrorFor(functions, \"array\");\n }\n\n return function () {\n return map(functions, applyTo(arguments));\n };\n }\n\n /**\n * Used by curry functions to collect arguments until the arity is consumed,\n * then applies the original function.\n * @private\n * @param {Function} fn\n * @param {Number} arity\n * @param {Boolean} isRightCurry\n * @param {Boolean} isAutoCurry\n * @param {Array} argsHolder\n * @returns {Function}\n */\n function _currier (fn, arity, isRightCurry, isAutoCurry, argsHolder) {\n return function () {\n var holderLen = argsHolder.length;\n var argsLen = arguments.length;\n var newArgsLen = holderLen + (argsLen > 1 && isAutoCurry ? argsLen : 1);\n var newArgs = Array(newArgsLen);\n\n for (var i = 0; i < holderLen; i++) {\n newArgs[i] = argsHolder[i];\n }\n\n for (; i < newArgsLen; i++) {\n newArgs[i] = arguments[i - holderLen];\n }\n\n if (newArgsLen >= arity) {\n return fn.apply(this, isRightCurry ? newArgs.reverse() : newArgs);\n } else {\n return _currier(fn, arity, isRightCurry, isAutoCurry, newArgs);\n }\n };\n }\n\n /**\n * Curries a function of arity 3.\n * @private\n * @param {Function} fn\n * @param {Boolean} [isRightCurry=false]\n * @returns {Function}\n */\n function _curry3 (fn, isRightCurry) {\n return function (a) {\n return function (b) {\n return function (c) {\n return isRightCurry ? fn.call(this, c, b, a) : fn.call(this, a, b, c);\n };\n };\n };\n }\n\n /**\n * Prepares a function for currying. If it's not auto-currying and the arity\n * is 2 or 3 returns optimized functions, otherwise delegates the currying\n * to the _currier function.
\n * If the desumed arity isn't greater than one, it will return the received\n * function itself, instead.\n * @private\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @param {Boolean} [isRightCurry=false]\n * @param {Boolean} [isAutoCurry=false]\n * @returns {Function}\n */\n function _curry (fn, arity, isRightCurry, isAutoCurry) {\n if (arity >>> 0 !== arity) {\n arity = fn.length;\n }\n\n if (isAutoCurry && arity > 1 || arity > 3) {\n return _currier(fn, arity, isRightCurry, isAutoCurry, []);\n } else if (arity === 2) {\n return _curry2(fn, isRightCurry);\n } else if (arity === 3) {\n return _curry3(fn, isRightCurry);\n } else {\n return fn;\n }\n }\n\n /**\n * Transforms the evaluation of the given function in the evaluation of a sequence of functions\n * expecting only one argument. Each function of the sequence is a partial application of the\n * original one, which will be applied when the specified (or derived) arity is consumed.
\n * Currying will start from the leftmost argument: use {@link module:lamb.curryRight|curryRight}\n * for right currying.\n * @example\n * var makeWithKeys = _.curry(_.make);\n * var makePerson = makeWithKeys([\"name\", \"surname\"]);\n *\n * makePerson([\"John\", \"Doe\"]) // => {name: \"John\", surname: \"Doe\"};\n * makePerson([\"Mario\", \"Rossi\"]) // => {name: \"Mario\", surname: \"Rossi\"};\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curry (fn, arity) {\n return _curry(fn, arity, false);\n }\n\n /**\n * Builds an auto-curried function. The resulting function can be called multiple times with\n * any number of arguments, and the original function will be applied only when the specified\n * (or derived) arity is consumed.
\n * Currying will start from the leftmost argument: use {@link module:lamb.curryableRight|curryableRight}\n * for right currying.\n * @example\n * var collectFourElements = _.curryable(_.list, 4);\n *\n * collectFourElements(2)(3)(4)(5) // => [2, 3, 4, 5]\n * collectFourElements(2)(3, 4)(5) // => [2, 3, 4, 5]\n * collectFourElements(2, 3, 4, 5) // => [2, 3, 4, 5]\n * collectFourElements(2, 3)(4, 5) // => [2, 3, 4, 5]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.6.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curryable (fn, arity) {\n return _curry(fn, arity, false, true);\n }\n\n /**\n * Same as {@link module:lamb.curryable|curryable}, but currying starts from the rightmost argument.\n * @example\n * var collectFourElements = _.curryableRight(_.list, 4);\n *\n * collectFourElements(2)(3)(4)(5) // => [5, 4, 3, 2]\n * collectFourElements(2)(3, 4)(5) // => [5, 4, 3, 2]\n * collectFourElements(2, 3, 4, 5) // => [5, 4, 3, 2]\n * collectFourElements(2, 3)(4, 5) // => [5, 4, 3, 2]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curryable|curryable}\n * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.9.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curryableRight (fn, arity) {\n return _curry(fn, arity, true, true);\n }\n\n /**\n * Same as {@link module:lamb.curry|curry}, but currying starts from the rightmost argument.\n * @example\n * var makeWithValues = _.curryRight(_.make);\n * var makeJohnDoe = makeWithValues([\"John\", \"Doe\"]);\n *\n * makeJohnDoe([\"name\", \"surname\"]) // => {name: \"John\", surname: \"Doe\"};\n * makeJohnDoe([\"firstName\", \"lastName\"]) // => {firstName: \"John\", lastName: \"Doe\"};\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.curry|curry}\n * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight}\n * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight}\n * @see {@link module:lamb.asPartial|asPartial}\n * @since 0.9.0\n * @param {Function} fn\n * @param {Number} [arity=fn.length]\n * @returns {Function}\n */\n function curryRight (fn, arity) {\n return _curry(fn, arity, true);\n }\n\n /**\n * Returns a function that will execute the given function only if it stops being called for the\n * specified timespan.
\n * See also {@link module:lamb.throttle|throttle} for a different behaviour where the first call\n * happens immediately.\n * @example A common use case of debounce in a browser environment:\n * var updateLayout = function () {\n * // some heavy DOM operations here\n * };\n *\n * window.addEventListener(\"resize\", _.debounce(updateLayout, 200), false);\n *\n * // The resize event is fired repeteadly until the user stops resizing the\n * // window, while the `updateLayout` function is called only once: 200 ms\n * // after he stopped.\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.throttle|throttle}\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} timespan - Expressed in milliseconds\n * @returns {Function}\n */\n function debounce (fn, timespan) {\n var timeoutID;\n\n return function () {\n var args = arguments;\n var debounced = function () {\n timeoutID = null;\n fn.apply(this, args);\n }.bind(this);\n\n clearTimeout(timeoutID);\n timeoutID = setTimeout(debounced, timespan);\n };\n }\n\n /**\n * Returns a function that applies the original function with the arguments in reverse order.\n * @example\n * _.list(1, 2, 3) // => [1, 2, 3]\n * _.flip(_.list)(1, 2, 3) // => [3, 2, 1]\n *\n * @memberof module:lamb\n * @category Function\n * @since 0.1.0\n * @param {Function} fn\n * @returns {Function}\n */\n function flip (fn) {\n return function () {\n var args = list.apply(null, arguments).reverse();\n\n return fn.apply(this, args);\n };\n }\n\n /**\n * Builds a function that returns the argument received at the given index.
\n * As with {@link module:lamb.getAt|getAt} negative indexes are allowed.
\n * The resulting function will return undefined if no arguments are\n * passed or if the index is out of bounds.\n * @example\n * var getFirstArg = _.getArgAt(0);\n * var getLastArg = _.getArgAt(-1);\n *\n * getFirstArg(1, 2, 3) // => 1\n * getLastArg(1, 2, 3) // => 3\n *\n * _.getArgAt()(1, 2, 3) // => undefined\n * _.getArgAt(6)(1, 2, 3) // => undefined\n * _.getArgAt(1)() // => undefined\n *\n * @memberof module:lamb\n * @category Function\n * @since 0.17.0\n * @param {Number} idx\n * @returns {Function}\n */\n function getArgAt (idx) {\n return function () {\n return arguments[_toNaturalIndex(idx, arguments.length)];\n };\n }\n\n /**\n * Builds an array with the received arguments excluding the first one.
\n * To be used with the arguments object, which needs to be passed to the apply\n * method of this function.\n * @private\n * @function\n * @param {...*} value\n * @returns {Array}\n */\n var _argsTail = _argsToArrayFrom(1);\n\n /**\n * If a method with the given name exists on the target, applies it to the provided\n * arguments and returns the result. Returns undefined otherwise.
\n * The arguments for the method are built by concatenating the array of bound arguments,\n * optionally received by {@link module:lamb.invoker|invoker}, with the final set of, also\n * optional, args.\n * @private\n * @param {Array} boundArgs\n * @param {String} methodName\n * @param {Object} target\n * @param {...*} [args]\n * @returns {*}\n */\n function _invoker (boundArgs, methodName, target) {\n var method = target[methodName];\n\n if (typeof method !== \"function\") {\n return void 0;\n }\n\n var boundArgsLen = boundArgs.length;\n var ofs = 3 - boundArgsLen;\n var len = arguments.length - ofs;\n var args = Array(len);\n\n for (var i = 0; i < boundArgsLen; i++) {\n args[i] = boundArgs[i];\n }\n\n for (; i < len; i++) {\n args[i] = arguments[i + ofs];\n }\n\n return method.apply(target, args);\n }\n\n /**\n * Builds a function that will invoke the given method name on any received object and\n * return the result. If no method with such name is found the function will return\n * undefined.
\n * Along with the method name it's possible to supply some arguments that will be bound to the\n * method call. Further arguments can also be passed when the function is actually called, and\n * they will be concatenated to the bound ones.
\n * Returning undefined is a behaviour meant to quickly create a case for\n * {@link module:lamb.adapter|adapter} without the need to check for the existence of the\n * desired method.
\n * See also {@link module:lamb.generic|generic} to create functions out of object methods.\n * @example Basic polymorphism with invoker:\n * var polySlice = _.invoker(\"slice\");\n *\n * polySlice([1, 2, 3, 4, 5], 1, 3) // => [2, 3]\n * polySlice(\"Hello world\", 1, 3) // => \"el\"\n *\n * @example With bound arguments:\n * var substrFrom2 = _.invoker(\"substr\", 2);\n * substrFrom2(\"Hello world\") // => \"llo world\"\n * substrFrom2(\"Hello world\", 5) // => \"llo w\"\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.invokerOn|invokerOn}\n * @since 0.1.0\n * @param {String} methodName\n * @param {...*} [boundArg]\n * @returns {Function}\n */\n function invoker (methodName) {\n return partial(_invoker, [_argsTail.apply(null, arguments), methodName]);\n }\n\n /**\n * Accepts an object and builds a function expecting a method name, and optionally arguments,\n * to call on such object.\n * Like {@link module:lamb.invoker|invoker}, if no method with the given name is found the\n * function will return undefined.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var arr = [1, 2, 3, 4, 5];\n * var invokerOnArr = _.invokerOn(arr);\n *\n * invokerOnArr(\"filter\", isEven) // => [2, 4]\n * invokerOnArr(\"slice\", 1, 3) // => [2, 3]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.invoker|invoker}\n * @since 0.15.0\n * @param {Object} target\n * @returns {Function}\n */\n function invokerOn (target) {\n return partial(_invoker, [[], __, target]);\n }\n\n /**\n * Builds a function that allows to map over the received arguments before applying them\n * to the original one.\n * @example\n * var __ = _.__;\n * var sumArray = _.reduceWith(_.sum);\n * var sumArgs = _.compose(sumArray, _.list);\n *\n * sumArgs(1, 2, 3, 4, 5) // => 15\n *\n * var square = _.partial(Math.pow, [__, 2]);\n * var sumSquares = _.mapArgs(sumArgs, square);\n *\n * sumSquares(1, 2, 3, 4, 5) // => 55\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.tapArgs|tapArgs}\n * @since 0.3.0\n * @param {Function} fn\n * @param {ListIteratorCallback} mapper\n * @returns {Function}\n */\n function mapArgs (fn, mapper) {\n return pipe([list, mapWith(mapper), apply(fn)]);\n }\n\n /**\n * Builds a function that allows to \"tap\" into the arguments of the original one.\n * This allows to extract simple values from complex ones, transform arguments or simply intercept them.\n * If a \"tapper\" isn't found the argument is passed as it is.\n * @example\n * var someObject = {count: 5};\n * var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0];\n * var getDataAmount = _.tapArgs(_.sum, [_.getKey(\"count\"), _.getKey(\"length\")]);\n *\n * getDataAmount(someObject, someArrayData); // => 15\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.mapArgs|mapArgs}\n * @since 0.3.0\n * @param {Function} fn\n * @param {Function[]} tappers\n * @returns {Function}\n */\n function tapArgs (fn, tappers) {\n return function () {\n var len = arguments.length;\n var tappersLen = tappers.length;\n var args = [];\n\n for (var i = 0; i < len; i++) {\n args.push(i < tappersLen ? tappers[i](arguments[i]) : arguments[i]);\n }\n\n return fn.apply(this, args);\n };\n }\n\n /**\n * Returns a function that will invoke the passed function at most once in the given timespan.
\n * The first call in this case happens as soon as the function is invoked; see also\n * {@link module:lamb.debounce|debounce} for a different behaviour where the first call is delayed.\n * @example\n * var log = _.throttle(console.log.bind(console), 5000);\n *\n * log(\"Hi\"); // console logs \"Hi\"\n * log(\"Hi again\"); // nothing happens\n * // after five seconds\n * log(\"Hello world\"); // console logs \"Hello world\"\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.debounce|debounce}\n * @since 0.1.0\n * @param {Function} fn\n * @param {Number} timespan - Expressed in milliseconds.\n * @returns {Function}\n */\n function throttle (fn, timespan) {\n var result;\n var lastCall = 0;\n\n return function () {\n var now = Date.now();\n\n if (now - lastCall >= timespan) {\n lastCall = now;\n result = fn.apply(this, arguments);\n }\n\n return result;\n };\n }\n\n /**\n * Builds a function that passes only one argument to the given function.
\n * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize},\n * exposed for convenience.\n * @example\n * var weights = [\"2 Kg\", \"10 Kg\", \"1 Kg\", \"7 Kg\"];\n *\n * _.map(weights, _.unary(parseInt)) // => [2, 10, 1, 7]\n *\n * @memberof module:lamb\n * @category Function\n * @see {@link module:lamb.aritize|aritize}\n * @see {@link module:lamb.binary|binary}\n * @since 0.10.0\n * @param {Function} fn\n * @returns {Function}\n */\n function unary (fn) {\n return function (a) {\n return fn.call(this, a);\n };\n }\n\n /**\n * Accepts a series of functions and builds a function that applies the received\n * arguments to each one and returns the first non-undefined value.
\n * Meant to work in synergy with {@link module:lamb.case|case} and\n * {@link module:lamb.invoker|invoker}, can be useful as a strategy pattern for functions,\n * to mimic conditional logic or pattern matching, and also to build polymorphic functions.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var filterString = _.compose(_.invoker(\"join\", \"\"), _.filter);\n * var filterAdapter = _.adapter([\n * _.invoker(\"filter\"),\n * _.case(_.isType(\"String\"), filterString)\n * ]);\n *\n * filterAdapter([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6]\n * filterAdapter(\"123456\", isEven) // => \"246\"\n * filterAdapter({}, isEven) // => undefined\n *\n * // by its nature is composable\n * var filterWithDefault = _.adapter([filterAdapter, _.always(\"Not implemented\")]);\n *\n * filterWithDefault([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6]\n * filterWithDefault(\"123456\", isEven) // => \"246\"\n * filterWithDefault({}, isEven) // => \"Not implemented\"\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.case|case}\n * @see {@link module:lamb.invoker|invoker}\n * @since 0.6.0\n * @param {Function[]} functions\n * @returns {Function}\n */\n function adapter (functions) {\n if (!Array.isArray(functions)) {\n throw _makeTypeErrorFor(functions, \"array\");\n }\n\n return function () {\n var len = functions.length;\n var result;\n\n for (var i = 0; i < len; i++) {\n result = functions[i].apply(this, arguments);\n\n if (!isUndefined(result)) {\n break;\n }\n }\n\n return result;\n };\n }\n\n /**\n * Creates a function to check the given predicates.
\n * Used to build the {@link module:lamb.allOf|allOf} and the\n * {@link module:lamb.anyOf|anyOf} functions.\n * @private\n * @param {Boolean} checkAll\n * @returns {Function}\n */\n function _checkPredicates (checkAll) {\n return function (predicates) {\n if (!Array.isArray(predicates)) {\n throw _makeTypeErrorFor(predicates, \"array\");\n }\n\n return function () {\n for (var i = 0, len = predicates.length, result; i < len; i++) {\n result = predicates[i].apply(this, arguments);\n\n if (checkAll && !result) {\n return false;\n } else if (!checkAll && result) {\n return true;\n }\n }\n\n return checkAll;\n };\n };\n }\n\n /**\n * Accepts an array of predicates and builds a new one that returns true if they are all satisfied\n * by the same arguments. The functions in the array will be applied one at a time until a\n * false value is produced, which is returned immediately.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var isPositiveEven = _.allOf([isEven, _.isGT(0)]);\n *\n * isPositiveEven(-2) // => false\n * isPositiveEven(11) // => false\n * isPositiveEven(6) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.anyOf|anyOf}\n * @since 0.1.0\n * @param {Function[]} predicates\n * @returns {Function}\n */\n var allOf = _checkPredicates(true);\n\n /**\n * Accepts an array of predicates and builds a new one that returns true if at least one of them is\n * satisfied by the received arguments. The functions in the array will be applied one at a time\n * until a true value is produced, which is returned immediately.\n * @example\n * var users = [\n * {id: 1, name: \"John\", group: \"guest\"},\n * {id: 2, name: \"Jane\", group: \"root\"},\n * {id: 3, name: \"Mario\", group: \"admin\"}\n * ];\n * var isInGroup = _.partial(_.hasKeyValue, [\"group\"]);\n * var isSuperUser = _.anyOf([isInGroup(\"admin\"), isInGroup(\"root\")]);\n *\n * isSuperUser(users[0]) // => false\n * isSuperUser(users[1]) // => true\n * isSuperUser(users[2]) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.allOf|allOf}\n * @since 0.1.0\n * @param {Function[]} predicates\n * @returns {Function}\n */\n var anyOf = _checkPredicates(false);\n\n /**\n * Verifies that the two supplied values are the same value using the \"SameValue\" comparison.
\n * Note that this doesn't behave as the strict equality operator, but rather as a shim of ES6's\n * [Object.is]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is}.\n * Differences are that 0 and -0 aren't the same value and, finally,\n * NaN is equal to itself.
\n * See also {@link module:lamb.is|is} for a curried version building a predicate and\n * {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} to perform a \"SameValueZero\"\n * comparison.\n * @example\n * var testObject = {};\n *\n * _.areSame({}, testObject) // => false\n * _.areSame(testObject, testObject) // => true\n * _.areSame(\"foo\", \"foo\") // => true\n * _.areSame(0, -0) // => false\n * _.areSame(0 / 0, NaN) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.is|is}\n * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.50.0\n * @param {*} a\n * @param {*} b\n * @returns {Boolean}\n */\n function areSame (a, b) {\n return a === 0 && b === 0 ? 1 / a === 1 / b : areSVZ(a, b);\n }\n\n /**\n * Builds a case for {@link module:lamb.adapter|adapter}.
\n * The function will apply the received arguments to fn if the predicate is satisfied\n * with the same arguments, otherwise will return undefined.
\n * See also {@link module:lamb.condition|condition} to build a condition with two branching functions\n * and {@link module:lamb.unless|unless} and {@link module:lamb.when|when} where one of the branches\n * is the identity function.\n * @example\n * var halveIfNumber = _.case(_.isType(\"Number\"), _.divideBy(2));\n *\n * halveIfNumber(2) // => 1\n * halveIfNumber(\"2\") // => undefined\n *\n * @alias module:lamb.case\n * @category Logic\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.condition|condition}\n * @see {@link module:lamb.unless|unless}\n * @see {@link module:lamb.when|when}\n * @since 0.51.0\n * @param {Function} predicate\n * @param {Function} fn\n * @returns {Function}\n */\n function case_ (predicate, fn) {\n return function () {\n return predicate.apply(this, arguments) ? fn.apply(this, arguments) : void 0;\n };\n }\n\n /**\n * Builds a function that will apply the received arguments to trueFn,\n * if the predicate is satisfied with the same arguments, or to falseFn otherwise.
\n * Although you can use other conditions as trueFn or falseFn,\n * it's probably better to use {@link module:lamb.adapter|adapter} to build more complex behaviours.
\n * See also {@link module:lamb.unless|unless} and {@link module:lamb.when|when} as they are\n * shortcuts to common use cases.\n * @example\n * var isEven = function (n) { return n % 2 === 0};\n * var halveEvenAndDoubleOdd = _.condition(isEven, _.divideBy(2), _.multiplyBy(2));\n *\n * halveEvenAndDoubleOdd(5) // => 10\n * halveEvenAndDoubleOdd(6) // => 3\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.unless|unless}\n * @see {@link module:lamb.when|when}\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.case|case}\n * @since 0.2.0\n * @param {Function} predicate\n * @param {Function} trueFn\n * @param {Function} falseFn\n * @returns {Function}\n */\n function condition (predicate, trueFn, falseFn) {\n return function () {\n return (predicate.apply(this, arguments) ? trueFn : falseFn).apply(this, arguments);\n };\n }\n\n /**\n * Verifies that the first given value is greater than the second.
\n * Wraps the native > operator within a function.\n * @example\n * var pastDate = new Date(2010, 2, 12);\n * var today = new Date();\n *\n * _.gt(today, pastDate) // => true\n * _.gt(pastDate, today) // => false\n * _.gt(3, 4) // => false\n * _.gt(3, 3) // => false\n * _.gt(3, 2) // => true\n * _.gt(0, -0) // => false\n * _.gt(-0, 0) // => false\n * _.gt(\"a\", \"A\") // => true\n * _.gt(\"b\", \"a\") // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.gte|gte}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function gt (a, b) {\n return a > b;\n }\n\n /**\n * Verifies that the first given value is greater than or equal to the second.\n * Regarding equality, beware that this is simply a wrapper for the native\n * >= operator, so -0 === 0.\n * @example\n * _.gte(3, 4) // => false\n * _.gte(3, 3) // => true\n * _.gte(3, 2) // => true\n * _.gte(0, -0) // => true\n * _.gte(-0, 0) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.gt|gt}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function gte (a, b) {\n return a >= b;\n }\n\n /**\n * A curried version of {@link module:lamb.areSame|areSame}.
\n * Accepts a value and builds a predicate that checks whether the value\n * and the one received by the predicate are the same using the \"SameValue\"\n * comparison.
\n * See also {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ}\n * to perform a \"SameValueZero\" comparison.\n * @example\n * var john = {name: \"John\", surname: \"Doe\"};\n * var isJohn = _.is(john);\n * var isNegativeZero = _.is(-0);\n * var isReallyNaN = _.is(NaN);\n *\n * isJohn(john) // => true\n * isJohn({name: \"John\", surname: \"Doe\"}) // => false\n *\n * isNegativeZero(0) // => false\n * isNegativeZero(-0) // => true\n *\n * isNaN(NaN) // => true\n * isNaN(\"foo\") // => true\n *\n * isReallyNaN(NaN) // => true\n * isReallyNaN(\"foo\") // => false\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.areSame|areSame}\n * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ}\n * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue}\n * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero}\n * @since 0.1.0\n * @param {*} value\n * @returns {Function}\n */\n var is = _curry2(areSame);\n\n /**\n * A right curried version of {@link module:lamb.gt|gt}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is greater than the one received by the predicate.\n * @example\n * var isGreaterThan5 = _.isGT(5);\n *\n * isGreaterThan5(3) // => false\n * isGreaterThan5(5) // => false\n * isGreaterThan5(7) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isGT = _curry2(gt, true);\n\n /**\n * A right curried version of {@link module:lamb.gte|gte}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is greater than or equal to the one received by the predicate.\n * @example\n * var isPositiveOrZero = _.isGTE(0);\n *\n * isPositiveOrZero(-3) // => false\n * isPositiveOrZero(-0) // => true\n * isPositiveOrZero(0) // => true\n * isPositiveOrZero(5) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isGT|isGT}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isGTE = _curry2(gte, true);\n\n /**\n * Verifies that the first given value is less than the second.
\n * Wraps the native < operator within a function.\n * @example\n * var pastDate = new Date(2010, 2, 12);\n * var today = new Date();\n *\n * _.lt(today, pastDate) // => false\n * _.lt(pastDate, today) // => true\n * _.lt(3, 4) // => true\n * _.lt(3, 3) // => false\n * _.lt(3, 2) // => false\n * _.lt(0, -0) // => false\n * _.lt(-0, 0) // => false\n * _.lt(\"a\", \"A\") // => false\n * _.lt(\"a\", \"b\") // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.lte|lte}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function lt (a, b) {\n return a < b;\n }\n\n /**\n * A right curried version of {@link module:lamb.lt|lt}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is less than the one received by the predicate.\n * @example\n * var isLessThan5 = _.isLT(5);\n *\n * isLessThan5(7) // => false\n * isLessThan5(5) // => false\n * isLessThan5(3) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isLT = _curry2(lt, true);\n\n /**\n * Verifies that the first given value is less than or equal to the second.\n * Regarding equality, beware that this is simply a wrapper for the native\n * <= operator, so -0 === 0.\n * @example\n * _.lte(3, 4) // => true\n * _.lte(3, 3) // => true\n * _.lte(3, 2) // => false\n * _.lte(0, -0) // => true\n * _.lte(-0, 0) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.lt|lt}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @since 0.50.0\n * @param {Number|String|Date|Boolean} a\n * @param {Number|String|Date|Boolean} b\n * @returns {Boolean}\n */\n function lte (a, b) {\n return a <= b;\n }\n\n /**\n * A right curried version of {@link module:lamb.lte|lte}.
\n * Accepts a value and builds a predicate that checks whether the value\n * is less than or equal to the one received by the predicate.\n * @example\n * var isNegativeOrZero = _.isLTE(0);\n *\n * isNegativeOrZero(5) // => false\n * isNegativeOrZero(-0) // => true\n * isNegativeOrZero(0) // => true\n * isNegativeOrZero(-3) // => true\n *\n * @memberof module:lamb\n * @category Logic\n * @function\n * @see {@link module:lamb.isLT|isLT}\n * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE}\n * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte}\n * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte}\n * @since 0.1.0\n * @param {Number|String|Date|Boolean} value\n * @returns {Function}\n */\n var isLTE = _curry2(lte, true);\n\n /**\n * Builds a unary function that will check its argument against the given predicate.\n * If the predicate isn't satisfied, the provided fn function will be\n * applied to the same value. The received argument is returned as it is otherwise.
\n * See {@link module:lamb.when|when} for the opposite behaviour.
\n * It's a shortcut for a common use case of {@link module:lamb.condition|condition},\n * where its trueFn parameter is the [identity function]{@link module:lamb.identity}.\n * @example\n * var isEven = function (n) { return n % 2 === 0};\n * var halveUnlessIsEven = _.unless(isEven, _.divideBy(2));\n *\n * halveUnlessIsEven(5) // => 2.5\n * halveUnlessIsEven(6) // => 6\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.condition|condition}\n * @see {@link module:lamb.when|when}\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.case|case}\n * @since 0.42.0\n * @param {Function} predicate\n * @param {Function} fn\n * @returns {Function}\n */\n function unless (predicate, fn) {\n return function (value) {\n return predicate.call(this, value) ? value : fn.call(this, value);\n };\n }\n\n /**\n * Builds a unary function that will check its argument against the given predicate.\n * If the predicate is satisfied, the provided fn function will be\n * applied to the same value. The received argument is returned as it is otherwise.
\n * See {@link module:lamb.unless|unless} for the opposite behaviour.
\n * It's a shortcut for a common use case of {@link module:lamb.condition|condition},\n * where its falseFn parameter is the [identity function]{@link module:lamb.identity}.\n * @example\n * var isEven = function (n) { return n % 2 === 0; };\n * var halveIfEven = _.when(isEven, _.divideBy(2));\n *\n * halveIfEven(5) // => 5\n * halveIfEven(6) // => 3\n *\n * @memberof module:lamb\n * @category Logic\n * @see {@link module:lamb.condition|condition}\n * @see {@link module:lamb.unless|unless}\n * @see {@link module:lamb.adapter|adapter}\n * @see {@link module:lamb.case|case}\n * @since 0.42.0\n * @param {Function} predicate\n * @param {Function} fn\n * @returns {Function}\n */\n function when (predicate, fn) {\n return function (value) {\n return predicate.call(this, value) ? fn.call(this, value) : value;\n };\n }\n\n /**\n * Sums two numbers.\n * @example\n * _.sum(4, 5) // => 9\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.add|add}\n * @since 0.50.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function sum (a, b) {\n return a + b;\n }\n\n /**\n * A curried version of {@link module:lamb.sum|sum}.\n * @example\n * var add5 = _.add(5);\n *\n * _.add5(4) // => 9\n * _.add5(-2) // => 3\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.sum|sum}\n * @since 0.1.0\n * @param {Number} a\n * @returns {Function}\n */\n var add = _curry2(sum, true);\n\n /**\n * Subtracts two numbers.\n * @example\n * _.subtract(5, 3) // => 2\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.deduct|deduct}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function subtract (a, b) {\n return a - b;\n }\n\n /**\n * A curried version of {@link module:lamb.subtract|subtract} that expects the\n * subtrahend to build a function waiting for the minuend.\n * @example\n * var deduct5 = _.deduct(5);\n *\n * deduct5(12) // => 7\n * deduct5(3) // => -2\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.subtract|subtract}\n * @since 0.50.0\n * @param {Number} a\n * @returns {Function}\n */\n var deduct = _curry2(subtract, true);\n\n /**\n * Divides two numbers.\n * @example\n * _.divide(5, 2) // => 2.5\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.divideBy|divideBy}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function divide (a, b) {\n return a / b;\n }\n\n /**\n * A curried version of {@link module:lamb.divide|divide} that expects a divisor to\n * build a function waiting for the dividend.\n * @example\n * var halve = divideBy(2);\n *\n * halve(10) // => 5\n * halve(5) // => 2.5\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.divide|divide}\n * @since 0.50.0\n * @param {Number} a\n * @returns {Function}\n */\n var divideBy = _curry2(divide, true);\n\n /**\n * Generates a sequence of values of the desired length with the provided iteratee.\n * The values being iterated, and received by the iteratee, are the results generated so far.\n * @example\n * var fibonacci = function (n, idx, results) {\n * return n + (results[idx - 1] || 0);\n * };\n *\n * _.generate(1, 10, fibonacci) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.range|range}\n * @since 0.21.0\n * @param {*} start - The starting value\n * @param {Number} len - The desired length for the sequence\n * @param {ListIteratorCallback} iteratee\n * @returns {Array}\n */\n function generate (start, len, iteratee) {\n var result = [start];\n\n for (var i = 0, limit = len - 1; i < limit; i++) {\n result.push(iteratee(result[i], i, result));\n }\n\n return result;\n }\n\n /**\n * Verifies whether the received value is a finite number.
\n * Behaves almost as a shim of ES6's [Number.isFinite]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite},\n * but with a difference: it will return true even for Number object's instances.\n * @example\n * _.isFinite(5) // => true\n * _.isFinite(new Number(5)) // => true\n * _.isFinite(Infinity) // => false\n * _.isFinite(-Infinity) // => false\n * _.isFinite(\"5\") // => false\n * _.isFinite(NaN) // => false\n * _.isFinite(null) // => false\n *\n * @alias module:lamb.isFinite\n * @category Math\n * @since 0.46.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isFinite_ (value) {\n return type(value) === \"Number\" && isFinite(value);\n }\n\n /**\n * Verifies whether the received value is a number and an integer.\n * Behaves almost as a shim of ES6's [Number.isInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger},\n * but with a difference: it will return true even for Number object's instances.\n * @example\n * _.isInteger(5) // => true\n * _.isInteger(new Number(5)) // => true\n * _.isInteger(2.5) // => false\n * _.isInteger(Infinity) // => false\n * _.isInteger(-Infinity) // => false\n * _.isInteger(\"5\") // => false\n * _.isInteger(NaN) // => false\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.isSafeInteger|isSafeInteger}\n * @since 0.46.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isInteger (value) {\n return type(value) === \"Number\" && value % 1 === 0;\n }\n\n /**\n * Verifies whether the received value is a \"safe integer\", meaning that is a number and that\n * can be exactly represented as an IEEE-754 double precision number.\n * The safe integers consist of all integers from -(253 - 1) inclusive to\n * 253 - 1 inclusive.
\n * Behaves almost as a shim of ES6's [Number.isSafeInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger},\n * but with a difference: it will return true even for Number object's instances.\n * @example\n * _.isSafeInteger(5) // => true\n * _.isSafeInteger(new Number(5)) // => true\n * _.isSafeInteger(Math.pow(2, 53) - 1) // => true\n * _.isSafeInteger(Math.pow(2, 53)) // => false\n * _.isSafeInteger(2e32) // => false\n * _.isSafeInteger(2.5) // => false\n * _.isSafeInteger(Infinity) // => false\n * _.isSafeInteger(-Infinity) // => false\n * _.isSafeInteger(\"5\") // => false\n * _.isSafeInteger(NaN) // => false\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.isInteger|isInteger}\n * @since 0.46.0\n * @param {*} value\n * @returns {Boolean}\n */\n function isSafeInteger (value) {\n return isInteger(value) && Math.abs(value) <= MAX_SAFE_INTEGER;\n }\n\n /**\n * Performs the modulo operation and should not be confused with the\n * {@link module:lamb.remainder|remainder}.\n * The function performs a floored division to calculate the result and not\n * a truncated one, hence the sign of the dividend is not kept, unlike the\n * {@link module:lamb.remainder|remainder}.\n * @example\n * _.modulo(5, 3) // => 2\n * _.remainder(5, 3) // => 2\n *\n * _.modulo(-5, 3) // => 1\n * _.remainder(-5, 3) // => -2\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.remainder|remainder}\n * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function modulo (a, b) {\n return a - (b * Math.floor(a / b));\n }\n\n /**\n * Multiplies two numbers.\n * @example\n * _.multiply(5, 3) // => 15\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.multiplyBy|multiplyBy}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function multiply (a, b) {\n return a * b;\n }\n\n /**\n * A curried version of {@link module:lamb.multiply|multiply}.\n * @example\n * var double = _.multiplyBy(2);\n *\n * double(5) // => 10\n *\n * @memberof module:lamb\n * @category Math\n * @function\n * @see {@link module:lamb.multiply|multiply}\n * @since 0.50.0\n * @param {Number} a\n * @returns {Function}\n */\n var multiplyBy = _curry2(multiply, true);\n\n /**\n * Generates a random integer between two given integers, both included.\n * Note that no safety measure is taken if the provided arguments aren't integers, so\n * you may end up with unexpected (not really) results.\n * For example randomInt(0.1, 1.2) could be 2.\n * @example\n *\n * _.randomInt(1, 10) // => an integer >=1 && <= 10\n *\n * @memberof module:lamb\n * @category Math\n * @since 0.1.0\n * @param {Number} min\n * @param {Number} max\n * @returns {Number}\n */\n function randomInt (min, max) {\n return Math.floor(Math.random() * (max - min + 1) + min);\n }\n\n /**\n * Converts a value to a number and returns it if it's not NaN, otherwise\n * returns zero.\n * @private\n * @param {*} value\n * @returns {Number}\n */\n function _forceToNumber (value) {\n var n = +value;\n\n return n === n ? n : 0; // eslint-disable-line no-self-compare\n }\n\n /**\n * Generates an arithmetic progression of numbers starting from start up to,\n * but not including, limit, using the given step.\n * @example\n * _.range(2, 10) // => [2, 3, 4, 5, 6, 7, 8, 9]\n * _.range(1, -10, -2) // => [1, -1, -3, -5, -7, -9]\n * _.range(0, 3, 1) // => [0, 1, 2]\n * _.range(-0, 3, 1) // => [-0, 1, 2]\n * _.range(1, -10, 2) // => []\n * _.range(3, 5, -1) // => []\n *\n * @example Behaviour if step happens to be zero:\n * _.range(2, 10, 0) // => [2]\n * _.range(2, -10, 0) // => [2]\n * _.range(2, 2, 0) // => []\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.generate|generate}\n * @since 0.1.0\n * @param {Number} start\n * @param {Number} limit\n * @param {Number} [step=1]\n * @returns {Number[]}\n */\n function range (start, limit, step) {\n start = _forceToNumber(start);\n limit = _forceToNumber(limit);\n step = arguments.length === 3 ? _forceToNumber(step) : 1;\n\n if (step === 0) {\n return limit === start ? [] : [start];\n }\n\n var len = Math.max(Math.ceil((limit - start) / step), 0);\n var result = Array(len);\n\n for (var i = 0, last = start; i < len; i++) {\n result[i] = last;\n last += step;\n }\n\n return result;\n }\n\n /**\n * Gets the remainder of the division of two numbers.\n * Not to be confused with the {@link module:lamb.modulo|modulo} as the remainder\n * keeps the sign of the dividend and may lead to some unexpected results.\n * @example\n * // example of wrong usage of the remainder\n * // (in this case the modulo operation should be used)\n * var isOdd = function (n) { return _.remainder(n, 2) === 1; };\n * isOdd(-3) // => false as -3 % 2 === -1\n *\n * @memberof module:lamb\n * @category Math\n * @see {@link module:lamb.modulo|modulo}\n * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation}\n * @since 0.1.0\n * @param {Number} a\n * @param {Number} b\n * @returns {Number}\n */\n function remainder (a, b) {\n return a % b;\n }\n\n /**\n * Checks whether the specified key is a own enumerable property of the given object or not.\n * @private\n * @function\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n var _isOwnEnumerable = generic(Object.prototype.propertyIsEnumerable);\n\n /**\n * Builds a list of the enumerable properties of an object.\n * The function is null-safe, unlike the public one.\n * @private\n * @param {Object} obj\n * @returns {String[]}\n */\n function _safeEnumerables (obj) {\n var result = [];\n\n for (var key in obj) {\n result.push(key);\n }\n\n return result;\n }\n\n /**\n * Checks whether the specified key is an enumerable property of the given object or not.\n * @private\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n function _isEnumerable (obj, key) {\n return key in Object(obj) && (_isOwnEnumerable(obj, key) || ~_safeEnumerables(obj).indexOf(key));\n }\n\n /**\n * Helper to retrieve the correct key while evaluating a path.\n * @private\n * @param {Object} target\n * @param {String} key\n * @param {Boolean} includeNonEnumerables\n * @returns {String|Number|Undefined}\n */\n function _getPathKey (target, key, includeNonEnumerables) {\n if (includeNonEnumerables && key in Object(target) || _isEnumerable(target, key)) {\n return key;\n }\n\n var n = +key;\n var len = target && target.length;\n\n return n >= -len && n < len ? n < 0 ? n + len : n : void 0;\n }\n\n /**\n * Checks if a path is valid in the given object and retrieves the path target.\n * @private\n * @param {Object} obj\n * @param {String[]} parts\n * @param {Boolean} walkNonEnumerables\n * @returns {Object}\n */\n function _getPathInfo (obj, parts, walkNonEnumerables) {\n if (isNil(obj)) {\n throw _makeTypeErrorFor(obj, \"object\");\n }\n\n var target = obj;\n var i = -1;\n var len = parts.length;\n var key;\n\n while (++i < len) {\n key = _getPathKey(target, parts[i], walkNonEnumerables);\n\n if (isUndefined(key)) {\n break;\n }\n\n target = target[key];\n }\n\n return i === len ? { isValid: true, target: target } : { isValid: false, target: void 0 };\n }\n\n /**\n * Splits a sting path using the provided separator and returns an array\n * of path parts.\n * @private\n * @param {String} path\n * @param {String} separator\n * @returns {String[]}\n */\n function _toPathParts (path, separator) {\n return String(path).split(separator || \".\");\n }\n\n /**\n * Gets a nested property value from an object using the given path.
\n * The path is a string with property names separated by dots by default, but\n * it can be customised with the optional third parameter.
\n * You can use integers in the path, even negative ones, to refer to array-like\n * object indexes, but the priority will be given to existing object keys:\n * the last example explains this particular case.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * \"user.name\": \"jdoe\",\n * password: \"abc123\"\n * },\n * scores: [\n * {id: 1, value: 10},\n * {id: 2, value: 20},\n * {id: 3, value: 30}\n * ]\n * };\n *\n * _.getPathIn(user, \"name\") // => \"John\"\n * _.getPathIn(user, \"login.password\") // => \"abc123\";\n * _.getPathIn(user, \"login/user.name\", \"/\") // => \"jdoe\"\n * _.getPathIn(user, \"name.foo\") // => undefined\n * _.getPathIn(user, \"name.foo.bar\") // => undefined\n *\n * @example Accessing array-like objects indexes:\n * _.getPathIn(user, \"login.password.1\") // => \"b\"\n * _.getPathIn(user, \"scores.0\") // => {id: 1, value: 10}\n * _.getPathIn(user, \"scores.-1.value\") // => 30\n *\n * @example Priority will be given to existing object keys over indexes:\n * _.getPathIn(user, \"scores.-1\") // => {id: 3, value: 30}\n *\n * // let's do something funny\n * user.scores[\"-1\"] = \"foo bar\";\n *\n * _.getPathIn(user, \"scores.-1\") // => \"foo bar\";\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.getPath|getPath}\n * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey}\n * @since 0.19.0\n * @param {Object|ArrayLike} obj\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {*}\n */\n function getPathIn (obj, path, separator) {\n return _getPathInfo(obj, _toPathParts(path, separator), true).target;\n }\n\n /**\n * Builds a checker function meant to be used with\n * {@link module:lamb.validate|validate}.
\n * Note that the function accepts multiple keyPaths as a means to\n * compare their values. In other words all the received keyPaths will be\n * passed as arguments to the predicate to run the test.
\n * If you want to run the same single property check with multiple properties, you should build\n * multiple checkers and combine them with {@link module:lamb.validate|validate}.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * username: \"jdoe\",\n * password: \"abc123\",\n * passwordConfirm: \"abc123\"\n * }\n * };\n * var pwdMatch = _.checker(\n * _.areSame,\n * \"Passwords don't match\",\n * [\"login.password\", \"login.passwordConfirm\"]\n * );\n *\n * pwdMatch(user) // => []\n *\n * var newUser = _.setPathIn(user, \"login.passwordConfirm\", \"avc123\");\n *\n * pwdMatch(newUser) // => [\"Passwords don't match\", [\"login.password\", \"login.passwordConfirm\"]]\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.validate|validate}, {@link module:lamb.validateWith|validateWith}\n * @since 0.1.0\n * @param {Function} predicate - The predicate to test the object properties\n * @param {String} message - The error message\n * @param {String[]} keyPaths - The array of keys, or {@link module:lamb.getPathIn|paths}, to test.\n * @param {String} [pathSeparator=\".\"]\n * @returns {Function} A checker function which returns an error in the form\n * [\"message\", [\"propertyA\", \"propertyB\"]] or an empty array.\n */\n function checker (predicate, message, keyPaths, pathSeparator) {\n return function (obj) {\n var getValues = partial(getPathIn, [obj, __, pathSeparator]);\n\n return predicate.apply(obj, map(keyPaths, getValues)) ? [] : [message, keyPaths];\n };\n }\n\n /**\n * Creates a non-null-safe version of the provided \"getKeys\" function.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _unsafeKeyListFrom = _curry2(function (getKeys, obj) {\n if (isNil(obj)) {\n throw _makeTypeErrorFor(obj, \"object\");\n }\n\n return getKeys(obj);\n });\n\n /**\n * Creates an array with all the enumerable properties of the given object.\n * @example Showing the difference with {@link module:lamb.keys|keys}:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3},\n * d: {value: 4, enumerable: true}\n * });\n *\n * _.keys(foo) // => [\"d\"]\n * _.enumerables(foo) // => [\"d\", \"a\"]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.keys|keys}\n * @since 0.12.0\n * @param {Object} obj\n * @returns {String[]}\n */\n var enumerables = _unsafeKeyListFrom(_safeEnumerables);\n\n /**\n * Builds an object from a list of key / value pairs like the one\n * returned by {@link module:lamb.pairs|pairs} or {@link module:lamb.ownPairs|ownPairs}.
\n * In case of duplicate keys the last key / value pair is used.\n * @example\n * _.fromPairs([[\"a\", 1], [\"b\", 2], [\"c\", 3]]) // => {\"a\": 1, \"b\": 2, \"c\": 3}\n * _.fromPairs([[\"a\", 1], [\"b\", 2], [\"a\", 3]]) // => {\"a\": 3, \"b\": 2}\n * _.fromPairs([[1], [void 0, 2], [null, 3]]) // => {\"1\": undefined, \"undefined\": 2, \"null\": 3}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.ownPairs|ownPairs}, {@link module:lamb.pairs|pairs}\n * @since 0.8.0\n * @param {Array>} pairsList\n * @returns {Object}\n */\n function fromPairs (pairsList) {\n var result = {};\n\n forEach(pairsList, function (pair) {\n result[pair[0]] = pair[1];\n });\n\n return result;\n }\n\n /**\n * Builds a partial application of {@link module:lamb.getPathIn|getPathIn} with the given\n * path and separator, expecting the object to act upon.
\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * \"user.name\": \"jdoe\",\n * password: \"abc123\"\n * }\n * };\n *\n * var getPwd = _.getPath(\"login.password\");\n * var getUsername = _.getPath(\"login/user.name\", \"/\");\n *\n * getPwd(user) // => \"abc123\";\n * getUsername(user) // => \"jdoe\"\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.getPathIn|getPathIn}\n * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey}\n * @since 0.19.0\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n var getPath = _makePartial3(getPathIn);\n\n /**\n * Verifies the existence of a property in an object.\n * @example\n * var user1 = {name: \"john\"};\n *\n * _.has(user1, \"name\") // => true\n * _.has(user1, \"surname\") // => false\n * _.has(user1, \"toString\") // => true\n *\n * var user2 = Object.create(null);\n *\n * // not inherited through the prototype chain\n * _.has(user2, \"toString\") // => false\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.hasKey|hasKey}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n function has (obj, key) {\n if (typeof obj !== \"object\" && !isUndefined(obj)) {\n obj = Object(obj);\n }\n\n return key in obj;\n }\n\n /**\n * Curried version of {@link module:lamb.has|has}.
\n * Returns a function expecting the object to check against the given key.\n * @example\n * var user1 = {name: \"john\"};\n * var user2 = {};\n * var hasName = _.hasKey(\"name\");\n *\n * hasName(user1) // => true\n * hasName(user2) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.has|has}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {String} key\n * @returns {Function}\n */\n var hasKey = _curry2(has, true);\n\n /**\n * Verifies if an object has the specified property and that the property isn't inherited through\n * the prototype chain.
\n * @example Comparison with has:\n * var user = {name: \"john\"};\n *\n * _.has(user, \"name\") // => true\n * _.has(user, \"surname\") // => false\n * _.has(user, \"toString\") // => true\n *\n * _.hasOwn(user, \"name\") // => true\n * _.hasOwn(user, \"surname\") // => false\n * _.hasOwn(user, \"toString\") // => false\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {Object} obj\n * @param {String} key\n * @returns {Boolean}\n */\n var hasOwn = generic(Object.prototype.hasOwnProperty);\n\n /**\n * Curried version of {@link module:lamb.hasOwn|hasOwn}.
\n * Returns a function expecting the object to check against the given key.\n * @example\n * var user = {name: \"john\"};\n * var hasOwnName = _.hasOwnKey(\"name\");\n * var hasOwnToString = _.hasOwnToString(\"toString\");\n *\n * hasOwnName(user) // => true\n * hasOwnToString(user) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.hasOwn|hasOwn}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists}\n * @since 0.1.0\n * @param {String} key\n * @returns {Function}\n */\n var hasOwnKey = _curry2(hasOwn, true);\n\n /**\n * Builds a predicate expecting an object to check against the given key / value pair.
\n * The value check is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.\n * @example\n * var hasTheCorrectAnswer = _.hasKeyValue(\"answer\", 42);\n *\n * hasTheCorrectAnswer({answer: 2}) // false\n * hasTheCorrectAnswer({answer: 42}) // true\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.hasPathValue|hasPathValue}\n * @since 0.1.0\n * @param {String} key\n * @param {*} value\n * @returns {Function}\n */\n function hasKeyValue (key, value) {\n return function (obj) {\n return isUndefined(value) ? has(obj, key) && obj[key] === value : areSVZ(value, obj[key]);\n };\n }\n\n /**\n * Builds a predicate to check if the given path exists in an object and holds the desired value.
\n * The value check is made with the [\"SameValueZero\" comparison]{@link module:lamb.areSVZ|areSVZ}.
\n * Note that the function will check even non-enumerable properties.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * personal: {\n * age: 25,\n * gender: \"M\"\n * },\n * scores: [\n * {id: 1, value: 10, passed: false},\n * {id: 2, value: 20, passed: false},\n * {id: 3, value: 30, passed: true}\n * ]\n * };\n *\n * var isMale = _.hasPathValue(\"personal.gender\", \"M\");\n * var hasPassedFirstTest = _.hasPathValue(\"scores.0.passed\", true);\n * var hasPassedLastTest = _.hasPathValue(\"scores.-1.passed\", true);\n *\n * isMale(user) // => true\n * hasPassedFirstTest(user) // => false\n * hasPassedLastTest(user) // => true\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.hasKeyValue|hasKeyValue}\n * @since 0.41.0\n * @param {String} path\n * @param {*} value\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function hasPathValue (path, value, separator) {\n return function (obj) {\n var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true);\n\n return pathInfo.isValid && areSVZ(pathInfo.target, value);\n };\n }\n\n /**\n * Makes an object immutable by recursively calling Object.freeze\n * on its members.\n * @private\n * @param {Object} obj\n * @param {Array} seen\n * @returns {Object} The obj parameter itself, not a copy.\n */\n function _immutable (obj, seen) {\n if (seen.indexOf(obj) === -1) {\n seen.push(Object.freeze(obj));\n\n forEach(Object.getOwnPropertyNames(obj), function (key) {\n var value = obj[key];\n\n if (typeof value === \"object\" && !isNull(value)) {\n _immutable(value, seen);\n }\n });\n }\n\n return obj;\n }\n\n /**\n * Makes an object immutable by recursively calling [Object.freeze]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze}\n * on its members.
\n * Any attempt to extend or modify the object can throw a TypeError or fail silently,\n * depending on the environment and the [strict mode]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode} directive.\n * @example\n * var user = _.immutable({\n * name: \"John\",\n * surname: \"Doe\",\n * login: {\n * username: \"jdoe\",\n * password: \"abc123\"\n * },\n * luckyNumbers: [13, 17]\n * });\n *\n * // All of these statements will fail and possibly\n * // throw a TypeError (see the function description)\n * user.name = \"Joe\";\n * delete user.name;\n * user.newProperty = [];\n * user.login.password = \"foo\";\n * user.luckyNumbers.push(-13);\n *\n * @memberof module:lamb\n * @category Object\n * @since 0.8.0\n * @param {Object} obj\n * @returns {Object}\n */\n function immutable (obj) {\n return _immutable(obj, []);\n }\n\n /**\n * A null-safe version of Object.keys.\n * @private\n * @function\n * @param {Object} obj\n * @returns {String[]}\n */\n var _safeKeys = compose(Object.keys, Object);\n\n /**\n * Retrieves the list of the own enumerable properties of an object.
\n * Although [Object.keys]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys}\n * is already present in ECMAScript 5, its behaviour changed in the subsequent specifications\n * of the standard.
\n * This function shims the ECMAScript 6 version, by forcing a conversion to\n * object for any value but null and undefined.\n * @example Showing the difference with {@link module:lamb.enumerables|enumerables}:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3},\n * d: {value: 4, enumerable: true}\n * });\n *\n * _.enumerables(foo) // => [\"d\", \"a\"]\n * _.keys(foo) // => [\"d\"]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.enumerables|enumerables}\n * @since 0.25.1\n * @param {Object} obj\n * @returns {String[]}\n */\n var keys = _unsafeKeyListFrom(_safeKeys);\n\n /**\n * Builds a predicate to check if the given key satisfies the desired condition\n * on an object.\n * @example\n * var users = [\n * {name: \"John\", age: 25},\n * {name: \"Jane\", age: 15},\n * ];\n * var isAdult = _.keySatisfies(_.isGTE(18), \"age\");\n *\n * isAdult(users[0]) // => true\n * isAdult(users[1]) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pathSatisfies|pathSatisfies}\n * @since 0.45.0\n * @param {Function} predicate\n * @param {String} key\n * @returns {Function}\n */\n function keySatisfies (predicate, key) {\n return function (obj) {\n return predicate.call(this, obj[key]);\n };\n }\n\n /**\n * Builds an object from the two given lists, using the first one as keys and the last\n * one as values.
\n * If the list of keys is longer than the values one, the keys will be created with\n * undefined values.
\n * If more values than keys are supplied, the extra values will be ignored.\n * @example\n * _.make([\"a\", \"b\", \"c\"], [1, 2, 3]) // => {a: 1, b: 2, c: 3}\n * _.make([\"a\", \"b\", \"c\"], [1, 2]) // => {a: 1, b: 2, c: undefined}\n * _.make([\"a\", \"b\"], [1, 2, 3]) // => {a: 1, b: 2}\n * _.make([null, void 0, 2], [1, 2, 3]) // => {\"null\": 1, \"undefined\": 2, \"2\": 3}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.tear|tear}, {@link module:lamb.tearOwn|tearOwn} for the reverse operation\n * @since 0.8.0\n * @param {String[]} names\n * @param {ArrayLike} values\n * @returns {Object}\n */\n function make (names, values) {\n var result = {};\n var valuesLen = values.length;\n\n for (var i = 0, len = names.length; i < len; i++) {\n result[names[i]] = i < valuesLen ? values[i] : void 0;\n }\n\n return result;\n }\n\n /**\n * Creates a new object by applying the given function\n * to all enumerable properties of the source one.\n * @example\n * var weights = {\n * john: \"72.5 Kg\",\n * jane: \"52.3 Kg\"\n * };\n *\n * _.mapValues(weights, parseFloat) // => {john: 72.5, jane: 52.3}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.mapValuesWith|mapValuesWith}\n * @since 0.54.0\n * @param {Object} source\n * @param {ObjectIteratorCallback} fn\n * @returns {Object}\n */\n function mapValues (source, fn) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n var result = {};\n\n for (var key in source) {\n result[key] = fn(source[key], key, source);\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.mapValues|mapValues}.
\n * Expects a mapping function to build a new function waiting for the\n * object to act upon.\n * @example\n * var incValues = _.mapValuesWith(_.add(1));\n * var results = {\n * first: 10,\n * second: 5,\n * third: 3\n * };\n *\n * incValues(results) // => {first: 11, second: 6, third: 4}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.mapValues|mapValues}\n * @since 0.54.0\n * @function\n * @param {ObjectIteratorCallback} fn\n * @returns {Function}\n */\n var mapValuesWith = _curry2(mapValues, true);\n\n /**\n * Merges the received objects using the provided function to retrieve their keys.\n * @private\n * @param {Function} getKeys\n * @param {Object} a\n * @param {Object} b\n * @returns {Function}\n */\n function _merge (getKeys, a, b) {\n return reduce([a, b], function (result, source) {\n forEach(getKeys(source), function (key) {\n result[key] = source[key];\n });\n\n return result;\n }, {});\n }\n\n /**\n * Merges the enumerable properties of the provided sources into a new object.
\n * In case of key homonymy the last source has precedence over the first.\n * @example\n * _.merge({a: 1, b: 3}, {b: 5, c: 4}) // => {a: 1, b: 5, c: 4}\n *\n * @example Array-like objects will be transformed to objects with numbers as keys:\n * _.merge([1, 2], {a: 2}) // => {\"0\": 1, \"1\": 2, a: 2}\n * _.merge(\"foo\", {a: 2}) // => {\"0\": \"f\", \"1\": \"o\", \"2\": \"o\", a: 2}\n *\n * @example Every other non-nil value will be treated as an empty object:\n * _.merge({a: 2}, 99) // => {a: 2}\n * _.merge({a: 2}, NaN) // => {a: 2}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.mergeOwn|mergeOwn} to merge own properties only\n * @since 0.10.0\n * @function\n * @param {Object} a\n * @param {Object} b\n * @returns {Object}\n */\n var merge = partial(_merge, [enumerables]);\n\n /**\n * Same as {@link module:lamb.merge|merge}, but only the own properties of the\n * sources are taken into account.\n * @example Showing the difference with merge:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * var bar = {d: 4};\n *\n * _.merge(foo, bar) // => {a: 1, b: 2, c: 3, d: 4}\n * _.mergeOwn(foo, bar) // => {c: 3, d: 4}\n *\n * @example Array-like objects will be transformed to objects with numbers as keys:\n * _.mergeOwn([1, 2], {a: 2}) // => {\"0\": 1, \"1\": 2, a: 2}\n * _.mergeOwn(\"foo\", {a: 2}) // => {\"0\": \"f\", \"1\": \"o\", \"2\": \"o\", a: 2}\n *\n * @example Every other non-nil value will be treated as an empty object:\n * _.mergeOwn({a: 2}, 99) // => {a: 2}\n * _.mergeOwn({a: 2}, NaN) // => {a: 2}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.merge|merge} to merge all enumerable properties\n * @since 0.12.0\n * @function\n * @param {Object} a\n * @param {Object} b\n * @returns {Object}\n */\n var mergeOwn = partial(_merge, [keys]);\n\n /**\n * Accepts an object and build a function expecting a key to create a \"pair\" with the key\n * and its value.\n * @private\n * @function\n * @param {Object} obj\n * @returns {Function}\n */\n var _keyToPairIn = _curry2(function (obj, key) {\n return [key, obj[key]];\n });\n\n /**\n * Using the provided function to retrieve the keys, builds a new function\n * expecting an object to create a list of key / value pairs.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _pairsFrom = _curry2(function (getKeys, obj) {\n return map(getKeys(obj), _keyToPairIn(obj));\n });\n\n /**\n * Same as {@link module:lamb.pairs|pairs}, but only the own enumerable properties of the object are\n * taken into account.
\n * See also {@link module:lamb.fromPairs|fromPairs} for the reverse operation.\n * @example Showing the difference with pairs:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * _.pairs(foo) // => [[\"c\", 3], [\"b\", 2], [\"a\", 1]]\n * _.ownPairs(foo) // => [[\"c\", 3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.pairs|pairs}\n * @see {@link module:lamb.fromPairs|fromPairs}\n * @since 0.12.0\n * @param {Object} obj\n * @returns {Array>}\n */\n var ownPairs = _pairsFrom(keys);\n\n /**\n * Using the provided function to retrieve the keys of an object, builds\n * a function expecting an object to create the list of values for such keys.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _valuesFrom = _curry2(function (getKeys, obj) {\n return map(getKeys(obj), function (key) {\n return obj[key];\n });\n });\n\n /**\n * Same as {@link module:lamb.values|values}, but only the own enumerable properties of the object are\n * taken into account.
\n * @example Showing the difference with values:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * _.values(foo) // => [3, 2, 1]\n * _.ownValues(foo) // => [3]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.values|values}\n * @since 0.12.0\n * @param {Object} obj\n * @returns {Array}\n */\n var ownValues = _valuesFrom(keys);\n\n /**\n * Converts an object into an array of key / value pairs of its enumerable properties.
\n * See also {@link module:lamb.ownPairs|ownPairs} for picking only the own enumerable\n * properties and {@link module:lamb.fromPairs|fromPairs} for the reverse operation.\n * @example\n * _.pairs({a: 1, b: 2, c: 3}) // => [[\"a\", 1], [\"b\", 2], [\"c\", 3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.ownPairs|ownPairs}\n * @see {@link module:lamb.fromPairs|fromPairs}\n * @since 0.8.0\n * @param {Object} obj\n * @returns {Array>}\n */\n var pairs = _pairsFrom(enumerables);\n\n /**\n * Checks if the provided path exists in the given object.
\n * Note that the function will check even non-enumerable properties.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * address: {\n * city: \"New York\"\n * },\n * scores: [10, 20, 15]\n * };\n *\n * _.pathExistsIn(user, \"address.city\") // => true\n * _.pathExistsIn(user, \"address.country\") // => false\n * _.pathExistsIn(user, \"scores.1\") // => true\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pathExists|pathExists}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @since 0.43.0\n * @param {Object} obj\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Boolean}\n */\n function pathExistsIn (obj, path, separator) {\n return _getPathInfo(obj, _toPathParts(path, separator), true).isValid;\n }\n\n /**\n * Builds a partial application of {@link module:lamb.pathExistsIn|pathExistsIn} using the given\n * path and the optional separator. The resulting function expects the object to check.
\n * Note that the function will check even non-enumerable properties.\n * @example\n * var user = {\n * name: \"John\",\n * surname: \"Doe\",\n * address: {\n * city: \"New York\"\n * },\n * scores: [10, 20, 15]\n * };\n *\n * var hasCity = _.pathExists(\"address.city\");\n * var hasCountry = _.pathExists(\"address.country\");\n * var hasAtLeastThreeScores = _.pathExists(\"scores.2\");\n *\n * hasCity(user) // => true\n * hasCountry(user) // => false\n * hasAtLeastThreeScores(user) // => true\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.pathExistsIn|pathExistsIn}\n * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey}\n * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey}\n * @since 0.43.0\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n var pathExists = _makePartial3(pathExistsIn);\n\n /**\n * Builds a predicate that verifies if a condition is satisfied for the given\n * path in an object.
\n * Like the other \"path functions\" you can use integers in the path, even\n * negative ones, to refer to array-like object indexes, but the priority will\n * be given to existing object keys.\n * @example\n * var user = {\n * name: \"John\",\n * performance: {\n * scores: [1, 5, 10]\n * }\n * };\n *\n * var gotAnHighScore = _.pathSatisfies(_.contains(10), \"performance.scores\");\n * var hadAGoodStart = _.pathSatisfies(_.isGT(6), \"performance.scores.0\");\n *\n * gotAnHighScore(user) // => true\n * hadAGoodStart(user) // => false\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.keySatisfies|keySatisfies}\n * @since 0.45.0\n * @param {Function} predicate\n * @param {String} path\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function pathSatisfies (predicate, path, separator) {\n return function (obj) {\n var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true);\n\n return predicate.call(this, pathInfo.target);\n };\n }\n\n /**\n * Returns an object containing only the specified properties of the given object.
\n * Non existent properties will be ignored.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n *\n * _.pick(user, [\"name\", \"age\"]) // => {\"name\": \"john\", \"age\": 30};\n * _.pick(user, [\"name\", \"email\"]) // => {\"name\": \"john\"}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pickIf|pickIf}, {@link module:lamb.pickKeys|pickKeys}\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf}\n * @since 0.1.0\n * @param {Object} source\n * @param {String[]} whitelist\n * @returns {Object}\n */\n function pick (source, whitelist) {\n var result = {};\n\n for (var i = 0, len = whitelist.length, key; i < len; i++) {\n key = whitelist[i];\n\n if (has(source, key)) {\n result[key] = source[key];\n }\n }\n\n return result;\n }\n\n /**\n * Builds a function expecting an object whose enumerable properties will be checked\n * against the given predicate.
\n * The properties satisfying the predicate will be included in the resulting object.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n * var pickIfIsString = _.pickIf(_.isType(\"String\"));\n *\n * pickIfIsString(user) // => {name: \"john\", surname: \"doe\"}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys},\n * {@link module:lamb.skipIf|skipIf}\n * @since 0.1.0\n * @param {ObjectIteratorCallback} predicate\n * @returns {Function}\n */\n function pickIf (predicate) {\n return function (source) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n var result = {};\n\n for (var key in source) {\n if (predicate(source[key], key, source)) {\n result[key] = source[key];\n }\n }\n\n return result;\n };\n }\n\n /**\n * A curried version of {@link module:lamb.pick|pick}, expecting a whitelist of keys to build\n * a function waiting for the object to act upon.\n * @example\n * var user = {id: 1, name: \"Jane\", surname: \"Doe\", active: false};\n * var getUserInfo = _.pickKeys([\"id\", \"active\"]);\n *\n * getUserInfo(user) // => {id: 1, active: false}\n *\n * @example A useful composition with mapWith:\n * var users = [\n * {id: 1, name: \"Jane\", surname: \"Doe\", active: false},\n * {id: 2, name: \"John\", surname: \"Doe\", active: true},\n * {id: 3, name: \"Mario\", surname: \"Rossi\", active: true},\n * {id: 4, name: \"Paolo\", surname: \"Bianchi\", active: false}\n * ];\n * var select = _.compose(_.mapWith, _.pickKeys);\n * var selectUserInfo = select([\"id\", \"active\"]);\n *\n * selectUserInfo(users) // =>\n * // [\n * // {id: 1, active: false},\n * // {id: 2, active: true},\n * // {id: 3, active: true},\n * // {id: 4, active: false}\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickIf|pickIf}\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys},\n * {@link module:lamb.skipIf|skipIf}\n * @since 0.35.0\n * @param {String[]} whitelist\n * @returns {Function}\n */\n var pickKeys = _curry2(pick, true);\n\n /**\n * Creates a copy of the given object with its enumerable keys renamed as\n * indicated in the provided lookup table.\n * @example\n * var person = {\"firstName\": \"John\", \"lastName\": \"Doe\"};\n * var keysMap = {\"firstName\": \"name\", \"lastName\": \"surname\"};\n *\n * _.rename(person, keysMap) // => {\"name\": \"John\", \"surname\": \"Doe\"}\n *\n * @example It's safe using it to swap keys:\n * var keysMap = {\"firstName\": \"lastName\", \"lastName\": \"firstName\"};\n *\n * _.rename(person, keysMap) // => {\"lastName\": \"John\", \"firstName\": \"Doe\"}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.renameKeys|renameKeys}, {@link module:lamb.renameWith|renameWith}\n * @since 0.26.0\n * @param {Object} source\n * @param {Object} keysMap\n * @returns {Object}\n */\n function rename (source, keysMap) {\n keysMap = Object(keysMap);\n var result = {};\n var oldKeys = enumerables(source);\n\n for (var prop in keysMap) {\n if (~oldKeys.indexOf(prop)) {\n result[keysMap[prop]] = source[prop];\n }\n }\n\n for (var i = 0, len = oldKeys.length, key; i < len; i++) {\n key = oldKeys[i];\n\n if (!(key in keysMap || key in result)) {\n result[key] = source[key];\n }\n }\n\n return result;\n }\n\n /**\n * A curried version of {@link module:lamb.rename|rename} expecting a\n * keysMap to build a function waiting for the object to act upon.\n * @example\n * var persons = [\n * {\"firstName\": \"John\", \"lastName\": \"Doe\"},\n * {\"first_name\": \"Mario\", \"last_name\": \"Rossi\"},\n * ];\n * var normalizeKeys = _.renameKeys({\n * \"firstName\": \"name\",\n * \"first_name\": \"name\",\n * \"lastName\": \"surname\",\n * \"last_name\": \"surname\"\n * });\n *\n * _.map(persons, normalizeKeys) // =>\n * // [\n * // {\"name\": \"John\", \"surname\": \"Doe\"},\n * // {\"name\": \"Mario\", \"surname\": \"Rossi\"}\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameWith|renameWith}\n * @since 0.26.0\n * @param {Object} keysMap\n * @returns {Function}\n */\n var renameKeys = _curry2(rename, true);\n\n /**\n * Uses the provided function as a keysMap generator and returns\n * a function expecting the object whose keys we want to {@link module:lamb.rename|rename}.\n * @example\n * var person = {\"NAME\": \"John\", \"SURNAME\": \"Doe\"};\n * var arrayToLower = _.mapWith(_.invoker(\"toLowerCase\"));\n * var makeLowerKeysMap = function (source) {\n * var sourceKeys = _.keys(source);\n *\n * return _.make(sourceKeys, arrayToLower(sourceKeys));\n * };\n * var lowerKeysFor = _.renameWith(makeLowerKeysMap);\n *\n * lowerKeysFor(person) // => {\"name\": \"John\", \"surname\": \"doe\"};\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameKeys|renameKeys}\n * @since 0.26.0\n * @param {Function} fn\n * @returns {Function}\n */\n function renameWith (fn) {\n return function (source) {\n return rename(source, fn(source));\n };\n }\n\n /**\n * Sets, or creates, a property in a copy of the provided object to the desired value.\n * @private\n * @param {Object} source\n * @param {String} key\n * @param {*} value\n * @returns {Object}\n */\n function _setIn (source, key, value) {\n var result = {};\n\n for (var prop in source) {\n result[prop] = source[prop];\n }\n\n result[key] = value;\n\n return result;\n }\n\n /**\n * Sets the specified key to the given value in a copy of the provided object.
\n * All the remaining enumerable keys of the source object will be simply copied in the\n * result object without breaking references.
\n * If the specified key is not part of the source object, it will be added to the\n * result.
\n * The main purpose of the function is to work on simple plain objects used as\n * data structures, such as JSON objects, and makes no effort to play nice with\n * objects created from an OOP perspective (it's not worth it).
\n * For example the prototype of the result will be Object's regardless\n * of the source's one.\n * @example\n * var user = {name: \"John\", surname: \"Doe\", age: 30};\n *\n * _.setIn(user, \"name\", \"Jane\") // => {name: \"Jane\", surname: \"Doe\", age: 30}\n * _.setIn(user, \"gender\", \"male\") // => {name: \"John\", surname: \"Doe\", age: 30, gender: \"male\"}\n *\n * // `user` still is {name: \"John\", surname: \"Doe\", age: 30}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.setKey|setKey}\n * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn}\n * @since 0.18.0\n * @param {Object} source\n * @param {String} key\n * @param {*} value\n * @returns {Object}\n */\n function setIn (source, key, value) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n return _setIn(source, key, value);\n }\n\n /**\n * Builds a partial application of {@link module:lamb.setIn|setIn} with the provided\n * key and value.
\n * The resulting function expects the object to act upon.
\n * Please refer to {@link module:lamb.setIn|setIn}'s description for explanations about\n * how the copy of the source object is made.\n * @example\n * var user = {name: \"John\", surname: \"Doe\", age: 30};\n * var setAgeTo40 = _.setKey(\"age\", 40);\n *\n * setAgeTo40(user) // => {name: \"john\", surname: \"doe\", age: 40}\n *\n * // `user` still is {name: \"John\", surname: \"Doe\", age: 30}\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.setIn|setIn}\n * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn}\n * @since 0.18.0\n * @param {String} key\n * @param {*} value\n * @returns {Function}\n */\n var setKey = _makePartial3(setIn);\n\n /**\n * Accepts a target object and a key name and verifies that the target is an array and that\n * the key is an existing index.\n * @private\n * @param {Object} target\n * @param {String|Number} key\n * @returns {Boolean}\n */\n function _isArrayIndex (target, key) {\n var n = +key;\n\n return Array.isArray(target) && n % 1 === 0 && !(n < 0 && _isEnumerable(target, key));\n }\n\n /**\n * Sets the object's property targeted by the given path to the desired value.
\n * Works with arrays and is able to set their indexes, even negative ones.\n * @private\n * @param {Object|Array} obj\n * @param {String[]} parts\n * @param {*} value\n * @returns {Object|Array}\n */\n function _setPathIn (obj, parts, value) {\n var key = parts[0];\n var partsLen = parts.length;\n var v;\n\n if (partsLen === 1) {\n v = value;\n } else {\n var targetKey = _getPathKey(obj, key, false);\n\n v = _setPathIn(\n isUndefined(targetKey) ? targetKey : obj[targetKey],\n slice(parts, 1, partsLen),\n value\n );\n }\n\n return _isArrayIndex(obj, key) ? _setIndex(obj, key, v) : _setIn(obj, key, v);\n }\n\n /**\n * Allows to change a nested value in a copy of the provided object.
\n * The function will delegate the \"set action\" to {@link module:lamb.setIn|setIn} or\n * {@link module:lamb.setAt|setAt} depending on the value encountered in the path,\n * so please refer to the documentation of those functions for specifics about the\n * implementation.
\n * Note anyway that the distinction will be between Arrays, delegated\n * to {@link module:lamb.setAt|setAt}, and everything else (including array-like objects),\n * which will be delegated to {@link module:lamb.setIn|setIn}.
\n * As a result of that, array-like objects will be converted to objects having numbers as keys\n * and paths targeting non-object values will be converted to empty objects.
\n * You can anyway target array elements using integers in the path, even negative ones, but\n * the priority will be given to existing, and enumerable, object keys.
\n * Non-enumerable properties encountered in the path will be considered as non-existent properties.
\n * Like {@link module:lamb.getPathIn|getPathIn} or {@link module:lamb.getPath|getPath} you can\n * use custom path separators.\n * @example\n * var user = {id: 1, status: {active : false, scores: [2, 4, 6]}};\n *\n * _.setPathIn(user, \"status.active\", true) // => {id: 1, status: {active : true, scores: [2, 4, 6]}}\n *\n * @example Targeting arrays:\n * _.setPathIn(user, \"status.scores.0\", 8) // => {id: 1, status: {active : false, scores: [8, 4, 6]}}\n *\n * // you can use negative indexes as well\n * _.setPathIn(user, \"status.scores.-1\", 8) // => {id: 1, status: {active : false, scores: [2, 4, 8]}}\n *\n * @example Arrays can also be part of the path and not necessarily its target:\n * var user = {id: 1, scores: [\n * {value: 2, year: \"2000\"},\n * {value: 4, year: \"2001\"},\n * {value: 6, year: \"2002\"}\n * ]};\n *\n * var newUser = _.setPathIn(user, \"scores.0.value\", 8);\n * // \"newUser\" holds:\n * // {id: 1, scores: [\n * // {value: 8, year: \"2000\"},\n * // {value: 4, year: \"2001\"},\n * // {value: 6, year: \"2002\"}\n * // ]}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.setPath|setPath}\n * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey}\n * @since 0.20.0\n * @param {Object|Array} source\n * @param {String} path\n * @param {*} value\n * @param {String} [separator=\".\"]\n * @returns {Object|Array}\n */\n function setPathIn (source, path, value, separator) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n return _setPathIn(source, _toPathParts(path, separator), value);\n }\n\n /**\n * Builds a partial application of {@link module:lamb.setPathIn|setPathIn} expecting the\n * object to act upon.
\n * See {@link module:lamb.setPathIn|setPathIn} for more details and examples.\n * @example\n * var user = {id: 1, status: {active: false}};\n * var activate = _.setPath(\"status.active\", true);\n *\n * activate(user) // => {id: 1, status: {active: true}}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.setPathIn|setPathIn}\n * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey}\n * @since 0.20.0\n * @param {String} path\n * @param {*} value\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function setPath (path, value, separator) {\n return function (source) {\n return setPathIn(source, path, value, separator);\n };\n }\n\n /**\n * Returns a copy of the source object without the specified properties.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n *\n * _.skip(user, [\"name\", \"age\"]) // => {surname: \"doe\"};\n * _.skip(user, [\"name\", \"email\"]) // => {surname: \"doe\", age: 30};\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.skipKeys|skipKeys}, {@link module:lamb.skipIf|skipIf}\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys},\n * {@link module:lamb.pickIf|pickIf}\n * @since 0.1.0\n * @param {Object} source\n * @param {String[]} blacklist\n * @returns {Object}\n */\n function skip (source, blacklist) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"object\");\n }\n\n var result = {};\n var props = make(blacklist, []);\n\n for (var key in source) {\n if (!(key in props)) {\n result[key] = source[key];\n }\n }\n\n return result;\n }\n\n /**\n * Builds a function expecting an object whose enumerable properties will be checked\n * against the given predicate.
\n * The properties satisfying the predicate will be omitted in the resulting object.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n * var skipIfIstring = _.skipIf(_.isType(\"String\"));\n *\n * skipIfIstring(user) // => {age: 30}\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys}\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys},\n * {@link module:lamb.pickIf|pickIf}\n * @since 0.1.0\n * @param {ObjectIteratorCallback} predicate\n * @returns {Function}\n */\n var skipIf = compose(pickIf, not);\n\n /**\n * A curried version of {@link module:lamb.skip|skip}, expecting a blacklist of keys to build\n * a function waiting for the object to act upon.\n * @example\n * var user = {id: 1, name: \"Jane\", surname: \"Doe\", active: false};\n * var getUserInfo = _.skipKeys([\"name\", \"surname\"]);\n *\n * getUserInfo(user) // => {id: 1, active: false}\n *\n * @example A useful composition with mapWith:\n * var users = [\n * {id: 1, name: \"Jane\", surname: \"Doe\", active: false},\n * {id: 2, name: \"John\", surname: \"Doe\", active: true},\n * {id: 3, name: \"Mario\", surname: \"Rossi\", active: true},\n * {id: 4, name: \"Paolo\", surname: \"Bianchi\", active: false}\n * ];\n * var discard = _.compose(_.mapWith, _.skipKeys);\n * var discardNames = discard([\"name\", \"surname\"]);\n *\n * discardNames(users) // =>\n * // [\n * // {id: 1, active: false},\n * // {id: 2, active: true},\n * // {id: 3, active: true},\n * // {id: 4, active: false}\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf}\n * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys},\n * {@link module:lamb.pickIf|pickIf}\n * @since 0.35.0\n * @param {String[]} blacklist\n * @returns {Function}\n */\n var skipKeys = _curry2(skip, true);\n\n /**\n * Using the provided function to retrieve the keys of an object, builds\n * a function expecting an object to create an array containing a list\n * of the keys in its first index and the corresponding list of values\n * in the second one.\n * @private\n * @function\n * @param {Function} getKeys\n * @returns {Function}\n */\n var _tearFrom = _curry2(function (getKeys, obj) {\n return reduce(getKeys(obj), function (result, key) {\n result[0].push(key);\n result[1].push(obj[key]);\n\n return result;\n }, [[], []]);\n });\n\n /**\n * Tears an object apart by transforming it in an array of two lists: one containing\n * its enumerable keys, the other containing the corresponding values.
\n * Although this \"tearing apart\" may sound as a rather violent process, the source\n * object will be unharmed.\n * @example\n * _.tear({a: 1, b: 2, c: 3}) // => [[\"a\", \"b\", \"c\"], [1, 2, 3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.tearOwn|tearOwn}\n * @see {@link module:lamb.make|make} for the reverse operation\n * @since 0.8.0\n * @param {Object} obj\n * @returns {Array}\n */\n var tear = _tearFrom(enumerables);\n\n /**\n * Same as {@link module:lamb.tear|tear}, but only the own properties of the object are\n * taken into account.\n * @example Showing the difference with tear:\n * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}});\n * var foo = Object.create(baseFoo, {\n * c: {value: 3, enumerable: true}\n * });\n *\n * _.tear(foo) // => [[\"c\", \"b\", \"a\"], [3, 2, 1]]\n * _.tearOwn(foo) // => [[\"c\"], [3]]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.tear|tear}\n * @see {@link module:lamb.make|make} for the reverse operation\n * @since 0.12.0\n * @param {Object} obj\n * @returns {Array}\n */\n var tearOwn = _tearFrom(keys);\n\n /**\n * Creates a copy of the given object having the desired key value updated by applying\n * the provided function to it.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setIn|setIn}; a copy of the\n * source is returned otherwise.\n * @example\n * var user = {name: \"John\", visits: 2};\n * var toUpperCase = _.invoker(\"toUpperCase\");\n *\n * _.updateIn(user, \"name\", toUpperCase) // => {name: \"JOHN\", visits: 2}\n * _.updateIn(user, \"surname\", toUpperCase) // => {name: \"John\", visits: 2}\n *\n * @example Non-enumerable properties will be treated as non-existent:\n * var user = Object.create({name: \"John\"}, {visits: {value: 2}});\n *\n * _.updateIn(user, \"visits\", _.add(1)) // => {name: \"John\", visits: 2}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.updateKey|updateKey}\n * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn}\n * @since 0.22.0\n * @param {Object} source\n * @param {String} key\n * @param {Function} updater\n * @returns {Object}\n */\n function updateIn (source, key, updater) {\n return _isEnumerable(source, key) ?\n _setIn(source, key, updater(source[key])) :\n _merge(enumerables, source, {});\n }\n\n /**\n * Builds a partial application of {@link module:lamb.updateIn|updateIn} with the provided\n * key and updater, expecting the object to act upon.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setIn|setIn}; a copy of the\n * source is returned otherwise.\n * @example\n * var user = {name: \"John\", visits: 2};\n * var incrementVisits = _.updateKey(\"visits\", _.add(1));\n *\n * incrementVisits(user) // => {name: \"John\", visits: 3}\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.updateIn|updateIn}\n * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn}\n * @since 0.22.0\n * @param {String} key\n * @param {Function} updater\n * @returns {Function}\n */\n var updateKey = _makePartial3(updateIn);\n\n /**\n * Allows to change a nested value in a copy of the given object by applying the provided\n * function to it.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setPathIn|setPathIn}; a copy of the\n * source is returned otherwise.
\n * Like the other \"path\" functions, negative indexes can be used to access array elements, but\n * the priority will be given to existing, and enumerable, object keys.\n * @example\n * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}};\n * var inc = _.add(1);\n *\n * _.updatePathIn(user, \"status.visits\", inc) // => {id: 1, status: {scores: [2, 4, 6]}, visits: 1}\n *\n * @example Targeting arrays:\n * _.updatePathIn(user, \"status.scores.0\", inc) // => {id: 1, status: {scores: [3, 4, 6], visits: 0}}\n *\n * // you can use negative indexes as well\n * _.updatePathIn(user, \"status.scores.-1\", inc) // => {id: 1, status: {scores: [2, 4, 7], visits: 0}}\n *\n * @example Arrays can also be part of the path and not necessarily its target:\n * var user = {id: 1, scores: [\n * {value: 2, year: \"2000\"},\n * {value: 4, year: \"2001\"},\n * {value: 6, year: \"2002\"}\n * ]};\n *\n * var newUser = _.updatePathIn(user, \"scores.0.value\", inc);\n * // \"newUser\" holds:\n * // {id: 1, scores: [\n * // {value: 3, year: \"2000\"},\n * // {value: 4, year: \"2001\"},\n * // {value: 6, year: \"2002\"}\n * // ]}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.updatePath|updatePath}\n * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey}\n * @since 0.24.0\n * @param {Object|Array} source\n * @param {String} path\n * @param {Function} updater\n * @param {String} [separator=\".\"]\n * @returns {Object|Array}\n */\n function updatePathIn (source, path, updater, separator) {\n var parts = _toPathParts(path, separator);\n var pathInfo = _getPathInfo(source, parts, false);\n\n if (pathInfo.isValid) {\n return _setPathIn(source, parts, updater(pathInfo.target));\n } else {\n return Array.isArray(source) ? slice(source, 0, source.length) : _merge(enumerables, source, {});\n }\n }\n\n /**\n * Builds a partial application of {@link module:lamb.updatePathIn|updatePathIn}\n * expecting the object to act upon.
\n * This function is meant for updating existing enumerable properties, and for those it\n * will delegate the \"set action\" to {@link module:lamb.setPathIn|setPathIn}; a copy of the\n * source is returned otherwise.
\n * Like the other \"path\" functions, negative indexes can be used to access array elements, but\n * the priority will be given to existing, and enumerable, object keys.\n * @example\n * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}};\n * var incrementScores = _.updatePath(\"status.scores\", _.mapWith(_.add(1)))\n *\n * incrementScores(user) // => {id: 1, status: {scores: [3, 5, 7], visits: 0}}\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.updatePathIn|updatePathIn}\n * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey}\n * @since 0.24.0\n * @param {String} path\n * @param {Function} updater\n * @param {String} [separator=\".\"]\n * @returns {Function}\n */\n function updatePath (path, updater, separator) {\n return function (source) {\n return updatePathIn(source, path, updater, separator);\n };\n }\n\n /**\n * Validates an object with the given list of {@link module:lamb.checker|checker} functions.\n * @example\n * var hasContent = function (s) { return s.trim().length > 0; };\n * var userCheckers = [\n * _.checker(hasContent, \"Name is required\", [\"name\"]),\n * _.checker(hasContent, \"Surname is required\", [\"surname\"]),\n * _.checker(_.isGTE(18), \"Must be at least 18 years old\", [\"age\"])\n * ];\n *\n * var user1 = {name: \"john\", surname: \"doe\", age: 30};\n * var user2 = {name: \"jane\", surname: \"\", age: 15};\n *\n * _.validate(user1, userCheckers) // => []\n * _.validate(user2, userCheckers) // =>\n * // [\n * // [\"Surname is required\", [\"surname\"]],\n * // [\"Must be at least 18 years old\", [\"age\"]]\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @see {@link module:lamb.validateWith|validateWith}\n * @see {@link module:lamb.checker|checker}\n * @since 0.1.0\n * @param {Object} obj\n * @param {Function[]} checkers\n * @returns {Array>} An array of errors in the form returned by\n * {@link module:lamb.checker|checker}, or an empty array.\n */\n function validate (obj, checkers) {\n return reduce(checkers, function (errors, _checker) {\n var result = _checker(obj);\n\n result.length && errors.push(result);\n\n return errors;\n }, []);\n }\n\n /**\n * A curried version of {@link module:lamb.validate|validate} accepting a list of\n * {@link module:lamb.checker|checkers} and returning a function expecting the object to validate.\n * @example\n * var hasContent = function (s) { return s.trim().length > 0; };\n * var userCheckers = [\n * _.checker(hasContent, \"Name is required\", [\"name\"]),\n * _.checker(hasContent, \"Surname is required\", [\"surname\"]),\n * _.checker(_.isGTE(18), \"Must be at least 18 years old\", [\"age\"])\n * ];\n * var validateUser = _.validateWith(userCheckers);\n *\n * var user1 = {name: \"john\", surname: \"doe\", age: 30};\n * var user2 = {name: \"jane\", surname: \"\", age: 15};\n *\n * validateUser(user1) // => []\n * validateUser(user2) // =>\n * // [\n * // [\"Surname is required\", [\"surname\"]],\n * // [\"Must be at least 18 years old\", [\"age\"]]\n * // ]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.validate|validate}\n * @see {@link module:lamb.checker|checker}\n * @since 0.1.0\n * @param {Function[]} checkers\n * @returns {Function}\n */\n var validateWith = _curry2(validate, true);\n\n /**\n * Generates an array with the values of the enumerable properties of the given object.
\n * See also {@link module:lamb.ownValues|ownValues} to pick only from the own properties of the object.\n * @example\n * var user = {name: \"john\", surname: \"doe\", age: 30};\n *\n * _.values(user) // => [\"john\", \"doe\", 30]\n *\n * @memberof module:lamb\n * @category Object\n * @function\n * @see {@link module:lamb.ownValues|ownValues}\n * @since 0.1.0\n * @param {Object} obj\n * @returns {Array}\n */\n var values = _valuesFrom(enumerables);\n\n /**\n * A null-safe function to repeat the source string the desired amount of times.\n * @private\n * @param {String} source\n * @param {Number} times\n * @returns {String}\n */\n function _repeat (source, times) {\n var result = \"\";\n\n for (var i = 0; i < times; i++) {\n result += source;\n }\n\n return result;\n }\n\n /**\n * Builds the prefix or suffix to be used when padding a string.\n * @private\n * @param {String} source\n * @param {String} char\n * @param {Number} len\n * @returns {String}\n */\n function _getPadding (source, char, len) {\n if (!isNil(source) && type(source) !== \"String\") {\n source = String(source);\n }\n\n return _repeat(String(char)[0] || \"\", Math.ceil(len - source.length));\n }\n\n /**\n * Pads a string to the desired length with the given char starting from the beginning of the string.\n * @example\n * _.padLeft(\"foo\", \"-\", 0) // => \"foo\"\n * _.padLeft(\"foo\", \"-\", -1) // => \"foo\"\n * _.padLeft(\"foo\", \"-\", 5) // => \"--foo\"\n * _.padLeft(\"foo\", \"-\", 3) // => \"foo\"\n * _.padLeft(\"foo\", \"ab\", 7) // => \"aaaafoo\"\n * _.padLeft(\"foo\", \"\", 5) // => \"foo\"\n * _.padLeft(\"\", \"-\", 5) // => \"-----\"\n *\n * @memberof module:lamb\n * @category String\n * @see {@link module:lamb.padRight|padRight}\n * @since 0.1.0\n * @param {String} source\n * @param {String} char - The padding char. If a string is passed only the first char is used.\n * @param {Number} len\n * @returns {String}\n */\n function padLeft (source, char, len) {\n return _getPadding(source, char, len) + source;\n }\n\n /**\n * Pads a string to the desired length with the given char starting from the end of the string.\n * @example\n * _.padRight(\"foo\", \"-\", 0) // => \"foo\"\n * _.padRight(\"foo\", \"-\", -1) // => \"foo\"\n * _.padRight(\"foo\", \"-\", 5) // => \"foo--\"\n * _.padRight(\"foo\", \"-\", 3) // => \"foo\"\n * _.padRight(\"foo\", \"ab\", 7) // => \"fooaaaa\"\n * _.padRight(\"foo\", \"\", 5) // => \"foo\"\n * _.padRight(\"\", \"-\", 5) // => \"-----\"\n *\n * @memberof module:lamb\n * @category String\n * @see {@link module:lamb.padLeft|padLeft}\n * @since 0.1.0\n * @param {String} source\n * @param {String} char - The padding char. If a string is passed only the first char is used.\n * @param {Number} len\n * @returns {String}\n */\n function padRight (source, char, len) {\n return source + _getPadding(source, char, len);\n }\n\n /**\n * Builds a new string by repeating the source string the desired amount of times.
\n * Note that unlike the current ES6 proposal for\n * [String.prototype.repeat]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat},\n * this function doesn't throw a RangeError if times is negative,\n * but returns an empty string instead.\n * @example\n * _.repeat(\"Hello\", -1) // => \"\"\n * _.repeat(\"Hello\", 1) // => \"Hello\"\n * _.repeat(\"Hello\", 3) // => \"HelloHelloHello\"\n *\n * @memberof module:lamb\n * @category String\n * @since 0.1.0\n * @param {String} source\n * @param {Number} times\n * @returns {String}\n */\n function repeat (source, times) {\n if (isNil(source)) {\n throw _makeTypeErrorFor(source, \"string\");\n }\n\n return _repeat(source, Math.floor(times));\n }\n\n /**\n * A generic version of String.prototype.search\n * @private\n * @function\n * @param {String} s\n * @param {RegExp} pattern\n * @return {Number}\n */\n var _search = generic(String.prototype.search);\n\n /**\n * Builds a predicate expecting a string to test against the given regular expression pattern.\n * @example\n * var hasNumbersOnly = _.testWith(/^\\d+$/);\n *\n * hasNumbersOnly(\"123\") // => true\n * hasNumbersOnly(\"123 Kg\") // => false\n *\n * @memberof module:lamb\n * @category String\n * @since 0.1.0\n * @param {RegExp} pattern\n * @returns {Function}\n */\n function testWith (pattern) {\n return function (s) {\n return _search(s, pattern) !== -1;\n };\n }\n\n /**\n * Accepts a constructor and builds a predicate expecting an object,\n * which will be tested to verify whether the prototype of the constructor\n * is in its prototype chain.
\n * Wraps in a convenient way the native\n * [instanceof]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof} operator.\n * @example\n * function SomeObjA () {}\n *\n * var a = new SomeObjA();\n * var sObj = new String(\"foo\");\n * var s = \"foo\";\n *\n * _.isInstanceOf(Object)(a) // => true\n * _.isInstanceOf(SomeObjA)(a) // => true\n *\n * _.isInstanceOf(Object)(sObj) // => true\n * _.isInstanceOf(String)(sObj) // => true\n *\n * _.isInstanceOf(Object)(s) // => false\n * _.isInstanceOf(String)(s) // => false\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.isType|isType}\n * @since 0.47.0\n * @param {*} constructor\n * @returns {Function}\n */\n function isInstanceOf (constructor) {\n return function (obj) {\n return obj instanceof constructor;\n };\n }\n\n /**\n * Builds a predicate that expects a value to check against the specified type.\n * @example\n * var isString = _.isType(\"String\");\n *\n * isString(\"Hello\") // => true\n * isString(new String(\"Hi\")) // => true\n *\n * @memberof module:lamb\n * @category Type\n * @see {@link module:lamb.type|type}\n * @since 0.1.0\n * @param {String} typeName\n * @returns {Function}\n */\n function isType (typeName) {\n return function (value) {\n return type(value) === typeName;\n };\n }\n\n exports.__ = __;\n exports.always = always;\n exports.areSVZ = areSVZ;\n exports.binary = binary;\n exports.clamp = clamp;\n exports.clampWithin = clampWithin;\n exports.compose = compose;\n exports.forEach = forEach;\n exports.generic = generic;\n exports.identity = identity;\n exports.isNil = isNil;\n exports.isNull = isNull;\n exports.isSVZ = isSVZ;\n exports.isUndefined = isUndefined;\n exports.map = map;\n exports.mapWith = mapWith;\n exports.partial = partial;\n exports.partialRight = partialRight;\n exports.reduce = reduce;\n exports.reduceWith = reduceWith;\n exports.slice = slice;\n exports.sliceAt = sliceAt;\n exports.type = type;\n exports.append = append;\n exports.appendTo = appendTo;\n exports.contains = contains;\n exports.count = count;\n exports.countBy = countBy;\n exports.difference = difference;\n exports.drop = drop;\n exports.dropFrom = dropFrom;\n exports.dropWhile = dropWhile;\n exports.every = every;\n exports.everyIn = everyIn;\n exports.filter = filter;\n exports.filterWith = filterWith;\n exports.find = find;\n exports.findIndex = findIndex;\n exports.findWhere = findWhere;\n exports.findIndexWhere = findIndexWhere;\n exports.flatMap = flatMap;\n exports.flatMapWith = flatMapWith;\n exports.flatten = flatten;\n exports.getAt = getAt;\n exports.getIndex = getIndex;\n exports.group = group;\n exports.groupBy = groupBy;\n exports.head = head;\n exports.index = index;\n exports.indexBy = indexBy;\n exports.init = init;\n exports.insert = insert;\n exports.insertAt = insertAt;\n exports.intersection = intersection;\n exports.isIn = isIn;\n exports.last = last;\n exports.list = list;\n exports.partition = partition;\n exports.partitionWith = partitionWith;\n exports.pluck = pluck;\n exports.pluckKey = pluckKey;\n exports.pull = pull;\n exports.pullFrom = pullFrom;\n exports.reduceRight = reduceRight;\n exports.reduceRightWith = reduceRightWith;\n exports.reverse = reverse;\n exports.rotate = rotate;\n exports.rotateBy = rotateBy;\n exports.setAt = setAt;\n exports.setIndex = setIndex;\n exports.shallowFlatten = shallowFlatten;\n exports.some = some;\n exports.someIn = someIn;\n exports.sort = sort;\n exports.sortedInsert = sortedInsert;\n exports.sorter = sorter;\n exports.sorterDesc = sorterDesc;\n exports.sortWith = sortWith;\n exports.tail = tail;\n exports.take = take;\n exports.takeFrom = takeFrom;\n exports.takeWhile = takeWhile;\n exports.transpose = transpose;\n exports.union = union;\n exports.unionBy = unionBy;\n exports.uniques = uniques;\n exports.uniquesBy = uniquesBy;\n exports.updateAt = updateAt;\n exports.updateIndex = updateIndex;\n exports.zip = zip;\n exports.zipWithIndex = zipWithIndex;\n exports.application = application;\n exports.apply = apply;\n exports.applyTo = applyTo;\n exports.asPartial = asPartial;\n exports.aritize = aritize;\n exports.collect = collect;\n exports.curry = curry;\n exports.curryable = curryable;\n exports.curryableRight = curryableRight;\n exports.curryRight = curryRight;\n exports.debounce = debounce;\n exports.flip = flip;\n exports.getArgAt = getArgAt;\n exports.invoker = invoker;\n exports.invokerOn = invokerOn;\n exports.mapArgs = mapArgs;\n exports.pipe = pipe;\n exports.tapArgs = tapArgs;\n exports.throttle = throttle;\n exports.unary = unary;\n exports.adapter = adapter;\n exports.allOf = allOf;\n exports.anyOf = anyOf;\n exports.areSame = areSame;\n exports.case = case_;\n exports.condition = condition;\n exports.gt = gt;\n exports.gte = gte;\n exports.is = is;\n exports.isGT = isGT;\n exports.isGTE = isGTE;\n exports.isLT = isLT;\n exports.isLTE = isLTE;\n exports.lt = lt;\n exports.lte = lte;\n exports.not = not;\n exports.unless = unless;\n exports.when = when;\n exports.add = add;\n exports.deduct = deduct;\n exports.divide = divide;\n exports.divideBy = divideBy;\n exports.generate = generate;\n exports.isFinite = isFinite_;\n exports.isInteger = isInteger;\n exports.isSafeInteger = isSafeInteger;\n exports.modulo = modulo;\n exports.multiply = multiply;\n exports.multiplyBy = multiplyBy;\n exports.randomInt = randomInt;\n exports.range = range;\n exports.remainder = remainder;\n exports.subtract = subtract;\n exports.sum = sum;\n exports.checker = checker;\n exports.enumerables = enumerables;\n exports.fromPairs = fromPairs;\n exports.getIn = getIn;\n exports.getKey = getKey;\n exports.getPath = getPath;\n exports.getPathIn = getPathIn;\n exports.has = has;\n exports.hasKey = hasKey;\n exports.hasOwn = hasOwn;\n exports.hasOwnKey = hasOwnKey;\n exports.hasKeyValue = hasKeyValue;\n exports.hasPathValue = hasPathValue;\n exports.immutable = immutable;\n exports.keys = keys;\n exports.keySatisfies = keySatisfies;\n exports.make = make;\n exports.mapValues = mapValues;\n exports.mapValuesWith = mapValuesWith;\n exports.merge = merge;\n exports.mergeOwn = mergeOwn;\n exports.ownPairs = ownPairs;\n exports.ownValues = ownValues;\n exports.pairs = pairs;\n exports.pathExists = pathExists;\n exports.pathExistsIn = pathExistsIn;\n exports.pathSatisfies = pathSatisfies;\n exports.pick = pick;\n exports.pickIf = pickIf;\n exports.pickKeys = pickKeys;\n exports.rename = rename;\n exports.renameKeys = renameKeys;\n exports.renameWith = renameWith;\n exports.setIn = setIn;\n exports.setKey = setKey;\n exports.setPath = setPath;\n exports.setPathIn = setPathIn;\n exports.skip = skip;\n exports.skipIf = skipIf;\n exports.skipKeys = skipKeys;\n exports.tear = tear;\n exports.tearOwn = tearOwn;\n exports.updateIn = updateIn;\n exports.updateKey = updateKey;\n exports.updatePath = updatePath;\n exports.updatePathIn = updatePathIn;\n exports.validate = validate;\n exports.validateWith = validateWith;\n exports.values = values;\n exports.padLeft = padLeft;\n exports.padRight = padRight;\n exports.repeat = repeat;\n exports.testWith = testWith;\n exports.isInstanceOf = isInstanceOf;\n exports.isType = isType;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n}));\n"]} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 39c2903..e3437fd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,57 +1,59 @@ -var fs = require("fs"); +/* eslint-disable require-jsdoc */ + var gulp = require("gulp"); -var pkg = require("./package.json"); -var concat = require("gulp-concat"); var eslint = require("gulp-eslint"); -var footer = require("gulp-footer"); -var header = require("gulp-header"); -var indent = require("gulp-indent"); -var istanbul = require("gulp-istanbul"); -var jasmine = require("gulp-jasmine"); +var jest = require("gulp-jest").default; var rename = require("gulp-rename"); +var rollup = require("rollup"); var sourcemaps = require("gulp-sourcemaps"); var uglify = require("gulp-uglify"); -var scripts = [ - "./src/core.js", - "./src/privates.js", - "./src/array_basics.js", - "./src/logic.js", - "./src/math.js", - "./src/type.js", - "./src/accessors.js", - "./src/array.js", - "./src/grouping.js", - "./src/sort.js", - "./src/function.js", - "./src/object.js", - "./src/object_checking.js", - "./src/string.js" -]; +var jestBaseConfig = require("./jest.config"); +var pkg = require("./package.json"); -/* build */ +var intro = [ + "/**", + "* @overview " + pkg.name + " - " + pkg.description, + "* @author " + pkg.author.name + " <" + pkg.author.email + ">", + "* @version " + pkg.version, + "* @module lamb", + "* @license " + pkg.license, + "* @preserve", + "*/" +].join("\n"); + +/* env */ -gulp.task("concat", function () { - var intro = fs.readFileSync("./src/_intro.js", "utf8"); - var outro = fs.readFileSync("./src/_outro.js", "utf8"); +gulp.task("set-test-env", cb => { + process.env.NODE_ENV = "test"; - return gulp.src(scripts) - .pipe(concat("lamb.js"), {newLine: "\n"}) - .pipe(indent({tabs: false, amount: 4})) - .pipe(header(intro, {pkg: pkg})) - .pipe(footer(outro, {pkg: pkg})) - .pipe(gulp.dest("dist")); + return cb(); +}); + +/* build */ + +gulp.task("build", function () { + return rollup.rollup({ input: "src/index.js" }).then(function (bundle) { + return bundle.write({ + banner: intro, + exports: "named", + file: "dist/lamb.js", + format: "umd", + freeze: false, + name: "lamb", + sourcemap: false, + strict: true + }); + }); }); gulp.task("minify", gulp.series( - "concat", + "build", function () { return gulp.src("dist/lamb.js") .pipe(sourcemaps.init()) - .pipe(uglify({ - output: {comments: "some"} - })) - .pipe(rename({extname: ".min.js"})) + .pipe(uglify({ output: { comments: "some" } })) + .pipe(rename({ extname: ".min.js" })) .pipe(sourcemaps.write(".")) .pipe(gulp.dest("dist")); } @@ -65,54 +67,46 @@ function lintWith (settings) { .pipe(eslint(settings.configPath)) .pipe(eslint.format()) .pipe(eslint.failAfterError()); - } + }; } -gulp.task("lint:code", gulp.series("concat", lintWith({ +gulp.task("lint:code", lintWith({ configPath: ".eslintrc.json", - inputs: "./dist/lamb.js" -}))); + inputs: ["./*.js", "./src/**/*.js", "!./src/**/__{tests,mocks}__/**"] +})); gulp.task("lint:tests", lintWith({ configPath: ".eslintrc.test.json", - inputs: "./test/**" + inputs: "./src/**/__{tests,mocks}__/**/*.js" })); gulp.task("lint", gulp.series("lint:code", "lint:tests")); /* test */ -gulp.task("test", gulp.series("concat", function () { - return gulp.src("./test/spec/*.js") - .pipe(jasmine({ - includeStackTrace: true - })); -})); +function testWith (extraSettings) { + return gulp.series( + "set-test-env", + function () { + return gulp.src("./src").pipe(jest(Object.assign({}, jestBaseConfig, extraSettings))); + } + ); +} -gulp.task("test:coverage", gulp.series("concat", function (cb) { - gulp.src("./dist/lamb.js") - .pipe(istanbul()) - .pipe(istanbul.hookRequire()) - .on("finish", function () { - gulp.src("./test/spec/*.js") - .pipe(jasmine()) - .pipe(istanbul.writeReports()) - .on("end", cb); - }); -})); +gulp.task("test", testWith({})); -gulp.task("test:verbose", gulp.series("concat", function () { - return gulp.src("./test/spec/*.js") - .pipe(jasmine({ - includeStackTrace: true, - verbose: true - })); -})); +gulp.task("test:coverage", testWith({ collectCoverage: true })); + +gulp.task("test:verbose", testWith({ verbose: true })); + +gulp.task("test:travis", testWith({ collectCoverage: true, maxWorkers: 4 })); + +gulp.task("test:watch", testWith({ watch: true })); /* travis */ -gulp.task("travis", gulp.series("concat", "lint", "test", "minify")); +gulp.task("travis", gulp.series("lint", "test:travis")); /* default */ -gulp.task("default", gulp.series("concat", "lint", "test:coverage", "minify")); +gulp.task("default", gulp.series("lint", "test:coverage")); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..c28e7ba --- /dev/null +++ b/jest.config.js @@ -0,0 +1,19 @@ +module.exports = { + clearMocks: false, + resetMocks: false, + restoreMocks: false, + coverageDirectory: "coverage", + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100 + } + }, + collectCoverageFrom: ["src/**/*.js", "!**/{__tests__,__mocks__}/**"], + rootDir: ".", + testRegex: "(/__tests__/.+\\.(test|spec))\\.js$", + testURL: "http://localhost:3000/", + verbose: false +}; diff --git a/src/_outro.js b/jsdoc.typedefs.js similarity index 92% rename from src/_outro.js rename to jsdoc.typedefs.js index d3cd7ce..6ad92b7 100644 --- a/src/_outro.js +++ b/jsdoc.typedefs.js @@ -1,16 +1,4 @@ - /* istanbul ignore next */ - if (typeof exports === "object") { - module.exports = lamb; - } else if (typeof define === "function" && define.amd) { - define(function () { - return lamb; - }); - } else { - host.lamb = lamb; - } -})(this); - /** * @callback AccumulatorCallback * @global diff --git a/license.txt b/license.txt index b034664..7575273 100644 --- a/license.txt +++ b/license.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2018 Andrea Scartabelli +Copyright (c) 2015-2019 Andrea Scartabelli Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package-lock.json b/package-lock.json index 8de8a12..138b8ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1478 +1,2470 @@ { "name": "lamb", - "version": "0.57.0-alpha.3", + "version": "0.57.0-alpha.18", "lockfileVersion": 1, "requires": true, "dependencies": { - "@gulp-sourcemaps/identity-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", - "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "acorn": "^5.0.3", - "css": "^2.2.1", - "normalize-path": "^2.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.3" + "@babel/highlight": "^7.0.0" } }, - "@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "@babel/core": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", + "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", "dev": true, "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.2.2", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.2.2", + "@babel/types": "^7.2.2", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } } }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "acorn": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", - "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", - "dev": true - }, - "acorn-jsx": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", - "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", + "@babel/generator": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.2.tgz", + "integrity": "sha512-f3QCuPppXxtZOEm5GWPra/uYUjmNQlu9pbAD8D/9jze4pTY83rTtB1igTBSwvkeNlC5gR24zFFkz+2WHLFQhqQ==", "dev": true, "requires": { - "acorn": "^5.0.3" + "@babel/types": "^7.3.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "@babel/types": "^7.0.0" } }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", "dev": true, "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "@babel/helper-call-delegate": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", + "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", "dev": true, "requires": { - "ansi-wrap": "^0.1.0" + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "@babel/helper-define-map": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", + "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" } }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", - "dev": true - }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true + "@babel/helper-hoist-variables": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz", + "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "@babel/types": "^7.0.0" } }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", "dev": true, "requires": { - "buffer-equal": "^1.0.0" + "@babel/types": "^7.0.0" } }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true + "@babel/helper-module-transforms": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz", + "integrity": "sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.2.2", + "@babel/types": "^7.2.2", + "lodash": "^4.17.10" + } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "@babel/types": "^7.0.0" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", "dev": true }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", "dev": true, "requires": { - "make-iterator": "^1.0.0" + "lodash": "^4.17.10" } }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "@babel/helper-replace-supers": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz", + "integrity": "sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==", "dev": true, "requires": { - "make-iterator": "^1.0.0" + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.2.3", + "@babel/types": "^7.0.0" } }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "@babel/helpers": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", "dev": true, "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.3.0" } }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { - "is-number": "^4.0.0" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" }, "dependencies": { - "is-number": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "@babel/parser": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.2.tgz", + "integrity": "sha512-QzNUC2RO1gadg+fs21fi0Uu0OuGNzRKEmgCxoLNzbCdoprLwjfmZwzUrpUNfJPaVRwBpDY47A17yYEGWyRelnQ==", "dev": true }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", "dev": true, "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" } }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", "dev": true, "requires": { - "array-uniq": "^1.0.1" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz", + "integrity": "sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz", + "integrity": "sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.2.0" + } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "async-done": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.1.tgz", - "integrity": "sha512-R1BaUeJ4PMoLNJuk+0tLJgjmEqVsdN118+Z8O+alhnQDQgy0kmD5Mqi0DNEmMx2LM0Ed5yekKu+ZXYvIHceicg==", + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^1.0.7", - "stream-exhaust": "^1.0.1" - }, - "dependencies": { - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - } + "@babel/helper-plugin-utils": "^7.0.0" } }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "@babel/plugin-transform-async-to-generator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz", + "integrity": "sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==", "dev": true, "requires": { - "async-done": "^1.2.2" + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" } }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true + "@babel/plugin-transform-block-scoping": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", + "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.10" + } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "@babel/plugin-transform-classes": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz", + "integrity": "sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + } }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", - "dev": true + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "@babel/plugin-transform-destructuring": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz", + "integrity": "sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw==", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "@babel/plugin-transform-dotall-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz", + "integrity": "sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==", "dev": true, "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" } }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", "dev": true, "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "@babel/plugin-transform-for-of": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz", + "integrity": "sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==", "dev": true, - "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true + "@babel/plugin-transform-function-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz", + "integrity": "sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true + "@babel/plugin-transform-modules-commonjs": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz", + "integrity": "sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "@babel/plugin-transform-modules-systemjs": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz", + "integrity": "sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", "dev": true, "requires": { - "callsites": "^0.2.0" + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz", + "integrity": "sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==", + "dev": true, + "requires": { + "regexp-tree": "^0.1.0" + } }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "@babel/plugin-transform-new-target": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", + "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==", "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "@babel/plugin-transform-parameters": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz", + "integrity": "sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==", "dev": true, - "optional": true, "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "@babel/helper-call-delegate": "^7.1.0", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "@babel/plugin-transform-regenerator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz", + "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "regenerator-transform": "^0.13.3" } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "@babel/plugin-transform-template-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz", + "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true + "@babel/plugin-transform-unicode-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz", + "integrity": "sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "@babel/preset-env": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.3.1.tgz", + "integrity": "sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ==", "dev": true, - "optional": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.2.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.2.0", + "@babel/plugin-transform-classes": "^7.2.0", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.2.0", + "@babel/plugin-transform-function-name": "^7.2.0", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.2.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.3.0", + "@babel/plugin-transform-new-target": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.2.0", + "browserslist": "^4.3.4", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" }, "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true } } }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" + } }, - "cloneable-readable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", - "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "@babel/traverse": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", "dev": true, "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "ms": "^2.1.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "@babel/types": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.2.tgz", + "integrity": "sha512-3Y6H8xlUlpbGR+XvawiH0UXehqydTmNmEpozWcXymqwcrwYAl5KMvKtQ+TF6f6E08V6Jur7v/ykdDSF+WDEIXQ==", "dev": true, "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" } }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "@gulp-sourcemaps/identity-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", + "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "acorn": "^5.0.3", + "css": "^2.2.1", + "normalize-path": "^2.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.3" } }, - "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", "dev": true, "requires": { - "color-name": "1.1.1" + "normalize-path": "^2.0.1", + "through2": "^2.0.3" } }, - "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "@types/node": { + "version": "10.12.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.24.tgz", + "integrity": "sha512-GWWbvt+z9G5otRBW8rssOFgRY87J9N/qbhqfjMZ+gUuL6zoL+Hm6gP/8qQBG4jjimqdaNLCehcVapZ/Fs2WjCQ==", "dev": true }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "acorn-globals": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", + "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "acorn": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", + "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } } } }, - "concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "acorn-jsx": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "acorn": "^5.0.3" } }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", "dev": true }, - "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, - "coveralls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.2.tgz", - "integrity": "sha512-Tv0LKe/MkBOilH2v7WBiTBdudg2ChfGbdXafc/s330djpF3zKOmuehTeRwjXWc7pzfj9FrDUTA7tEx6Div8NFw==", + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { - "growl": "~> 1.10.0", - "js-yaml": "^3.11.0", - "lcov-parse": "^0.0.10", - "log-driver": "^1.2.7", - "minimist": "^1.2.0", - "request": "^2.85.0" + "ansi-wrap": "^0.1.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - } + "ansi-wrap": "0.1.0" } }, - "css": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.3.tgz", - "integrity": "sha512-0W171WccAjQGGTKLhw4m2nnl0zPHUlTO/I8td4XzJgIB8Hg3ZZx71qT4G4eX8OVsSiaAKiUMy73E3nsbPlg2DQ==", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "inherits": "^2.0.1", - "source-map": "^0.1.38", - "source-map-resolve": "^0.5.1", - "urix": "^0.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - } + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, "requires": { - "array-find-index": "^1.0.1" + "buffer-equal": "^1.0.0" } }, - "d": { + "append-transform": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { - "es5-ext": "^0.10.9" + "default-require-extensions": "^2.0.0" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "assert-plus": "^1.0.0" + "sprintf-js": "~1.0.2" } }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", "dev": true, "requires": { - "ms": "2.0.0" + "make-iterator": "^1.0.0" } }, - "debug-fabulous": { + "arr-flatten": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", "dev": true, "requires": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - } + "make-iterator": "^1.0.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", "dev": true, "requires": { - "kind-of": "^5.0.2" + "array-slice": "^1.0.0", + "is-number": "^4.0.0" }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true } } }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, - "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "is-number": "^4.0.0" }, "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true } } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" }, "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "readable-stream": "~1.1.9" + "lodash": "^4.17.10" } }, - "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "async-done": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.1.tgz", + "integrity": "sha512-R1BaUeJ4PMoLNJuk+0tLJgjmEqVsdN118+Z8O+alhnQDQgy0kmD5Mqi0DNEmMx2LM0Ed5yekKu+ZXYvIHceicg==", "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^1.0.7", + "stream-exhaust": "^1.0.1" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } } } }, - "each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", "dev": true, "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" + "async-done": "^1.2.2" } }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, - "optional": true, "requires": { - "jsbn": "~0.1.0" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" } }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "babel-jest": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.1.0.tgz", + "integrity": "sha512-MLcagnVrO9ybQGLEfZUqnOzv36iQzU7Bj4elm39vCukumLVSfoX+tRy3/jW7lUKc7XdpRmB/jech6L/UCsSZjw==", "dev": true, "requires": { - "once": "^1.4.0" + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.1.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" }, "dependencies": { - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "wrappy": "1" + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, - "requires": { - "is-callable": "^1.1.1", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" - } - }, - "es5-ext": { - "version": "0.10.42", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", - "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", + "babel-plugin-istanbul": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.0.tgz", + "integrity": "sha512-CLoXPRSUWiR8yao8bShqZUIC6qLfZVVY3X1wj+QPNXu0wfmrRRfarh1LYy+dYMVI+bDj0ghy3tuqFFRFZmL1Nw==", "dev": true, "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.0.0", + "test-exclude": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } } }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } + "babel-plugin-jest-hoist": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.1.0.tgz", + "integrity": "sha512-gljYrZz8w1b6fJzKcsfKsipSru2DU2DmQ39aB6nV3xQ0DDv3zpIzKGortA5gknrhNnPN8DweaEgrnZdmbGmhnw==", + "dev": true }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "babel-preset-jest": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.1.0.tgz", + "integrity": "sha512-FfNLDxFWsNX9lUmtwY7NheGlANnagvxq8LZdl5PKnVG3umP+S/g0XbVBfwtA4Ai3Ri/IMkWabBz3Tyk9wdspcw==", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.1.0" } }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, - "optional": true, "requires": { - "amdefine": ">=0.0.4" + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } }, - "eslint": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.1.0.tgz", - "integrity": "sha512-DyH6JsoA1KzA5+OSWFjg56DFJT+sDLO0yokaPZ9qY0UEmYrPA1gEX/G1MnVkmRDsksG4H1foIVz2ZXXM3hHYvw==", + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, + "optional": true, "requires": { - "ajv": "^6.5.0", - "babel-code-frame": "^6.26.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserslist": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", + "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000929", + "electron-to-chromium": "^1.3.103", + "node-releases": "^1.1.3" + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000936", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz", + "integrity": "sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw==", + "dev": true + }, + "capture-exit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", + "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", + "dev": true, + "requires": { + "rsvp": "^3.3.3" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", + "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "coveralls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.2.tgz", + "integrity": "sha512-Tv0LKe/MkBOilH2v7WBiTBdudg2ChfGbdXafc/s330djpF3zKOmuehTeRwjXWc7pzfj9FrDUTA7tEx6Div8NFw==", + "dev": true, + "requires": { + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.85.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } + } + }, + "css": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.3.tgz", + "integrity": "sha512-0W171WccAjQGGTKLhw4m2nnl0zPHUlTO/I8td4XzJgIB8Hg3ZZx71qT4G4eX8OVsSiaAKiUMy73E3nsbPlg2DQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "source-map": "^0.1.38", + "source-map-resolve": "^0.5.1", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true + }, + "cssstyle": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", + "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "^2.0.5", + "object-keys": "^1.0.8" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff-sequences": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.0.0.tgz", + "integrity": "sha512-46OkIuVGBBnrC0soO/4LHu5LHGHx0uhP65OVz8XOrAJpqiCB2aVIuESvjI1F9oqebuvY8lekS1pt6TN7vt7qsw==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "duplexify": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "electron-to-chromium": { + "version": "1.3.113", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz", + "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + }, + "dependencies": { + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + } + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" + } + }, + "es5-ext": { + "version": "0.10.42", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", + "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "eslint": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.1.0.tgz", + "integrity": "sha512-DyH6JsoA1KzA5+OSWFjg56DFJT+sDLO0yokaPZ9qY0UEmYrPA1gEX/G1MnVkmRDsksG4H1foIVz2ZXXM3hHYvw==", + "dev": true, + "requires": { + "ajv": "^6.5.0", + "babel-code-frame": "^6.26.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", "globals": "^11.7.0", "ignore": "^3.3.3", "imurmurhash": "^0.1.4", @@ -1699,21 +2691,36 @@ "es5-ext": "~0.10.14" } }, - "event-stream": { - "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "exec-sh": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", + "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", + "dev": true, + "requires": { + "merge": "^1.2.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -1758,6 +2765,30 @@ "homedir-polyfill": "^1.0.1" } }, + "expect": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.1.0.tgz", + "integrity": "sha512-lVcAPhaYkQcIyMS+F8RVwzbm1jro20IG8OkvxQ6f1JfqhVZyyudCwYogQ7wnktlf14iF3ii7ArIUO/mqvrW9Gw==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-regex-util": "^24.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -1896,6 +2927,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "^2.0.0" + } + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -1923,6 +2963,16 @@ } } }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -2107,12 +3157,6 @@ "map-cache": "^0.2.2" } }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -2149,7 +3193,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2170,12 +3215,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2190,17 +3237,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2317,7 +3367,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2329,6 +3380,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2343,6 +3395,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2350,12 +3403,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2374,6 +3429,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2454,7 +3510,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2466,6 +3523,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2551,7 +3609,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2587,6 +3646,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2606,6 +3666,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2649,12 +3710,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -2676,11 +3739,26 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } }, "get-value": { "version": "2.0.6", @@ -2901,6 +3979,12 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, "gulp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.0.tgz", @@ -2999,678 +4083,928 @@ } } }, - "gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "gulp-eslint": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", + "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", + "dev": true, + "requires": { + "eslint": "^5.0.1", + "fancy-log": "^1.3.2", + "plugin-error": "^1.0.1" + } + }, + "gulp-jest": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp-jest/-/gulp-jest-4.0.2.tgz", + "integrity": "sha512-+ir9/nZCQQDwjBC4XZObJ8Xx1Ks5bMFLYnJbGL+67Bx1y8uclMMFk8USMM2zuR7yu4yg5GwRuG5he4JthMxVlw==", + "dev": true, + "requires": { + "plugin-error": "^1.0.1", + "through2": "^2.0.1" + } + }, + "gulp-rename": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", + "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "dev": true + }, + "gulp-sourcemaps": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz", + "integrity": "sha1-y7IAhFCxvM5s0jv5gze+dRv24wo=", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.X", + "@gulp-sourcemaps/map-sources": "1.X", + "acorn": "5.X", + "convert-source-map": "1.X", + "css": "2.X", + "debug-fabulous": "1.X", + "detect-newline": "2.X", + "graceful-fs": "4.X", + "source-map": "~0.6.0", + "strip-bom-string": "1.X", + "through2": "2.X" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "gulp-uglify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.1.tgz", + "integrity": "sha512-KVffbGY9d4Wv90bW/B1KZJyunLMyfHTBbilpDvmcrj5Go0/a1G3uVpt+1gRBWSw/11dqR3coJ1oWNTt1AiXuWQ==", + "dev": true, + "requires": { + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash": "^4.13.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.8.tgz", + "integrity": "sha512-WatYTD84gP/867bELqI2F/2xC9PQBETn/L+7RGq9MQOA/7yFBNvY1UwXqvtILeE6n0ITwBXxp34M0/o70dzj6A==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + } + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "handlebars": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", "dev": true, "requires": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" + "sparkles": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "vinyl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", - "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "is-buffer": "^1.1.5" } } } }, - "gulp-eslint": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "dev": true, "requires": { - "eslint": "^5.0.1", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1" + "parse-passwd": "^1.0.0" } }, - "gulp-footer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-2.0.1.tgz", - "integrity": "sha512-leXmoTEwXjklKuIE+8xGhuqlN/OdWubnkn3W/jNzmxxYbzQVjCtl2XI5iZztgPKLZ+Mh1t+7tTTGyVQqZva4+A==", + "hosted-git-info": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", "dev": true, "requires": { - "event-stream": "*", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.6.2" + "whatwg-encoding": "^1.0.1" } }, - "gulp-header": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-2.0.5.tgz", - "integrity": "sha512-7bOIiHvM1GUHIG3LRH+UIanOxyjSys0FbzzgUBlV2cZIIZihEW+KKKKm0ejUBNGvRdhISEFFr6HlptXoa28gtQ==", + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "concat-with-sourcemaps": "*", - "lodash.template": "^4.4.0", - "through2": "^2.0.0" - }, - "dependencies": { - "lodash.template": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", - "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", - "dev": true, - "requires": { - "lodash._reinterpolate": "~3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "lodash.templatesettings": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", - "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", - "dev": true, - "requires": { - "lodash._reinterpolate": "~3.0.0" - } - } + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, - "gulp-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gulp-indent/-/gulp-indent-1.0.1.tgz", - "integrity": "sha1-SudmS69Ut6bA1cOh1I2v0Qk44lU=", + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", "dev": true, "requires": { - "gulp-util": "~2.2.0", - "through2": "*" + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.1.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^5.5.2", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", - "dev": true - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "dev": true, - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - } - }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - } - }, - "gulp-util": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", - "dev": true, - "requires": { - "chalk": "^0.5.0", - "dateformat": "^1.0.7-1.2.3", - "lodash._reinterpolate": "^2.4.1", - "lodash.template": "^2.4.1", - "minimist": "^0.2.0", - "multipipe": "^0.1.0", - "through2": "^0.5.0", - "vinyl": "^0.2.1" - }, - "dependencies": { - "through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", - "dev": true, - "requires": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" - } - } - } - }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "dev": true, - "requires": { - "ansi-regex": "^0.2.0" - } - }, - "lodash._reinterpolate": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", - "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", - "dev": true - }, - "lodash.escape": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", - "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", - "dev": true, - "requires": { - "lodash._escapehtmlchar": "~2.4.1", - "lodash._reunescapedhtml": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - }, - "lodash.template": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "lodash._escapestringchar": "~2.4.1", - "lodash._reinterpolate": "~2.4.1", - "lodash.defaults": "~2.4.1", - "lodash.escape": "~2.4.1", - "lodash.keys": "~2.4.1", - "lodash.templatesettings": "~2.4.1", - "lodash.values": "~2.4.1" + "color-convert": "^1.9.0" } }, - "lodash.templatesettings": { + "chalk": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", - "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "lodash._reinterpolate": "~2.4.1", - "lodash.escape": "~2.4.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "minimist": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", - "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "dev": true }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^0.2.1" + "ansi-regex": "^3.0.0" } }, "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true - }, - "vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "clone-stats": "~0.0.1" + "has-flag": "^3.0.0" } - }, - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", - "dev": true } } }, - "gulp-istanbul": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gulp-istanbul/-/gulp-istanbul-1.1.3.tgz", - "integrity": "sha512-uMLSdqPDnBAV/B9rNyOgVMgrVC1tPbe+5GH6P13UOyxbRDT/w4sKYHWftPMA8j9om+NFvfeRlqpDXL2fixFWNA==", + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "istanbul": "^0.4.0", - "istanbul-threshold-checker": "^0.2.1", - "lodash": "^4.0.0", - "plugin-error": "^0.1.2", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.1" + "kind-of": "^3.0.2" }, "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" + "is-buffer": "^1.1.5" } } } }, - "gulp-jasmine": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/gulp-jasmine/-/gulp-jasmine-4.0.0.tgz", - "integrity": "sha512-0UqY2fA6RCdUDJDsVym3zXYSWmt0AV7YY/6PAeKb+oGTKEgS7zZOH5w/4gcSKs+2FXiWrucQwLDvtEKIDbpF4A==", + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "arrify": "^1.0.1", - "jasmine": "^3.1.0", - "jasmine-terminal-reporter": "^1.0.3", - "plugin-error": "^1.0.1", - "through2": "^2.0.3" + "binary-extensions": "^1.0.0" } }, - "gulp-rename": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "gulp-sourcemaps": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz", - "integrity": "sha1-y7IAhFCxvM5s0jv5gze+dRv24wo=", + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "@gulp-sourcemaps/identity-map": "1.X", - "@gulp-sourcemaps/map-sources": "1.X", - "acorn": "5.X", - "convert-source-map": "1.X", - "css": "2.X", - "debug-fabulous": "1.X", - "detect-newline": "2.X", - "graceful-fs": "4.X", - "source-map": "~0.6.0", - "strip-bom-string": "1.X", - "through2": "2.X" + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" }, "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } } } }, - "gulp-uglify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.1.tgz", - "integrity": "sha512-KVffbGY9d4Wv90bW/B1KZJyunLMyfHTBbilpDvmcrj5Go0/a1G3uVpt+1gRBWSw/11dqR3coJ1oWNTt1AiXuWQ==", + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash": "^4.13.1", - "make-error-cause": "^1.1.1", - "safe-buffer": "^5.1.2", - "through2": "^2.0.0", - "uglify-js": "^3.0.5", - "vinyl-sourcemaps-apply": "^0.2.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true - }, - "uglify-js": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.8.tgz", - "integrity": "sha512-WatYTD84gP/867bELqI2F/2xC9PQBETn/L+7RGq9MQOA/7yFBNvY1UwXqvtILeE6n0ITwBXxp34M0/o70dzj6A==", - "dev": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - } } } }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-generator-fn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.0.0.tgz", + "integrity": "sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g==", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "glogg": "^1.0.0" + "is-extglob": "^2.1.1" } }, - "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "async": "^1.4.0", - "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "kind-of": "^3.0.2" }, "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "is-buffer": "^1.1.5" } } } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", "dev": true }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" + "path-is-inside": "^1.0.1" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "isobject": "^3.0.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "has": "^1.0.1" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "sparkles": "^1.0.0" + "is-unc-path": "^1.0.0" } }, - "has-symbols": { + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, + "is-typedarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "has-value": { + "is-unc-path": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "unc-path-regex": "^0.1.2" } }, - "has-values": { + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.0.tgz", + "integrity": "sha512-+Ygg4t1StoiNlBGc6x0f8q/Bv26FbZqP/+jegzfNpU7Q8o+4ZRoJxJPhBkgE/UonpAjtxnE4zCZIyJX+MwLRMQ==", + "dev": true, + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.0", + "js-yaml": "^3.12.0", + "make-dir": "^1.3.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" }, "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "js-yaml": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" } } } }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, - "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", "dev": true }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "istanbul-lib-hook": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", + "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", "dev": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "append-transform": "^1.0.0" } }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } } }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "istanbul-lib-report": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", + "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", "dev": true, "requires": { - "repeating": "^2.0.0" + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "istanbul-lib-source-maps": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", + "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" + "istanbul-reports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.0.tgz", + "integrity": "sha512-azQdSX+dtTtkQEfqq20ICxWi6eOHXyHIgMFw1VOOVi8iIPWeCWRgCyFh/CsBKIhcgskMI8ExXmU7rjXTRCIJ+A==", + "dev": true, + "requires": { + "handlebars": "^4.0.11" + } + }, + "jest-changed-files": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.0.0.tgz", + "integrity": "sha512-nnuU510R9U+UX0WNb5XFEcsrMqriSiRLeO9KWDFgPrpToaQm60prfQYpxsXigdClpvNot5bekDY440x9dNGnsQ==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-cli": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.1.0.tgz", + "integrity": "sha512-U/iyWPwOI0T1CIxVLtk/2uviOTJ/OiSWJSe8qt6X1VkbbgP+nrtLJlmT9lPBe4lK78VNFJtrJ7pttcNv/s7yCw==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.1.15", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "istanbul-api": "^2.0.8", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-source-maps": "^3.0.1", + "jest-changed-files": "^24.0.0", + "jest-config": "^24.1.0", + "jest-environment-jsdom": "^24.0.0", + "jest-get-type": "^24.0.0", + "jest-haste-map": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-regex-util": "^24.0.0", + "jest-resolve-dependencies": "^24.1.0", + "jest-runner": "^24.1.0", + "jest-runtime": "^24.1.0", + "jest-snapshot": "^24.1.0", + "jest-util": "^24.0.0", + "jest-validate": "^24.0.0", + "jest-watcher": "^24.0.0", + "jest-worker": "^24.0.0", + "micromatch": "^3.1.10", + "node-notifier": "^5.2.1", + "p-each-series": "^1.0.0", + "pirates": "^4.0.0", + "prompts": "^2.0.1", + "realpath-native": "^1.0.0", + "rimraf": "^2.5.4", + "slash": "^2.0.0", + "string-length": "^2.0.0", + "strip-ansi": "^5.0.0", + "which": "^1.2.12", + "yargs": "^12.0.2" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", "dev": true }, "ansi-styles": { @@ -3683,9 +5017,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -3693,25 +5027,25 @@ "supports-color": "^5.3.0" } }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.0.0" } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -3719,458 +5053,783 @@ } } }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "jest-config": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.1.0.tgz", + "integrity": "sha512-FbbRzRqtFC6eGjG5VwsbW4E5dW3zqJKLWYiZWhB0/4E5fgsMw8GODLbGSrY5t17kKOtCWb/Z7nsIThRoDpuVyg==", "dev": true, "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "@babel/core": "^7.1.0", + "babel-jest": "^24.1.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.0.0", + "jest-environment-node": "^24.0.0", + "jest-get-type": "^24.0.0", + "jest-jasmine2": "^24.1.0", + "jest-regex-util": "^24.0.0", + "jest-resolve": "^24.1.0", + "jest-util": "^24.0.0", + "jest-validate": "^24.0.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.0.0", + "realpath-native": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "jest-diff": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.0.0.tgz", + "integrity": "sha512-XY5wMpRaTsuMoU+1/B2zQSKQ9RdE9gsLkGydx3nvApeyPijLA8GtEvIcPwISRCer+VDf9W1mStTYYq6fPt8ryA==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "chalk": "^2.0.1", + "diff-sequences": "^24.0.0", + "jest-get-type": "^24.0.0", + "pretty-format": "^24.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "jest-docblock": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.0.0.tgz", + "integrity": "sha512-KfAKZ4SN7CFOZpWg4i7g7MSlY0M+mq7K0aMqENaG2vHuhC9fc3vkpU/iNN9sOus7v3h3Y48uEjqz3+Gdn2iptA==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "jest-each": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.0.0.tgz", + "integrity": "sha512-gFcbY4Cu55yxExXMkjrnLXov3bWO3dbPAW7HXb31h/DNWdNc/6X8MtxGff8nh3/MjkF9DpVqnj0KsPKuPK0cpA==", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "chalk": "^2.0.1", + "jest-get-type": "^24.0.0", + "jest-util": "^24.0.0", + "pretty-format": "^24.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "jest-environment-jsdom": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.0.0.tgz", + "integrity": "sha512-1YNp7xtxajTRaxbylDc2pWvFnfDTH5BJJGyVzyGAKNt/lEULohwEV9zFqTgG4bXRcq7xzdd+sGFws+LxThXXOw==", + "dev": true, + "requires": { + "jest-mock": "^24.0.0", + "jest-util": "^24.0.0", + "jsdom": "^11.5.1" + } }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "jest-environment-node": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.0.0.tgz", + "integrity": "sha512-62fOFcaEdU0VLaq8JL90TqwI7hLn0cOKOl8vY2n477vRkCJRojiRRtJVRzzCcgFvs6gqU97DNqX5R0BrBP6Rxg==", "dev": true, "requires": { - "builtin-modules": "^1.0.0" + "jest-mock": "^24.0.0", + "jest-util": "^24.0.0" } }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "jest-get-type": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.0.0.tgz", + "integrity": "sha512-z6/Eyf6s9ZDGz7eOvl+fzpuJmN9i0KyTt1no37/dHu8galssxz5ZEgnc1KaV8R31q1khxyhB4ui/X5ZjjPk77w==", "dev": true }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "jest-haste-map": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.0.0.tgz", + "integrity": "sha512-CcViJyUo41IQqttLxXVdI41YErkzBKbE6cS6dRAploCeutePYfUimWd3C9rQEWhX0YBOQzvNsC0O9nYxK2nnxQ==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.0.0", + "jest-util": "^24.0.0", + "jest-worker": "^24.0.0", + "micromatch": "^3.1.10", + "sane": "^3.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true } } }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "jest-jasmine2": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.1.0.tgz", + "integrity": "sha512-H+o76SdSNyCh9fM5K8upK45YTo/DiFx5w2YAzblQebSQmukDcoVBVeXynyr7DDnxh+0NTHYRCLwJVf3tC518wg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "@babel/traverse": "^7.1.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.1.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-snapshot": "^24.1.0", + "jest-util": "^24.0.0", + "pretty-format": "^24.0.0", + "throat": "^4.0.0" }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "jest-leak-detector": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.0.0.tgz", + "integrity": "sha512-ZYHJYFeibxfsDSKowjDP332pStuiFT2xfc5R67Rjm/l+HFJWJgNIOCOlQGeXLCtyUn3A23+VVDdiCcnB6dTTrg==", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "pretty-format": "^24.0.0" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "jest-matcher-utils": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.0.0.tgz", + "integrity": "sha512-LQTDmO+aWRz1Tf9HJg+HlPHhDh1E1c65kVwRFo5mwCVp5aQDzlkz4+vCvXhOKFjitV2f0kMdHxnODrXVoi+rlA==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "chalk": "^2.0.1", + "jest-diff": "^24.0.0", + "jest-get-type": "^24.0.0", + "pretty-format": "^24.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "jest-message-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", + "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "@babel/code-frame": "^7.0.0", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "jest-mock": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.0.0.tgz", + "integrity": "sha512-sQp0Hu5fcf5NZEh1U9eIW2qD0BwJZjb63Yqd98PQJFvf/zzUTBoUAwv/Dc/HFeNHIw1f3hl/48vNn+j3STaI7A==", "dev": true }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } + "jest-regex-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.0.0.tgz", + "integrity": "sha512-Jv/uOTCuC+PY7WpJl2mpoI+WbY2ut73qwwO9ByJJNwOCwr1qWhEW2Lyi2S9ZewUdJqeVpEBisdEVZSI+Zxo58Q==", + "dev": true }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "jest-resolve": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.1.0.tgz", + "integrity": "sha512-TPiAIVp3TG6zAxH28u/6eogbwrvZjBMWroSLBDkwkHKrqxB/RIdwkWDye4uqPlZIXWIaHtifY3L0/eO5Z0f2wg==", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "realpath-native": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "jest-resolve-dependencies": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.1.0.tgz", + "integrity": "sha512-2VwPsjd3kRPu7qe2cpytAgowCObk5AKeizfXuuiwgm1a9sijJDZe8Kh1sFj6FKvSaNEfCPlBVkZEJa2482m/Uw==", "dev": true, "requires": { - "isobject": "^3.0.1" + "jest-regex-util": "^24.0.0", + "jest-snapshot": "^24.1.0" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "jest-runner": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.1.0.tgz", + "integrity": "sha512-CDGOkT3AIFl16BLL/OdbtYgYvbAprwJ+ExKuLZmGSCSldwsuU2dEGauqkpvd9nphVdAnJUcP12e/EIlnTX0QXg==", "dev": true, "requires": { - "has": "^1.0.1" + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.1.0", + "jest-docblock": "^24.0.0", + "jest-haste-map": "^24.0.0", + "jest-jasmine2": "^24.1.0", + "jest-leak-detector": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-runtime": "^24.1.0", + "jest-util": "^24.0.0", + "jest-worker": "^24.0.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "jest-runtime": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.1.0.tgz", + "integrity": "sha512-59/BY6OCuTXxGeDhEMU7+N33dpMQyXq7MLK07cNSIY/QYt2QZgJ7Tjx+rykBI0skAoigFl0A5tmT8UdwX92YuQ==", "dev": true, "requires": { - "is-unc-path": "^1.0.0" + "@babel/core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "exit": "^0.1.2", + "fast-json-stable-stringify": "^2.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.1.0", + "jest-haste-map": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-regex-util": "^24.0.0", + "jest-resolve": "^24.1.0", + "jest-snapshot": "^24.1.0", + "jest-util": "^24.0.0", + "jest-validate": "^24.0.0", + "micromatch": "^3.1.10", + "realpath-native": "^1.0.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "write-file-atomic": "2.4.1", + "yargs": "^12.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "jest-serializer": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.0.0.tgz", + "integrity": "sha512-9FKxQyrFgHtx3ozU+1a8v938ILBE7S8Ko3uiAVjT8Yfi2o91j/fj81jacCQZ/Ihjiff/VsUCXVgQ+iF1XdImOw==", "dev": true }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "jest-snapshot": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.1.0.tgz", + "integrity": "sha512-th6TDfFqEmXvuViacU1ikD7xFb7lQsPn2rJl7OEmnfIVpnrx3QNY2t3PE88meeg0u/mQ0nkyvmC05PBqO4USFA==", "dev": true, "requires": { - "unc-path-regex": "^0.1.2" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "@babel/types": "^7.0.0", + "chalk": "^2.0.1", + "jest-diff": "^24.0.0", + "jest-matcher-utils": "^24.0.0", + "jest-message-util": "^24.0.0", + "jest-resolve": "^24.1.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" + "jest-util": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", + "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "jest-message-util": "^24.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" }, "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, - "istanbul-threshold-checker": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/istanbul-threshold-checker/-/istanbul-threshold-checker-0.2.1.tgz", - "integrity": "sha1-xdyU6PLMXNP/0zVFL4S1U8QkgzE=", + "jest-validate": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.0.0.tgz", + "integrity": "sha512-vMrKrTOP4BBFIeOWsjpsDgVXATxCspC9S1gqvbJ3Tnn/b9ACsJmteYeVx9830UMV28Cob1RX55x96Qq3Tfad4g==", "dev": true, "requires": { - "istanbul": "~0.4.5", - "lodash": "~4.17.2" + "camelcase": "^5.0.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.0.0", + "leven": "^2.1.0", + "pretty-format": "^24.0.0" }, "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "jasmine": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.1.0.tgz", - "integrity": "sha1-K9Wf1+xuwOistk4J9Fpo7SrRlSo=", + "jest-watcher": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.0.0.tgz", + "integrity": "sha512-GxkW2QrZ4YxmW1GUWER05McjVDunBlKMFfExu+VsGmXJmpej1saTEKvONdx5RJBlVdpPI5x6E3+EDQSIGgl53g==", "dev": true, "requires": { - "glob": "^7.0.6", - "jasmine-core": "~3.1.0" + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.0.0", + "string-length": "^2.0.0" }, "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "color-convert": "^1.9.0" } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, - "jasmine-core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.1.0.tgz", - "integrity": "sha1-pHheE11d9lAk38kiSVPfWFvSdmw=", - "dev": true - }, - "jasmine-terminal-reporter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/jasmine-terminal-reporter/-/jasmine-terminal-reporter-1.0.3.tgz", - "integrity": "sha1-iW8eyP30v2rs3UHFA+2nNH9hUms=", + "jest-worker": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.0.0.tgz", + "integrity": "sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==", "dev": true, "requires": { - "indent-string": "^2.1.0", - "pluralize": "^1.2.1" + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" }, "dependencies": { - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -4194,6 +5853,52 @@ "dev": true, "optional": true }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -4227,6 +5932,15 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -4257,6 +5971,12 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, + "kleur": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.2.tgz", + "integrity": "sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q==", + "dev": true + }, "last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", @@ -4267,13 +5987,6 @@ "es6-weak-map": "^2.0.1" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", @@ -4339,6 +6052,18 @@ "flush-write-stream": "^1.0.2" } }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -4395,270 +6120,55 @@ } } }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._escapehtmlchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", - "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", - "dev": true, - "requires": { - "lodash._htmlescapes": "~2.4.1" - } - }, - "lodash._escapestringchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", - "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._htmlescapes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", - "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", - "dev": true - }, - "lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { + "locate-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._reunescapedhtml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", - "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "lodash._htmlescapes": "~2.4.1", - "lodash.keys": "~2.4.1" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" }, "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "dev": true }, - "lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1" - } - }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, - "lodash.defaults": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", - "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } - } - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1" - } - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", - "dev": true, - "requires": { - "lodash.keys": "~2.4.1" - }, - "dependencies": { - "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - } - } - }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", "dev": true }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "lru-queue": { @@ -4667,7 +6177,24 @@ "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "dev": true, "requires": { - "es5-ext": "~0.10.2" + "es5-ext": "~0.10.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "make-error": { @@ -4694,24 +6221,30 @@ "kind-of": "^6.0.2" } }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -4733,6 +6266,17 @@ "stack-trace": "0.0.10" } }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, "memoizee": { "version": "0.4.12", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.12.tgz", @@ -4749,30 +6293,19 @@ "timers-ext": "^0.1.2" } }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", + "dev": true + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", "dev": true, "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - } + "readable-stream": "^2.0.1" } }, "micromatch": { @@ -4818,12 +6351,12 @@ "dev": true }, "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "^1.0.0" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -4876,15 +6409,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", @@ -4941,13 +6465,54 @@ "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", "dev": true }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + }, + "dependencies": { + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.7.tgz", + "integrity": "sha512-bKdrwaqJUPHqlCzDD7so/R+Nk0jGv9a11ZhLrD9f6i947qGLrGAhU3OxRENa19QQmwzGy/g6zCDEuLGDO8HPvA==", "dev": true, "requires": { - "abbrev": "1" + "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } } }, "normalize-package-data": { @@ -4980,18 +6545,39 @@ "once": "^1.3.2" } }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "nwsapi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.0.tgz", + "integrity": "sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg==", + "dev": true + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -5062,6 +6648,16 @@ "isobject": "^3.0.0" } }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, "object.map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", @@ -5203,6 +6799,63 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -5229,6 +6882,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -5308,15 +6967,6 @@ } } }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -5344,6 +6994,35 @@ "pinkie": "^2.0.0" } }, + "pirates": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.0.tgz", + "integrity": "sha512-8t5BsXy1LUIjn3WWOlOuFDuKswhQb/tkak641lvBgmPOBUQHXveORtlMCp6OdPV1dtuTaEahKA8VNz6uLfKBtA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } + } + }, "plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", @@ -5362,6 +7041,12 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -5374,12 +7059,45 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "pretty-format": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", + "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", + "dev": true, + "requires": { + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -5392,6 +7110,16 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, + "prompts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.2.tgz", + "integrity": "sha512-Pc/c53d2WZHJWZr78/BhZ5eHsdQtltbyBjHoA4T0cs/4yKJqCcoOHrq2SNKwtspVE0C+ebqAR5u0/mXwrHaADQ==", + "dev": true, + "requires": { + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" + } + }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -5447,15 +7175,18 @@ } }, "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "readdirp": { @@ -5511,6 +7242,15 @@ } } }, + "realpath-native": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.0.2.tgz", + "integrity": "sha512-+S3zTvVt9yTntFrBpm7TQmQ3tzpCrnA1a/y+3cUHAc9ZR6aIjG0WNLR+Rj79QpJktY+VeW/TQtFlQ1bzsehI8g==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -5520,14 +7260,28 @@ "resolve": "^1.1.6" } }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", + "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-transform": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", + "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", "dev": true, "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "private": "^0.1.6" } }, "regex-not": { @@ -5540,6 +7294,17 @@ "safe-regex": "^1.1.0" } }, + "regexp-tree": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.1.tgz", + "integrity": "sha512-HwRjOquc9QOwKTgbxvZTcddS5mlNlwePMQ3NFL8broajMLD5CXDAqas8Y5yxJH5QtZp5iRor3YCILd5pz71Cgw==", + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "colors": "^1.1.2", + "yargs": "^12.0.5" + } + }, "regexp.prototype.flags": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", @@ -5555,6 +7320,43 @@ "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", "dev": true }, + "regexpu-core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.4.0.tgz", + "integrity": "sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -5594,15 +7396,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", @@ -5648,6 +7441,26 @@ "uuid": "^3.1.0" } }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "^4.13.1" + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "dev": true, + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5679,6 +7492,23 @@ "path-parse": "^1.0.5" } }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -5726,16 +7556,6 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.1" - } - }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -5770,6 +7590,31 @@ } } }, + "rollup": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.1.2.tgz", + "integrity": "sha512-OkdMxqMl8pWoQc5D8y1cIinYQPPLV8ZkfLgCzL6SytXeNA2P7UHynEQXI9tYxuAjAMsSyvRaWnyJDLHMxq0XAg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "*", + "acorn": "^6.0.5" + }, + "dependencies": { + "acorn": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", + "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "dev": true + } + } + }, + "rsvp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", + "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", + "dev": true + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -5809,6 +7654,30 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sane": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-3.1.0.tgz", + "integrity": "sha512-G5GClRRxT1cELXfdAq7UKtUsv8q/ZC5k8lQGmjEm4HcAl3HzBy68iglyNCmw4+0tiXPCBZntslHlRhbnsSws+Q==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "capture-exit": "^1.2.0", + "exec-sh": "^0.2.0", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.3", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5", + "watch": "~0.18.0" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "semver": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", @@ -5874,12 +7743,30 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "sisteransi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", @@ -6015,6 +7902,24 @@ "urix": "^0.1.0" } }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", @@ -6059,15 +7964,6 @@ "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "requires": { - "through": "2" - } - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -6106,6 +8002,12 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -6127,14 +8029,11 @@ } } }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true }, "stream-exhaust": { "version": "1.0.2", @@ -6148,6 +8047,33 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -6189,10 +8115,13 @@ } }, "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } }, "strip-ansi": { "version": "3.0.1", @@ -6203,20 +8132,23 @@ "ansi-regex": "^2.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", "dev": true }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-json-comments": { "version": "2.0.1", @@ -6246,6 +8178,12 @@ "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", "dev": true }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, "table": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", @@ -6321,12 +8259,99 @@ } } }, + "test-exclude": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.1.0.tgz", + "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -6410,6 +8435,12 @@ "os-tmpdir": "~1.0.2" } }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, "to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -6420,6 +8451,12 @@ "is-negated-glob": "^1.0.0" } }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -6480,10 +8517,27 @@ "punycode": "^1.4.1" } }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, "tunnel-agent": { @@ -6518,24 +8572,25 @@ "dev": true }, "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, "optional": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -6565,6 +8620,34 @@ "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", "dev": true }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", + "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", + "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==", + "dev": true + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -6697,6 +8780,16 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -6842,6 +8935,77 @@ "source-map": "^0.5.1" } }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "watch": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", + "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", + "dev": true, + "requires": { + "exec-sh": "^0.2.0", + "minimist": "^1.2.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", @@ -6857,13 +9021,6 @@ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -6917,6 +9074,32 @@ "mkdirp": "^0.5.1" } }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", @@ -6930,24 +9113,75 @@ "dev": true }, "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" }, "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, - "optional": true + "requires": { + "locate-path": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, diff --git a/package.json b/package.json index b177b91..6b29010 100644 --- a/package.json +++ b/package.json @@ -1,55 +1,62 @@ { - "author": { - "email": "andrea.scartabelli@gmail.com", - "name": "Andrea Scartabelli" - }, - "description": "A lightweight, and docile, JavaScript library to help embracing functional programming.", - "files": [ - "dist/lamb.js", - "dist/lamb.min.js", - "dist/lamb.min.js.map" - ], - "homepage": "https://ascartabelli.github.io/lamb/", - "keywords": [ - "functional programming", - "utility", - "library", - "server", - "client", - "browser", - "node.js", - "es5", - "javascript" - ], - "license": "MIT", - "main": "./dist/lamb.js", - "name": "lamb", - "repository": { - "type": "git", - "url": "https://github.com/ascartabelli/lamb.git" - }, - "engines": { - "node": ">=4.0.0" - }, - "scripts": { - "test": "gulp test", - "coveralls": "gulp test:coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" - }, - "tonicExample": "var _ = require('lamb');", - "version": "0.57.0-alpha.3", - "devDependencies": { - "coveralls": "^3.0.2", - "gulp": "^4.0.0", - "gulp-concat": "^2.6.1", - "gulp-eslint": "^5.0.0", - "gulp-footer": "^2.0.1", - "gulp-header": "^2.0.5", - "gulp-indent": "^1.0.1", - "gulp-istanbul": "^1.1.3", - "gulp-jasmine": "^4.0.0", - "gulp-rename": "^1.4.0", - "gulp-sourcemaps": "^2.6.4", - "gulp-uglify": "^3.0.1" - }, - "dependencies": {} + "author": { + "email": "andrea.scartabelli@gmail.com", + "name": "Andrea Scartabelli" + }, + "description": "A lightweight, and docile, JavaScript library to help embracing functional programming.", + "engines": { + "node": ">=4.0.0" + }, + "files": [ + "dist/lamb.js", + "dist/lamb.min.js", + "dist/lamb.min.js.map", + "src/*.js", + "src/**/!(__tests__)/*.js" + ], + "homepage": "https://ascartabelli.github.io/lamb/", + "keywords": [ + "functional programming", + "utility", + "library", + "server", + "client", + "browser", + "node.js", + "es5", + "javascript" + ], + "license": "MIT", + "main": "dist/lamb.js", + "module": "src/index.js", + "name": "lamb", + "repository": { + "type": "git", + "url": "https://github.com/ascartabelli/lamb.git" + }, + "scripts": { + "test": "gulp test", + "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" + }, + "babel": { + "env": { "test": { "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]] } } + }, + "jsdelivr": "dist/lamb.min.js", + "unpkg": "dist/lamb.min.js", + "sideEffects": false, + "tonicExample": "var _ = require('lamb');", + "version": "0.57.0-rc.2", + "devDependencies": { + "@babel/preset-env": "^7.3.1", + "coveralls": "^3.0.2", + "gulp": "^4.0.0", + "gulp-eslint": "^5.0.0", + "gulp-jest": "^4.0.2", + "gulp-rename": "^1.4.0", + "gulp-sourcemaps": "^2.6.4", + "gulp-uglify": "^3.0.1", + "jest-cli": "^24.1.0", + "rollup": "^1.1.2" + }, + "dependencies": {} } diff --git a/test/commons.js b/src/__tests__/commons.js similarity index 75% rename from test/commons.js rename to src/__tests__/commons.js index 27227e1..41598b4 100644 --- a/test/commons.js +++ b/src/__tests__/commons.js @@ -1,10 +1,5 @@ -/* eslint-disable strict */ -// we don't want strict mode to leak inside jasmine - -var lamb = require("../dist/lamb.js"); -var equalities = require("./custom_equalities.js"); - -var concat = lamb.generic(Array.prototype.concat); +var generic = Function.bind.bind(Function.call); +var concat = generic(Array.prototype.concat); var coupleWithWrapper = function (values) { return values.reduce(function (result, value) { @@ -27,7 +22,7 @@ var nils = [null, void 0]; var numbersList = [-0.5, -0, 0, 0.5, -2.5, 2.5, 3]; var numbers = coupleWithWrapper(numbersList); -var objects = [Object.create(null), {}, {a: 1, b: 2}, argsObject]; +var objects = [Object.create(null), {}, { a: 1, b: 2 }, argsObject]; // discarding the first element in `objects` because you can't extract the // primitive value of `Object.create(null)` and it's not worth adding checks for it @@ -129,23 +124,35 @@ var nonNumbers = concat( var valuesList = concat(nils, nonNils); -module.exports = { - lamb: lamb, - equalities: equalities, - vars: lamb.immutable({ - nonStrings: nonStrings, - nonStringsAsStrings: nonStringsAsStrings, - nonArrayLikes: nonArrayLikes, - nonFunctions: nonFunctions, - nonNils: nonNils, - nonNulls: nonNulls, - nonNumbers: nonNumbers, - nonUndefineds: nonUndefineds, - valuesList: valuesList, - wannabeEmptyArrays: wannabeEmptyArrays, - wannabeEmptyObjects: wannabeEmptyObjects, - wannabeNaNs: wannabeNaNs, - zeroesAsIntegers: zeroesAsIntegers, - zeroesAsNumbers: zeroesAsNumbers - }) +export { + nonStrings, + nonStringsAsStrings, + nonArrayLikes, + nonFunctions, + nonNils, + nonNulls, + nonNumbers, + nonUndefineds, + valuesList, + wannabeEmptyArrays, + wannabeEmptyObjects, + wannabeNaNs, + zeroesAsIntegers, + zeroesAsNumbers }; + +/* +export nonStringsAsStrings; +export nonArrayLikes; +export nonFunctions; +export nonNils; +export nonNulls; +export nonNumbers; +export nonUndefineds; +export valuesList; +export wannabeEmptyArrays; +export wannabeEmptyObjects; +export wannabeNaNs; +export zeroesAsIntegers; +export zeroesAsNumber; +*/ diff --git a/src/__tests__/custom_matchers.js b/src/__tests__/custom_matchers.js new file mode 100644 index 0000000..072d274 --- /dev/null +++ b/src/__tests__/custom_matchers.js @@ -0,0 +1,49 @@ +function isSparseArray (array) { + return Array.isArray(array) && Object.keys(array).filter(function (v) { + return String(v >>> 0) === v; + }).length !== array.length; +} + +function isSparseArrayCheckNeeded (a, b) { + return isSparseArray(a) && Array.isArray(b) || isSparseArray(b) && Array.isArray(a); +} + +expect.extend({ + toStrictArrayEqual: function (a, b) { + var result = { + message: function () { + return "Expected " + a + " to strict array equal " + b; + } + }; + + if (isSparseArrayCheckNeeded(a, b)) { + var aLen = a.length; + + if (aLen !== b.length) { + result.pass = false; + + return result; + } + + for (var i = 0; i < aLen; i++) { + if (i in a ^ i in b) { + result.pass = false; + + return result; + } else if (a[i] !== b[i]) { + result.pass = false; + + return result; + } + } + + result.pass = true; + + return result; + } + + result.pass = this.equals(a, b); + + return result; + } +}); diff --git a/src/_intro.js b/src/_intro.js deleted file mode 100644 index c6230a5..0000000 --- a/src/_intro.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @overview <%= pkg.name %> - <%= pkg.description %> - * @author <%= pkg.author.name %> <<%= pkg.author.email %>> - * @version <%= pkg.version %> - * @module lamb - * @license <%= pkg.license %> - * @preserve - */ -(function (host) { - "use strict"; - - var lamb = Object.create(null); - var _ = {}; // internal placeholder for partial application - var _placeholder = lamb; // default value for public placeholder - - Object.defineProperties(lamb, { - /** - * The object used as a placeholder in partial application. Its default value is - * the lamb object itself.
- * The property is public so that you can make Lamb use your own placeholder, however - * you can't change it at will or the partially applied functions you defined before the - * change won't recognize the former placeholder. - * @alias module:lamb.@@lamb/placeholder - * @category Special properties - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.53.0 - * @type Object - */ - "@@lamb/placeholder": { - get: function () { - return _placeholder; - }, - set: function (value) { - _placeholder = value; - } - }, - - /** - * The current library version. - * @alias module:lamb.@@lamb/version - * @category Special properties - * @readonly - * @since 0.53.0 - * @type String - */ - "@@lamb/version": {value: "<%= pkg.version %>"} - }); - - // prototype shortcuts - var _objectProto = Object.prototype; - var _stringProto = String.prototype; - - // constants - var MAX_ARRAY_LENGTH = 4294967295; diff --git a/src/accessors.js b/src/accessors.js deleted file mode 100644 index 2312198..0000000 --- a/src/accessors.js +++ /dev/null @@ -1,646 +0,0 @@ -/** - * A curried version of {@link module:lamb.getIndex|getIndex} that uses the provided index - * to build a function expecting the array-like object holding the element we want to retrieve. - * @example - * var getFifthElement = _.getAt(4); - * - * getFifthElement([1, 2, 3, 4, 5]) // => 5 - * getFifthElement("foo bar") // => "b" - * getFifthElement([]) // => undefined - * getFifthElement("foo") // => undefined - * - * @example Using negative indexes: - * _.getAt(-2)([1, 2, 3]) // => 2 - * _.getAt(-3)("foo") // => "f" - * - * @memberof module:lamb - * @category Array - * @function - * @since 0.16.0 - * @see {@link module:lamb.getIndex|getIndex} - * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. - * @param {Number} index - * @returns {Function} - */ -var getAt = _curry2(getIndex, true); - -/** - * Returns the value of the object property with the given key. - * @example - * var user = {name: "John"}; - * - * _.getIn(user, "name") // => "John"; - * _.getIn(user, "surname") // => undefined - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.getKey|getKey} - * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} - * @since 0.18.0 - * @param {Object} obj - * @param {String} key - * @returns {*} - */ -function getIn (obj, key) { - return obj[key]; -} - -/** - * Retrieves the element at the given index in an array-like object.
- * Like {@link module:lamb.slice|slice} the index can be negative.
- * If the index isn't supplied, or if its value isn't an integer within the array-like bounds, - * the function will return undefined.
- * getIndex will throw an exception when receives null or - * undefined in place of an array-like object, but returns undefined - * for any other value. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.getIndex(arr, 1) // => 2 - * _.getIndex(arr, -1) // => 5 - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.getAt|getAt} - * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. - * @since 0.23.0 - * @param {ArrayLike} arrayLike - * @param {Number} index - * @returns {*} - */ -function getIndex (arrayLike, index) { - var idx = _toNaturalIndex(index, _toArrayLength(arrayLike.length)); - - return idx === idx ? arrayLike[idx] : void 0; // eslint-disable-line no-self-compare -} - -/** - * A curried version of {@link module:lamb.getIn|getIn}.
- * Receives a property name and builds a function expecting the object from which we want to retrieve - * the property. - * @example - * var user1 = {name: "john"}; - * var user2 = {name: "jane"}; - * var getName = _.getKey("name"); - * - * getName(user1) // => "john" - * getName(user2) // => "jane" - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.getIn|getIn} - * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} - * @since 0.1.0 - * @param {String} key - * @returns {Function} - */ -var getKey = _curry2(getIn, true); - -/** - * Builds a partial application of {@link module:lamb.getPathIn|getPathIn} with the given - * path and separator, expecting the object to act upon.
- * @example - * var user = { - * name: "John", - * surname: "Doe", - * login: { - * "user.name": "jdoe", - * password: "abc123" - * } - * }; - * - * var getPwd = _.getPath("login.password"); - * var getUsername = _.getPath("login/user.name", "/"); - * - * getPwd(user) // => "abc123"; - * getUsername(user) // => "jdoe" - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.getPathIn|getPathIn} - * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} - * @since 0.19.0 - * @param {String} path - * @param {String} [separator="."] - * @returns {Function} - */ -var getPath = _makePartial3(getPathIn); - -/** - * Gets a nested property value from an object using the given path.
- * The path is a string with property names separated by dots by default, but - * it can be customised with the optional third parameter.
- * You can use integers in the path, even negative ones, to refer to array-like - * object indexes, but the priority will be given to existing object keys: - * the last example explains this particular case. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * login: { - * "user.name": "jdoe", - * password: "abc123" - * }, - * scores: [ - * {id: 1, value: 10}, - * {id: 2, value: 20}, - * {id: 3, value: 30} - * ] - * }; - * - * _.getPathIn(user, "name") // => "John" - * _.getPathIn(user, "login.password") // => "abc123"; - * _.getPathIn(user, "login/user.name", "/") // => "jdoe" - * _.getPathIn(user, "name.foo") // => undefined - * _.getPathIn(user, "name.foo.bar") // => undefined - * - * @example Accessing array-like objects indexes: - * _.getPathIn(user, "login.password.1") // => "b" - * _.getPathIn(user, "scores.0") // => {id: 1, value: 10} - * _.getPathIn(user, "scores.-1.value") // => 30 - * - * @example Priority will be given to existing object keys over indexes: - * _.getPathIn(user, "scores.-1") // => {id: 3, value: 30} - * - * // let's do something funny - * user.scores["-1"] = "foo bar"; - * - * _.getPathIn(user, "scores.-1") // => "foo bar"; - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.getPath|getPath} - * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} - * @since 0.19.0 - * @param {Object|ArrayLike} obj - * @param {String} path - * @param {String} [separator="."] - * @returns {*} - */ -function getPathIn (obj, path, separator) { - return _getPathInfo(obj, _toPathParts(path, separator), true).target; -} - -/** - * Retrieves the first element of an array-like object.
- * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. - * @example - * _.head([1, 2, 3]) // => 1 - * _.head("hello") // => "h" - * _.head([]) // => undefined - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.last|last} - * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {*} - */ -var head = getAt(0); - -/** - * Retrieves the last element of an array-like object.
- * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. - * @example - * _.last([1, 2, 3]) // => 3 - * _.last("hello") // => "o" - * _.last([]) // => undefined - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.head|head} - * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {*} - */ -var last = getAt(-1); - -/** - * A curried version of {@link module:lamb.setIndex|setIndex} that builds - * a function that creates a copy of an array-like object with the given - * index changed to the desired value.
- * If the index is not an integer or if it's out of bounds, the function - * will return a copy of the original array.
- * Negative indexes are allowed. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.setAt(2, 99)(arr) // => [1, 2, 99, 4, 5] - * arr // => [1, 2, 3, 4, 5] - * - * _.setAt(10, 99)(arr) // => [1, 2, 3, 4, 5] (not a reference to `arr`) - * - * @example Using negative indexes: - * _.setAt(-1, 99)(arr) // => [1, 2, 3, 4, 99] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.setIndex|setIndex} - * @since 0.17.0 - * @param {Number} index - * @param {*} value - * @returns {Function} - */ -var setAt = _makePartial3(_setIndex); - -/** - * Sets the specified key to the given value in a copy of the provided object.
- * All the remaining enumerable keys of the source object will be simply copied in the - * result object without breaking references.
- * If the specified key is not part of the source object, it will be added to the - * result.
- * The main purpose of the function is to work on simple plain objects used as - * data structures, such as JSON objects, and makes no effort to play nice with - * objects created from an OOP perspective (it's not worth it).
- * For example the prototype of the result will be Object's regardless - * of the source's one. - * @example - * var user = {name: "John", surname: "Doe", age: 30}; - * - * _.setIn(user, "name", "Jane") // => {name: "Jane", surname: "Doe", age: 30} - * _.setIn(user, "gender", "male") // => {name: "John", surname: "Doe", age: 30, gender: "male"} - * - * // `user` still is {name: "John", surname: "Doe", age: 30} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.setKey|setKey} - * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} - * @since 0.18.0 - * @param {Object} source - * @param {String} key - * @param {*} value - * @returns {Object} - */ -function setIn (source, key, value) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "object"); - } - - return _setIn(source, key, value); -} - -/** - * Creates a copy of an array-like object with the given index changed to - * the desired value.
- * If the index is not an integer or if it's out of bounds, the function - * will return a copy of the original array.
- * Negative indexes are allowed. - * @example - * var arr = [1, 2, 3]; - * - * _.setIndex(arr, 1, 99) // => [1, 99, 3] - * _.setIndex(arr, -1, 99) // => [1, 2, 99] - * _.setIndex(arr, 10, 99) // => [1, 2, 3] (not a reference to `arr`) - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.setAt|setAt} - * @since 0.23.0 - * @param {ArrayLike} arrayLike - * @param {Number} index - * @param {*} value - * @returns {Array} - */ -var setIndex = aritize(_setIndex, 3); - -/** - * Builds a partial application of {@link module:lamb.setIn|setIn} with the provided - * key and value.
- * The resulting function expects the object to act upon.
- * Please refer to {@link module:lamb.setIn|setIn}'s description for explanations about - * how the copy of the source object is made. - * @example - * var user = {name: "John", surname: "Doe", age: 30}; - * var setAgeTo40 = _.setKey("age", 40); - * - * setAgeTo40(user) // => {name: "john", surname: "doe", age: 40} - * - * // `user` still is {name: "John", surname: "Doe", age: 30} - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.setIn|setIn} - * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} - * @since 0.18.0 - * @param {String} key - * @param {*} value - * @returns {Function} - */ -var setKey = _makePartial3(setIn); - -/** - * Builds a partial application of {@link module:lamb.setPathIn|setPathIn} expecting the - * object to act upon.
- * See {@link module:lamb.setPathIn|setPathIn} for more details and examples. - * @example - * var user = {id: 1, status: {active: false}}; - * var activate = _.setPath("status.active", true); - * - * activate(user) // => {id: 1, status: {active: true}} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.setPathIn|setPathIn} - * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} - * @since 0.20.0 - * @param {String} path - * @param {*} value - * @param {String} [separator="."] - * @returns {Function} - */ -function setPath (path, value, separator) { - return function (source) { - return setPathIn(source, path, value, separator); - }; -} - -/** - * Allows to change a nested value in a copy of the provided object.
- * The function will delegate the "set action" to {@link module:lamb.setIn|setIn} or - * {@link module:lamb.setAt|setAt} depending on the value encountered in the path, - * so please refer to the documentation of those functions for specifics about the - * implementation.
- * Note anyway that the distinction will be between Arrays, delegated - * to {@link module:lamb.setAt|setAt}, and everything else (including array-like objects), - * which will be delegated to {@link module:lamb.setIn|setIn}.
- * As a result of that, array-like objects will be converted to objects having numbers as keys - * and paths targeting non-object values will be converted to empty objects.
- * You can anyway target array elements using integers in the path, even negative ones, but - * the priority will be given to existing, and enumerable, object keys.
- * Non-enumerable properties encountered in the path will be considered as non-existent properties.
- * Like {@link module:lamb.getPathIn|getPathIn} or {@link module:lamb.getPath|getPath} you can - * use custom path separators. - * @example - * var user = {id: 1, status: {active : false, scores: [2, 4, 6]}}; - * - * _.setPathIn(user, "status.active", true) // => {id: 1, status: {active : true, scores: [2, 4, 6]}} - * - * @example Targeting arrays: - * _.setPathIn(user, "status.scores.0", 8) // => {id: 1, status: {active : false, scores: [8, 4, 6]}} - * - * // you can use negative indexes as well - * _.setPathIn(user, "status.scores.-1", 8) // => {id: 1, status: {active : false, scores: [2, 4, 8]}} - * - * @example Arrays can also be part of the path and not necessarily its target: - * var user = {id: 1, scores: [ - * {value: 2, year: "2000"}, - * {value: 4, year: "2001"}, - * {value: 6, year: "2002"} - * ]}; - * - * var newUser = _.setPathIn(user, "scores.0.value", 8); - * // "newUser" holds: - * // {id: 1, scores: [ - * // {value: 8, year: "2000"}, - * // {value: 4, year: "2001"}, - * // {value: 6, year: "2002"} - * // ]} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.setPath|setPath} - * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} - * @since 0.20.0 - * @param {Object|Array} source - * @param {String} path - * @param {*} value - * @param {String} [separator="."] - * @returns {Object|Array} - */ -function setPathIn (source, path, value, separator) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "object"); - } - - return _setPathIn(source, _toPathParts(path, separator), value); -} - -/** - * Builds a function that creates a copy of an array-like object with the given index - * changed by applying the provided function to its value.
- * If the index is not an integer or if it's out of bounds, the function will return - * a copy of the original array.
- * Negative indexes are allowed. - * @example - * var arr = ["a", "b", "c"]; - * var toUpperCase = _.invoker("toUpperCase"); - * - * _.updateAt(1, toUpperCase)(arr) // => ["a", "B", "c"] - * _.updateAt(-1, toUpperCase)(arr) // => ["a", "b", "C"] - * _.updateAt(10, toUpperCase)(arr) // => ["a", "b", "c"] (not a reference to `arr`) - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.updateIndex|updateIndex} - * @since 0.22.0 - * @param {Number} index - * @param {Function} updater - * @returns {Function} - */ -function updateAt (index, updater) { - return function (arrayLike) { - return _setIndex(arrayLike, index, null, updater); - }; -} - -/** - * Creates a copy of the given object having the desired key value updated by applying - * the provided function to it.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the - * source is returned otherwise. - * @example - * var user = {name: "John", visits: 2}; - * var toUpperCase = _.invoker("toUpperCase"); - * - * _.updateIn(user, "name", toUpperCase) // => {name: "JOHN", visits: 2} - * _.updateIn(user, "surname", toUpperCase) // => {name: "John", visits: 2} - * - * @example Non-enumerable properties will be treated as non-existent: - * var user = Object.create({name: "John"}, {visits: {value: 2}}); - * - * _.updateIn(user, "visits", _.add(1)) // => {name: "John", visits: 2} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.updateKey|updateKey} - * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} - * @since 0.22.0 - * @param {Object} source - * @param {String} key - * @param {Function} updater - * @returns {Object} - */ -function updateIn (source, key, updater) { - return _isEnumerable(source, key) ? - _setIn(source, key, updater(source[key])) : - _merge(enumerables, source, {}); -} - -/** - * Creates a copy of an array-like object with the given index changed by applying the - * provided function to its value.
- * If the index is not an integer or if it's out of bounds, the function will return - * a copy of the original array.
- * Negative indexes are allowed. - * @example - * var arr = ["a", "b", "c"]; - * var toUpperCase = _.invoker("toUpperCase"); - * - * _.updateIndex(arr, 1, toUpperCase) // => ["a", "B", "c"] - * _.updateIndex(arr, -1, toUpperCase) // => ["a", "b", "C"] - * _.updateIndex(arr, 10, toUpperCase) // => ["a", "b", "c"] (not a reference to `arr`) - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.updateAt|updateAt} - * @since 0.23.0 - * @param {ArrayLike} arrayLike - * @param {Number} index - * @param {Function} updater - * @returns {Array} - */ -var updateIndex = partial(_setIndex, [_, _, null, _]); - -/** - * Builds a partial application of {@link module:lamb.updateIn|updateIn} with the provided - * key and updater, expecting the object to act upon.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the - * source is returned otherwise. - * @example - * var user = {name: "John", visits: 2}; - * var incrementVisits = _.updateKey("visits", _.add(1)); - * - * incrementVisits(user) // => {name: "John", visits: 3} - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.updateIn|updateIn} - * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} - * @since 0.22.0 - * @param {String} key - * @param {Function} updater - * @returns {Function} - */ -var updateKey = _makePartial3(updateIn); - -/** - * Builds a partial application of {@link module:lamb.updatePathIn|updatePathIn} - * expecting the object to act upon.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the - * source is returned otherwise.
- * Like the other "path" functions, negative indexes can be used to access array elements, but - * the priority will be given to existing, and enumerable, object keys. - * @example - * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; - * var incrementScores = _.updatePath("status.scores", _.mapWith(_.add(1))) - * - * incrementScores(user) // => {id: 1, status: {scores: [3, 5, 7], visits: 0}} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.updatePathIn|updatePathIn} - * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} - * @since 0.24.0 - * @param {String} path - * @param {Function} updater - * @param {String} [separator="."] - * @returns {Function} - */ -function updatePath (path, updater, separator) { - return function (source) { - return updatePathIn(source, path, updater, separator); - }; -} - -/** - * Allows to change a nested value in a copy of the given object by applying the provided - * function to it.
- * This function is meant for updating existing enumerable properties, and for those it - * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the - * source is returned otherwise.
- * Like the other "path" functions, negative indexes can be used to access array elements, but - * the priority will be given to existing, and enumerable, object keys. - * @example - * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; - * var inc = _.add(1); - * - * _.updatePathIn(user, "status.visits", inc) // => {id: 1, status: {scores: [2, 4, 6]}, visits: 1} - * - * @example Targeting arrays: - * _.updatePathIn(user, "status.scores.0", inc) // => {id: 1, status: {scores: [3, 4, 6], visits: 0}} - * - * // you can use negative indexes as well - * _.updatePathIn(user, "status.scores.-1", inc) // => {id: 1, status: {scores: [2, 4, 7], visits: 0}} - * - * @example Arrays can also be part of the path and not necessarily its target: - * var user = {id: 1, scores: [ - * {value: 2, year: "2000"}, - * {value: 4, year: "2001"}, - * {value: 6, year: "2002"} - * ]}; - * - * var newUser = _.updatePathIn(user, "scores.0.value", inc); - * // "newUser" holds: - * // {id: 1, scores: [ - * // {value: 3, year: "2000"}, - * // {value: 4, year: "2001"}, - * // {value: 6, year: "2002"} - * // ]} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.updatePath|updatePath} - * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} - * @since 0.24.0 - * @param {Object|Array} source - * @param {String} path - * @param {Function} updater - * @param {String} [separator="."] - * @returns {Object|Array} - */ -function updatePathIn (source, path, updater, separator) { - var parts = _toPathParts(path, separator); - var pathInfo = _getPathInfo(source, parts, false); - - if (pathInfo.isValid) { - return _setPathIn(source, parts, updater(pathInfo.target)); - } else { - return Array.isArray(source) ? slice(source, 0, source.length) : _merge(enumerables, source, {}); - } -} - -lamb.getAt = getAt; -lamb.getIn = getIn; -lamb.getIndex = getIndex; -lamb.getKey = getKey; -lamb.getPath = getPath; -lamb.getPathIn = getPathIn; -lamb.head = head; -lamb.last = last; -lamb.setAt = setAt; -lamb.setIn = setIn; -lamb.setIndex = setIndex; -lamb.setKey = setKey; -lamb.setPath = setPath; -lamb.setPathIn = setPathIn; -lamb.updateAt = updateAt; -lamb.updateIn = updateIn; -lamb.updateIndex = updateIndex; -lamb.updateKey = updateKey; -lamb.updatePath = updatePath; -lamb.updatePathIn = updatePathIn; diff --git a/src/array.js b/src/array.js deleted file mode 100644 index 52d65bb..0000000 --- a/src/array.js +++ /dev/null @@ -1,899 +0,0 @@ -/** - * A curried version of {@link module:lamb.appendTo|appendTo} that uses the value to append - * to build a function expecting the array-like object to act upon. - * @example - * var arr = [1, 2, 3, 4]; - * - * _.append(5)(arr) // => [1, 2, 3, 4, 5] - * _.append([5])(arr) // => [1, 2, 3, 4, [5]] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.appendTo|appendTo} - * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} - * @since 0.44.0 - * @param {*} value - * @returns {Function} - */ -var append = _curry2(appendTo, true); - -/** - * Appends the given value at the end of a copy of the provided array-like object. - * @example - * var arr = [1, 2, 3, 4]; - * - * _.appendTo(arr, 5) // => [1, 2, 3, 4, 5] - * _.appendTo(arr, [5]) // => [1, 2, 3, 4, [5]] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.append|append} - * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} - * @since 0.44.0 - * @param {ArrayLike} arrayLike - * @param {*} value - * @returns {Array} - */ -function appendTo (arrayLike, value) { - return slice(arrayLike, 0, arrayLike.length).concat([value]); -} - -/** - * Returns an array of unique items present only in the first of the two given - * array-like objects. To determine uniqueness the function uses the - * ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. - * @example - * var a1 = [1, 2, 1, 3, 4]; - * var a2 = [2, 4, 5, 6]; - * var a3 = [3, 4, 5, 2, 1]; - * - * _.difference(a1, a2) // => [1, 3] - * _.difference(a2, a3) // => [6] - * _.difference(a1, a3) // => [] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.intersection|intersection} - * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} - * @see {@link module:lamb.pull|pull}, {@link module:lamb.pullFrom|pullFrom} - * @since 0.6.0 - * @param {ArrayLike} arrayLike - * @param {ArrayLike} other - * @returns {Array} - */ -function difference (arrayLike, other) { - var isNotInOther = partial(not(isIn), [other]); - - return uniques(filter(arrayLike, isNotInOther)); -} - -/** - * A curried version of {@link module:lamb.dropFrom|dropFrom} that expects the number of elements - * to drop to build a function waiting for the list to take the elements from.
- * See the note and examples for {@link module:lamb.dropFrom|dropFrom} about passing a - * negative n. - * @example - * var drop2 = _.drop(2); - * - * drop2([1, 2, 3, 4, 5]) // => [3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @function - * @since 0.5.0 - * @see {@link module:lamb.dropFrom|dropFrom} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @param {Number} n - * @returns {Function} - */ -var drop = _curry2(dropFrom, true); - -/** - * Builds an array without the first n elements of the given array or array-like object. - * Note that, being this only a shortcut for a specific use case of {@link module:lamb.slice|slice}, - * n can be a negative number. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.dropFrom(arr, 2) // => [3, 4, 5] - * _.dropFrom(arr, -1) // => [5] - * _.dropFrom(arr, -10) // => [1, 2, 3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @since 0.51.0 - * @param {ArrayLike} arrayLike - * @param {Number} n - * @returns {Array} - */ -function dropFrom (arrayLike, n) { - return slice(arrayLike, n, arrayLike.length); -} - -/** - * Builds a function that drops the first n elements satisfying a predicate - * from an array or array-like object. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var dropWhileIsEven = _.dropWhile(isEven); - * - * dropWhileIsEven([2, 4, 6, 8]) // => [] - * dropWhileIsEven([2, 4, 7, 8]) // => [7, 8] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.takeWhile|takeWhile} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @since 0.5.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -function dropWhile (predicate) { - return function (arrayLike) { - return slice(arrayLike, _getNumConsecutiveHits(arrayLike, predicate), arrayLike.length); - }; -} - -/** - * Similar to {@link module:lamb.map|map}, but if the mapping function returns an array this will - * be concatenated, rather than pushed, to the final result. - * @example Showing the difference with map: - * var words = ["foo", "bar"]; - * var toCharArray = function (s) { return s.split(""); }; - * - * _.map(words, toCharArray) // => [["f", "o", "o"], ["b", "a", "r"]] - * _.flatMap(words, toCharArray) // => ["f", "o", "o", "b", "a", "r"] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.flatMapWith|flatMapWith} - * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} - * @since 0.1.0 - * @param {Array} array - * @param {ListIteratorCallback} iteratee - * @returns {Array} - */ -function flatMap (array, iteratee) { - return reduce(array, function (result, el, idx, arr) { - var v = iteratee(el, idx, arr); - - if (!Array.isArray(v)) { - v = [v]; - } - - for (var i = 0, len = v.length, rLen = result.length; i < len; i++) { - result[rLen + i] = v[i]; - } - - return result; - }, []); -} - -/** - * A curried version of {@link module:lamb.flatMap|flatMap} that uses provided iteratee - * to build a function expecting the array to act upon. - * @example - * var toCharArray = function (s) { return s.split(""); }; - * var wordsToCharArray = _.flatMapWith(toCharArray); - * - * wordsToCharArray(["foo", "bar"]) // => ["f", "o", "o", "b", "a", "r"] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.flatMap|flatMap} - * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} - * @since 0.11.0 - * @param {ListIteratorCallback} iteratee - * @returns {Function} - */ -var flatMapWith = _curry2(flatMap, true); - -/** - * Flattens an array. - * @example Showing the difference with shallowFlatten: - * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; - * - * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] - * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.shallowFlatten|shallowFlatten} - * @since 0.1.0 - * @param {Array} array - * @returns {Array} - */ -var flatten = _makeArrayFlattener(true); - -/** - * Returns a copy of the given array-like object without the last element. - * @example - * _.init([1, 2, 3, 4]) // => [1, 2, 3] - * _.init([1]) // => [] - * _.init([]) // => [] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.tail|tail} - * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {Array} - */ -var init = partial(slice, [_, 0, -1]); - -/** - * Inserts the provided element in a copy of an array-like object at the - * specified index.
- * If the index is greater than the length of the array-like, the element - * will be appended at the end.
- * Negative indexes are allowed; when a negative index is out of bounds - * the element will be inserted at the start of the resulting array. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.insert(arr, 3, 99) // => [1, 2, 3, 99, 4, 5] - * _.insert(arr, -2, 99) // => [1, 2, 3, 99, 4, 5] - * _.insert(arr, 10, 99) // => [1, 2, 3, 4, 5, 99] - * _.insert(arr, -10, 99) // => [99, 1, 2, 3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.insertAt|insertAt} - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {Number} index - * @param {*} element - * @returns {Array} - */ -function insert (arrayLike, index, element) { - var result = slice(arrayLike, 0, arrayLike.length); - - result.splice(index, 0, element); - - return result; -} - -/** - * Builds a partial application of {@link module:lamb.insert|insert} - * expecting the array-like object to act upon. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.insertAt(3, 99)(arr) // => [1, 2, 3, 99, 4, 5] - * _.insertAt(-2, 99)(arr) // => [1, 2, 3, 99, 4, 5] - * _.insertAt(10, 99)(arr) // => [1, 2, 3, 4, 5, 99] - * _.insertAt(-10, 99)(arr) // => [99, 1, 2, 3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.insert|insert} - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} - * @since 0.27.0 - * @param {Number} index - * @param {*} element - * @returns {Function} - */ -var insertAt = _makePartial3(insert); - -/** - * Returns an array of every unique item that is included in all two given arrays - * or array-like objects.
- * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. - * @example - * var a1 = [1, 2, 3, 4]; - * var a2 = [2, 5, 4, 2, 6]; - * var a3 = [5, 6, 7]; - * - * _.intersection(a1, a2) // => [2, 4] - * _.intersection(a2, a3) // => [5, 6] - * _.intersection(a1, a3) // => [] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.difference|difference} - * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} - * @since 0.5.0 - * @param {ArrayLike} a - * @param {ArrayLike} b - * @returns {Array} - */ -function intersection (a, b) { - var result = []; - var lenA = a.length; - - if (lenA && b.length) { - for (var i = 0; i < lenA; i++) { - !isIn(result, a[i]) && isIn(b, a[i]) && result.push(a[i]); - } - } - - return result; -} - -/** - * Splits an array-like object in two lists: the first with the elements satisfying the given predicate, - * the others with the remaining elements. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - * - * _.partition(numbers, isEven) // => [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.partitionWith|partitionWith} - * @since 0.11.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Array} - */ -function partition (arrayLike, predicate) { - var result = [[], []]; - var len = arrayLike.length; - - for (var i = 0, el; i < len; i++) { - el = arrayLike[i]; - result[predicate(el, i, arrayLike) ? 0 : 1].push(el); - } - - return result; -} - -/** - * A curried version of {@link module:lamb.partition|partition} that uses the provided - * predicate to build a function expecting the array-like object to act upon. - * @example - * var users = [ - * {"name": "Jane", "surname": "Doe", "active": false}, - * {"name": "John", "surname": "Doe", "active": true}, - * {"name": "Mario", "surname": "Rossi", "active": true}, - * {"name": "Paolo", "surname": "Bianchi", "active": false} - * ]; - * var isActive = _.hasKeyValue("active", true); - * var splitByActiveStatus = _.partitionWith(isActive); - * - * splitByActiveStatus(users) // => - * // [[ - * // {"name": "John", "surname": "Doe", "active": true}, - * // {"name": "Mario", "surname": "Rossi", "active": true} - * // ], [ - * // {"name": "Jane", "surname": "Doe", "active": false}, - * // {"name": "Paolo", "surname": "Bianchi", "active": false} - * // ]] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.partition|partition} - * @since 0.11.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -var partitionWith = _curry2(partition, true); - -/** - * "Plucks" the values of the specified key from a list of objects. - * @example - * var persons = [ - * {"name": "Jane", "surname": "Doe", "age": 12}, - * {"name": "John", "surname": "Doe", "age": 40}, - * {"name": "Mario", "surname": "Rossi", "age": 18}, - * {"name": "Paolo", "surname": "Bianchi", "age": 15} - * ]; - * - * _.pluck(persons, "age") // => [12, 40, 18, 15] - * - * var lists = [ - * [1, 2], - * [3, 4, 5], - * [6] - * ]; - * - * _.pluck(lists, "length") // => [2, 3, 1] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.pluckKey|pluckKey} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {String} key - * @returns {Array} - */ -function pluck (arrayLike, key) { - return map(arrayLike, getKey(key)); -} - -/** - * A curried version of {@link module:lamb.pluck|pluck} expecting the key to retrieve to - * build a function waiting for the array-like object to act upon. - * @example - * var persons = [ - * {"name": "Jane", "surname": "Doe", "age": 12}, - * {"name": "John", "surname": "Doe", "age": 40}, - * {"name": "Mario", "surname": "Rossi", "age": 18}, - * {"name": "Paolo", "surname": "Bianchi", "age": 15} - * ]; - * var getAges = _.pluckKey("age"); - * - * getAges(persons) // => [12, 40, 18, 15] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.pluck|pluck} - * @since 0.12.0 - * @param {String} key - * @returns {Function} - */ -var pluckKey = compose(mapWith, getKey); - -/** - * A curried version of {@link module:lamb.pullFrom|pullFrom} expecting - * a list of values to build a function waiting for an array-like object.
- * The new function will create an array copy of the array-like without - * the specified values.
- * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
- * See examples in {@link module:lamb.pullFrom|pullFrom} about the - * relationship with {@link module:lamb.difference|difference}. - * @example - * var scores = [40, 20, 30, 10]; - * var newScores = [30, 10]; - * var pullNewScores = _.pull(newScores); - * - * pullNewScores(scores) // => [40, 20] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.pullFrom|pullFrom} - * @see {@link module:lamb.difference|difference} - * @since 0.45.0 - * @param {ArrayLike} values - * @returns {Function} - */ -var pull = _curry2(pullFrom, true); - -/** - * Creates an array copy of the given array-like object without the - * specified values.
- * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.pullFrom(arr, [2, 5]) // => [1, 3, 4] - * - * @example It's not the same as {@link module:lamb.difference|difference}: - * - * var arr = [1,1,2,3,4,4,5]; - * - * _.pullFrom(arr, [1, 2]) // => [3, 4, 4, 5] - * _.difference(arr, [1, 2]) // => [3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.pull|pull} - * @see {@link module:lamb.difference|difference} - * @since 0.45.0 - * @param {ArrayLike} arrayLike - * @param {ArrayLike} values - * @returns {Array} - */ -function pullFrom (arrayLike, values) { - return values ? filter(arrayLike, function (element) { - return !isIn(values, element); - }) : slice(arrayLike, 0, arrayLike.length); -} - -/** - * Returns a copy of the given array-like with the element rotated by the desired amount. - * Negative indexes are allowed. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.rotate(arr, 3) // => [3, 4, 5, 1, 2] - * _.rotate(arr, -3) // => [4, 5, 1, 2, 3] - * _.rotate(arr, 11) // => [5, 1, 2, 3, 4] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.rotateBy|rotateBy} - * @since 0.55.0 - * @param {ArrayLike} arrayLike - * @param {Number} amount - * @returns {Array} - */ -function rotate (arrayLike, amount) { - var len = arrayLike.length; - var shift = amount % len; - - return slice(arrayLike, -shift, len).concat(slice(arrayLike, 0, -shift)); -} - -/** - * A curried version of {@link module:lamb.rotate|rotate}.
- * Uses the given amount to build a function expecting the array to rotate by that amount. - * @example - * var arr = [1, 2, 3, 4, 5]; - * var rotateByTwo = _.rotateBy(2); - * - * rotateByTwo(arr) // => [4, 5, 1, 2, 3] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.rotate|rotate} - * @since 0.55.0 - * @param {Number} amount - * @returns {Function} - */ -var rotateBy = _curry2(rotate, true); - -/** - * Flattens the "first level" of an array. - * @example Showing the difference with flatten: - * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; - * - * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] - * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.flatten|flatten} - * @since 0.9.0 - * @param {Array} array - * @returns {Array} - */ -var shallowFlatten = _makeArrayFlattener(false); - -/** - * Returns a copy of the given array-like object without the first element. - * @example - * _.tail([1, 2, 3, 4]) // => [2, 3, 4] - * _.tail([1]) // => [] - * _.tail([]) // => [] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.init|init} - * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} - * @since 0.16.0 - * @param {ArrayLike} arrayLike - * @returns {Array} - */ -var tail = drop(1); - -/** - * A curried version of {@link module:lamb.takeFrom|takeFrom} that expects the number of elements - * to retrieve to build a function waiting for the list to take the elements from.
- * See the note and examples for {@link module:lamb.takeFrom|takeFrom} about passing a - * negative n. - * @example - * var take2 = _.take(2); - * - * take2([1, 2, 3, 4, 5]) // => [1, 2] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.takeFrom|takeFrom} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @since 0.5.0 - * @param {Number} n - * @returns {Function} - */ -var take = _curry2(takeFrom, true); - -/** - * Retrieves the first n elements from an array or array-like object.
- * Note that, being this a shortcut for a common use case of {@link module:lamb.slice|slice}, - * n can be a negative number. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.takeFrom(arr, 3) // => [1, 2, 3] - * _.takeFrom(arr, -1) // => [1, 2, 3, 4] - * _.takeFrom(arr, -10) // => [] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.take|take} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} - * @since 0.51.0 - * @param {ArrayLike} arrayLike - * @param {Number} n - * @returns {Array} - */ -function takeFrom (arrayLike, n) { - return slice(arrayLike, 0, n); -} - -/** - * Builds a function that takes the first n elements satisfying a predicate from - * an array or array-like object. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var takeWhileIsEven = _.takeWhile(isEven); - * - * takeWhileIsEven([1, 2, 4, 6, 8]) // => [] - * takeWhileIsEven([2, 4, 7, 8]) // => [2, 4] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.dropWhile|dropWhile} - * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @since 0.5.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -function takeWhile (predicate) { - return function (arrayLike) { - return slice(arrayLike, 0, _getNumConsecutiveHits(arrayLike, predicate)); - }; -} - -/** - * Transposes a matrix. Can also be used to reverse a {@link module:lamb.zip|zip} operation.
- * Just like {@link module:lamb.zip|zip}, the received array-like objects will be truncated to the - * shortest length. - * @example Transposing a matrix: - * _.transpose([ - * [1, 2, 3], - * [4, 5, 6], - * [7, 8, 9] - * ]) // => - * // [ - * // [1, 4, 7], - * // [2, 5, 8], - * // [3, 6, 9] - * // ] - * - * @example Showing the relationship with zip: - * var zipped = _.zip(["a", "b", "c"], [1, 2, 3]); // => [["a", 1], ["b", 2], ["c", 3]] - * - * _.transpose(zipped) // => [["a", "b", "c"], [1, 2, 3]] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.zip|zip} - * @since 0.14.0 - * @param {ArrayLike} arrayLike - * @returns {Array} - */ -function transpose (arrayLike) { - var minLen = MAX_ARRAY_LENGTH; - var len = _toArrayLength(arrayLike.length); - - if (len === 0) { - return []; - } - - for (var j = 0, elementLen; j < len; j++) { - elementLen = _toArrayLength(arrayLike[j].length); - - if (elementLen < minLen) { - minLen = elementLen; - } - } - - var result = Array(minLen); - - for (var i = 0, el; i < minLen; i++) { - el = result[i] = Array(len); - - for (j = 0; j < len; j++) { - el[j] = arrayLike[j][i]; - } - } - - return result; -} - -/** - * Returns a list of every unique element present in the two given array-like objects.
- * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} - * to test the equality of values.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.unionBy|unionBy} if you need to transform the values before - * the comparison or if you have to extract them from complex ones. - * @example - * _.union([1, 2, 3, 2], [2, 3, 4]) // => [1, 2, 3, 4] - * _.union("abc", "bcd") // => ["a", "b", "c", "d"] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.unionBy|unionBy} - * @see {@link module:lamb.difference|difference} - * @see {@link module:lamb.intersection|intersection} - * @since 0.5.0 - * @param {ArrayLike} a - * @param {ArrayLike} b - * @returns {Array} - */ -var union = unionBy(identity); - -/** - * Using the provided iteratee to transform values, builds a function that will - * return an array of the unique elements in the two provided array-like objects.
- * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} - * to test the equality of values.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.union|union} if you don't need to compare transformed values. - * @example - * var unionByFloor = _.unionBy(Math.floor); - * - * unionByFloor([2.8, 3.2, 1.5], [3.5, 1.2, 4]) // => [2.8, 3.2, 1.5, 4] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.union|union} - * @see {@link module:lamb.difference|difference} - * @see {@link module:lamb.intersection|intersection} - * @since 0.51.0 - * @param {ListIteratorCallback} iteratee - * @returns {Function} - */ -function unionBy (iteratee) { - return pipe([binary(list), flatMapWith(drop(0)), uniquesBy(iteratee)]); -} - -/** - * Returns an array comprised of the unique elements of the given array-like object.
- * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} - * to test the equality of values.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.uniquesBy|uniquesBy} if you need to transform your values before - * the comparison or if you have to extract them from complex ones. - * @example - * _.uniques([-0, 1, 2, 0, 2, 3, 4, 3, 5, 1]) // => [-0, 1, 2, 3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.uniquesBy|uniquesBy} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @returns {Array} - */ -var uniques = uniquesBy(identity); - -/** - * Using the provided iteratee, builds a function that will return an array comprised of the - * unique elements of an array-like object. The values being compared are the ones returned by - * the iteratee.
- * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
- * When two values are considered equal, the first occurence will be the one included - * in the result array.
- * See also {@link module:lamb.uniques|uniques} if you don't need to transform your values before the - * comparison. - * @example - * var data = [ - * {id: "1", name: "John"}, - * {id: "4", name: "Jane"}, - * {id: "5", name: "Joe"}, - * {id: "1", name: "Mario"}, - * {id: "5", name: "Paolo"}, - * ]; - * var uniquesById = _.uniquesBy(_.getKey("id")); - * - * uniquesById(data) // => [{id: "1", name: "John"}, {id: "4", name: "Jane"}, {id: "5", name: "Joe"}] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.uniques|uniques} - * @since 0.51.0 - * @param {ListIteratorCallback} iteratee - * @returns {Function} - */ -function uniquesBy (iteratee) { - return function (arrayLike) { - var result = []; - - for (var i = 0, len = arrayLike.length, seen = [], value; i < len; i++) { - value = iteratee(arrayLike[i], i, arrayLike); - - if (!isIn(seen, value)) { - seen.push(value); - result.push(arrayLike[i]); - } - } - - return result; - }; -} - -/** - * Builds a list of arrays out of the two given array-like objects by pairing items with - * the same index.
- * The received array-like objects will be truncated to the shortest length. - * @example - * _.zip( - * ["a", "b", "c"], - * [1, 2, 3] - * ) // => [["a", 1], ["b", 2], ["c", 3]] - * - * _.zip([1, 2, 3, 4], [5, 6, 7]) // => [[1, 5], [2, 6], [3, 7]] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.transpose|transpose} for the reverse operation - * @see {@link module:lamb.zipWithIndex|zipWithIndex} - * @since 0.14.0 - * @param {ArrayLike} a - * @param {ArrayLike} b - * @returns {Array} - */ -function zip (a, b) { - return transpose([a, b]); -} - -/** - * "{@link module:lamb.zip|Zips}" an array-like object by pairing its values with their index. - * @example - * _.zipWithIndex(["a", "b", "c"]) // => [["a", 0], ["b", 1], ["c", 2]] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.zip|zip} - * @since 0.14.0 - * @param {ArrayLike} arrayLike - * @returns {Array>} - */ -var zipWithIndex = mapWith(binary(list)); - -lamb.append = append; -lamb.appendTo = appendTo; -lamb.difference = difference; -lamb.drop = drop; -lamb.dropFrom = dropFrom; -lamb.dropWhile = dropWhile; -lamb.flatMap = flatMap; -lamb.flatMapWith = flatMapWith; -lamb.flatten = flatten; -lamb.init = init; -lamb.insert = insert; -lamb.insertAt = insertAt; -lamb.intersection = intersection; -lamb.partition = partition; -lamb.partitionWith = partitionWith; -lamb.pluck = pluck; -lamb.pluckKey = pluckKey; -lamb.pull = pull; -lamb.pullFrom = pullFrom; -lamb.rotate = rotate; -lamb.rotateBy = rotateBy; -lamb.shallowFlatten = shallowFlatten; -lamb.tail = tail; -lamb.take = take; -lamb.takeFrom = takeFrom; -lamb.takeWhile = takeWhile; -lamb.transpose = transpose; -lamb.union = union; -lamb.unionBy = unionBy; -lamb.uniques = uniques; -lamb.uniquesBy = uniquesBy; -lamb.zip = zip; -lamb.zipWithIndex = zipWithIndex; diff --git a/src/array/__tests__/append.spec.js b/src/array/__tests__/append.spec.js new file mode 100644 index 0000000..aa58fcf --- /dev/null +++ b/src/array/__tests__/append.spec.js @@ -0,0 +1,60 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("append / appendTo", function () { + var arr = ["a", "b", "c", "d", "e"]; + var s = "abcde"; + var r1 = ["a", "b", "c", "d", "e", "z"]; + var r2 = ["a", "b", "c", "d", "e", ["z"]]; + var r3 = ["a", "b", "c", "d", "e", void 0]; + + afterEach(function () { + expect(arr).toEqual(["a", "b", "c", "d", "e"]); + }); + + it("should append a value at the end of a copy of the given array", function () { + expect(lamb.appendTo(arr, "z")).toEqual(r1); + expect(lamb.append("z")(arr)).toEqual(r1); + expect(lamb.appendTo(arr, ["z"])).toEqual(r2); + expect(lamb.append(["z"])(arr)).toEqual(r2); + }); + + it("should accept array-like objects", function () { + expect(lamb.appendTo(s, "z")).toEqual(r1); + expect(lamb.append("z")(s)).toEqual(r1); + expect(lamb.appendTo(s, ["z"])).toEqual(r2); + expect(lamb.append(["z"])(s)).toEqual(r2); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.appendTo([1, , , 4], 5)).toStrictArrayEqual([1, void 0, void 0, 4, 5]); + expect(lamb.append(5)([1, , , 4])).toStrictArrayEqual([1, void 0, void 0, 4, 5]); + /* eslint-enable no-sparse-arrays */ + }); + + it("should append an `undefined` value when the `value` parameter is missing", function () { + expect(lamb.appendTo(arr)).toEqual(r3); + expect(lamb.append()(arr)).toEqual(r3); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.appendTo).toThrow(); + expect(lamb.append("z")).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.appendTo(null, "z"); }).toThrow(); + expect(function () { lamb.appendTo(void 0, "z"); }).toThrow(); + expect(function () { lamb.append("z")(null); }).toThrow(); + expect(function () { lamb.append("z")(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.appendTo(value, "z")).toEqual(["z"]); + expect(lamb.append("z")(value)).toEqual(["z"]); + }); + }); +}); diff --git a/src/array/__tests__/count.spec.js b/src/array/__tests__/count.spec.js new file mode 100644 index 0000000..db8f193 --- /dev/null +++ b/src/array/__tests__/count.spec.js @@ -0,0 +1,84 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("count / countBy", function () { + var getCity = lamb.getKey("city"); + + var persons = [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "John", surname: "Doe", age: 40, city: "New York" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + { name: "Paolo", surname: "Bianchi", age: 15 } + ]; + + var personsCityCount = { + "New York": 2, + Rome: 1, + undefined: 1 + }; + + var personsAgeGroupCount = { + under20: 3, + over20: 1 + }; + + var splitByAgeGroup = function (person, idx, list) { + expect(list).toBe(persons); + expect(persons[idx]).toBe(person); + + return person.age > 20 ? "over20" : "under20"; + }; + + it("should count the occurences of the key generated by the provided iteratee", function () { + expect(lamb.count(persons, getCity)).toEqual(personsCityCount); + expect(lamb.countBy(getCity)(persons)).toEqual(personsCityCount); + expect(lamb.count(persons, splitByAgeGroup)).toEqual(personsAgeGroupCount); + expect(lamb.countBy(splitByAgeGroup)(persons)).toEqual(personsAgeGroupCount); + }); + + it("should work with array-like objects", function () { + var result = { + h: 1, e: 1, l: 3, o: 2, " ": 1, w: 1, r: 1, d: 1 + }; + + expect(lamb.count("hello world", lamb.identity)).toEqual(result); + expect(lamb.countBy(lamb.identity)("hello world")).toEqual(result); + }); + + it("should throw an exception if the iteratee isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.count(persons, value); }).toThrow(); + expect(function () { lamb.countBy(value)(persons); }).toThrow(); + }); + + expect(function () { lamb.count(persons); }).toThrow(); + expect(function () { lamb.countBy()(persons); }).toThrow(); + }); + + it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { + var arr = [1, , 3, void 0, 5]; // eslint-disable-line no-sparse-arrays + var result = { false: 3, true: 2 }; + + expect(lamb.count(arr, lamb.isUndefined)).toEqual(result); + expect(lamb.countBy(lamb.isUndefined)(arr)).toEqual(result); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.count).toThrow(); + expect(lamb.countBy(lamb.identity)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.count(null, lamb.identity); }).toThrow(); + expect(function () { lamb.count(void 0, lamb.identity); }).toThrow(); + expect(function () { lamb.countBy(lamb.identity)(null); }).toThrow(); + expect(function () { lamb.countBy(lamb.identity)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array and return an empty object", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.countBy(lamb.identity)(value)).toEqual({}); + expect(lamb.count(value, lamb.identity)).toEqual({}); + }); + }); +}); diff --git a/src/array/__tests__/difference.spec.js b/src/array/__tests__/difference.spec.js new file mode 100644 index 0000000..4c0a382 --- /dev/null +++ b/src/array/__tests__/difference.spec.js @@ -0,0 +1,58 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("difference", function () { + var a1 = [0, 1, 2, 3, 4, NaN]; + var a2 = [-0, 2, 3, 4, 5, NaN]; + var a3 = [4, 5, 1, 4, 5]; + var a4 = [6, 7]; + + it("should return a new array with the items present only in the first of the two arrays", function () { + var r = lamb.difference(a1, a4); + + expect(r).toEqual(a1); + expect(r).not.toBe(a1); + expect(lamb.difference(a1, a3)).toEqual([0, 2, 3, NaN]); + }); + + it("should use the \"SameValueZero\" comparison and keep the first encountered value in case of equality", function () { + expect(lamb.difference(a1, a2)).toEqual([1]); + expect(lamb.difference([-0, 1, 0, 2], [1, 3, 2])).toEqual([-0]); + }); + + it("should return an array without duplicates", function () { + expect(lamb.difference(a3, a4)).toEqual([4, 5, 1]); + expect(lamb.difference(a3, a1)).toEqual([5]); + }); + + it("should work with array-like objects", function () { + expect(lamb.difference("abc", "bd")).toEqual(["a", "c"]); + expect(lamb.difference(["a", "b", "c"], "bd")).toEqual(["a", "c"]); + expect(lamb.difference("abc", ["b", "d"])).toEqual(["a", "c"]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.difference).toThrow(); + }); + + it("should throw an exception when a parameter is `null` or `undefined`", function () { + expect(function () { lamb.difference(null, a4); }).toThrow(); + expect(function () { lamb.difference(void 0, a4); }).toThrow(); + expect(function () { lamb.difference(a4, null); }).toThrow(); + expect(function () { lamb.difference(a4, void 0); }).toThrow(); + expect(function () { lamb.difference(a4); }).toThrow(); + }); + + it("should treat every other value in the main parameter as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.difference(value, a4)).toEqual([]); + }); + }); + + it("should treat every non-array-like value in the `other` parameter as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.difference(a1, value)).toEqual(a1); + expect(lamb.difference(wannabeEmptyArrays, value)).toEqual(lamb.uniques(wannabeEmptyArrays)); + }); + }); +}); diff --git a/src/array/__tests__/dropFrom.spec.js b/src/array/__tests__/dropFrom.spec.js new file mode 100644 index 0000000..54083b1 --- /dev/null +++ b/src/array/__tests__/dropFrom.spec.js @@ -0,0 +1,79 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays, zeroesAsIntegers } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("drop / dropFrom", function () { + it("should drop the first `n` elements of an array or array-like object", function () { + expect(lamb.dropFrom(["a", "b"], 1)).toEqual(["b"]); + expect(lamb.drop(3)([1, 2, 3, 4])).toEqual([4]); + }); + + it("should work with array-like objects", function () { + expect(lamb.dropFrom("abcd", 2)).toEqual(["c", "d"]); + expect(lamb.drop(2)("abcd")).toEqual(["c", "d"]); + }); + + it("should accept a negative `n`", function () { + expect(lamb.dropFrom([1, 2, 3, 4], -1)).toEqual([4]); + expect(lamb.drop(-3)("abcd")).toEqual(["b", "c", "d"]); + }); + + it("should return a copy of the source array when `n` is 0 or less or equal than the additive inverse of the array-like length", function () { + expect(lamb.dropFrom(["a", "b"], 0)).toEqual(["a", "b"]); + expect(lamb.dropFrom([1, 2, 3, 4], -4)).toEqual([1, 2, 3, 4]); + expect(lamb.drop(-10)([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); + }); + + it("should return an empty array when `n` is greater than or equal to the array-like length", function () { + expect(lamb.dropFrom([1, 2, 3, 4], 4)).toEqual([]); + expect(lamb.dropFrom([1, 2, 3, 4], 5)).toEqual([]); + expect(lamb.drop(10)([1, 2, 3, 4])).toEqual([]); + }); + + it("should convert to integer the value received as `n`", function () { + var arr = [1, 2, 3, 4, 5]; + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.drop(value)(arr)).toEqual(arr); + expect(lamb.dropFrom(arr, value)).toEqual(arr); + }); + + [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { + expect(lamb.drop(value)(arr)).toEqual([2, 3, 4, 5]); + expect(lamb.dropFrom(arr, value)).toEqual([2, 3, 4, 5]); + }); + + expect(lamb.drop(new Date())(arr)).toEqual([]); + expect(lamb.dropFrom(arr, new Date())).toEqual([]); + + expect(lamb.drop()(arr)).toEqual(arr); + expect(lamb.dropFrom(arr)).toEqual(arr); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.dropFrom([1, , 3], 1)).toStrictArrayEqual([void 0, 3]); + expect(lamb.drop(1)([1, , 3])).toStrictArrayEqual([void 0, 3]); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if called without the data argument or without arguments at all", function () { + expect(lamb.dropFrom).toThrow(); + expect(lamb.drop(1)).toThrow(); + expect(lamb.drop()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.dropFrom(null, 0); }).toThrow(); + expect(function () { lamb.dropFrom(void 0, 0); }).toThrow(); + expect(function () { lamb.drop(0)(null); }).toThrow(); + expect(function () { lamb.drop(0)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.dropFrom(value, 0)).toEqual([]); + expect(lamb.drop(0)(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/dropWhile.spec.js b/src/array/__tests__/dropWhile.spec.js new file mode 100644 index 0000000..ff4ab79 --- /dev/null +++ b/src/array/__tests__/dropWhile.spec.js @@ -0,0 +1,66 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("dropWhile", function () { + // to check "truthy" and "falsy" values returned by predicates + var isVowel = function (char, idx, s) { + expect(s[idx]).toBe(char); + + return ~"aeiouAEIOU".indexOf(char); + }; + + var isEven = function (n, idx, list) { + expect(list[idx]).toBe(n); + + return n % 2 === 0; + }; + + var dropWhileIsEven = lamb.dropWhile(isEven); + + it("should build a function that drops the first n elements satisfying a predicate from an array or array-like object", function () { + expect(dropWhileIsEven([])).toEqual([]); + expect(dropWhileIsEven([2, 4, 6, 8])).toEqual([]); + expect(dropWhileIsEven([2, 3, 4, 6, 8])).toEqual([3, 4, 6, 8]); + expect(dropWhileIsEven([2, 4, 6, 7, 8])).toEqual([7, 8]); + expect(dropWhileIsEven([1, 3, 5, 7])).toEqual([1, 3, 5, 7]); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + var dropWhileisVowel = lamb.dropWhile(isVowel); + + expect(dropWhileisVowel("aiuola")).toEqual(["l", "a"]); + }); + + it("should build a function throwing an exception if the predicate isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.dropWhile(value)([1, 2]); }).toThrow(); + }); + + expect(function () { lamb.dropWhile()([1, 2]); }).toThrow(); + }); + + it("should always return dense arrays", function () { + var dropWhileIsUndefined = lamb.dropWhile(lamb.isUndefined); + + /* eslint-disable no-sparse-arrays */ + expect(dropWhileIsEven([2, 4, , , 5])).toStrictArrayEqual([void 0, void 0, 5]); + expect(dropWhileIsUndefined([, , void 0, , void 0, 5, 6])).toStrictArrayEqual([5, 6]); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if called without the data argument", function () { + expect(dropWhileIsEven).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { dropWhileIsEven(null); }).toThrow(); + expect(function () { dropWhileIsEven(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(dropWhileIsEven(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/everyIn.spec.js b/src/array/__tests__/everyIn.spec.js new file mode 100644 index 0000000..dcc2b0c --- /dev/null +++ b/src/array/__tests__/everyIn.spec.js @@ -0,0 +1,100 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("every / everyIn", function () { + var a1 = [2, 4, 6, 8]; + var a2 = [1, 3, 5, 6, 7, 8]; + var isEven = function (n, idx, list) { + expect(list[idx]).toBe(n); + + return n % 2 === 0; + }; + + // to check "truthy" and "falsy" values returned by predicates + var isVowel = function (char, idx, s) { + expect(s[idx]).toBe(char); + + return ~"aeiouAEIOU".indexOf(char); + }; + + it("should check if every element of an array satisfies the given predicate", function () { + expect(lamb.everyIn(a1, isEven)).toBe(true); + expect(lamb.every(isEven)(a1)).toBe(true); + expect(lamb.everyIn(a2, isEven)).toBe(false); + expect(lamb.every(isEven)(a2)).toBe(false); + }); + + it("should work with array-like objects", function () { + expect(lamb.everyIn("2468", isEven)).toBe(true); + expect(lamb.every(isEven)("2468")).toBe(true); + expect(lamb.everyIn("24678", isEven)).toBe(false); + expect(lamb.every(isEven)("24678")).toBe(false); + }); + + it("should always return true for empty array-likes because of vacuous truth", function () { + expect(lamb.everyIn([], isEven)).toBe(true); + expect(lamb.every(isEven)([])).toBe(true); + expect(lamb.everyIn("", isEven)).toBe(true); + expect(lamb.every(isEven)("")).toBe(true); + }); + + it("should stop calling the predicate as soon as a `false` value is returned", function () { + var isGreaterThan10 = jest.fn(function (n) { + return n > 10; + }); + + var arr = [12, 13, 9, 15]; + + expect(lamb.everyIn(arr, isGreaterThan10)).toBe(false); + expect(isGreaterThan10).toHaveBeenCalledTimes(3); + + expect(lamb.every(isGreaterThan10)(arr)).toBe(false); + expect(isGreaterThan10).toHaveBeenCalledTimes(6); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.everyIn("aiu", isVowel)).toBe(true); + expect(lamb.every(isVowel)("aiu")).toBe(true); + expect(lamb.everyIn("aiuole", isVowel)).toBe(false); + expect(lamb.every(isVowel)("aiuole")).toBe(false); + }); + + it("should not skip deleted or unassigned elements, unlike the native method", function () { + var isDefined = lamb.not(lamb.isUndefined); + var arr = Array(5); + + arr[2] = 99; + + expect(lamb.everyIn(arr, isDefined)).toBe(false); + expect(lamb.every(isDefined)(arr)).toBe(false); + }); + + it("should throw an exception if the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.everyIn(a1, value); }).toThrow(); + expect(function () { lamb.every(value)(a1); }).toThrow(); + }); + + expect(function () { lamb.everyIn(a1); }).toThrow(); + expect(function () { lamb.every()(a1); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.every(isEven)).toThrow(); + expect(lamb.everyIn).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.everyIn(null, isEven); }).toThrow(); + expect(function () { lamb.everyIn(void 0, isEven); }).toThrow(); + expect(function () { lamb.every(isEven)(null); }).toThrow(); + expect(function () { lamb.every(isEven)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array and return `true`", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.everyIn(value, isEven)).toBe(true); + expect(lamb.every(isEven)(value)).toBe(true); + }); + }); +}); diff --git a/src/array/__tests__/filter.spec.js b/src/array/__tests__/filter.spec.js new file mode 100644 index 0000000..d301fb9 --- /dev/null +++ b/src/array/__tests__/filter.spec.js @@ -0,0 +1,90 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("filter / filterWith", function () { + var arr = ["Foo", "bar", "baZ"]; + + var isLowerCase = function (s, idx, list) { + expect(list[idx]).toBe(s); + + return s.toLowerCase() === s; + }; + + // to check "truthy" and "falsy" values returned by predicates + var isVowel = function (char, idx, s) { + expect(s[idx]).toBe(char); + + return ~"aeiouAEIOU".indexOf(char); + }; + + var getLowerCaseEls = lamb.filterWith(isLowerCase); + + afterEach(function () { + expect(arr).toEqual(["Foo", "bar", "baZ"]); + }); + + it("should filter an array by keeping the items satisfying the given predicate", function () { + expect(lamb.filter(arr, isLowerCase)).toEqual(["bar"]); + expect(getLowerCaseEls(arr)).toEqual(["bar"]); + }); + + it("should work with array-like objects", function () { + expect(lamb.filter("fooBAR", isLowerCase)).toEqual(["f", "o", "o"]); + expect(getLowerCaseEls("fooBAR")).toEqual(["f", "o", "o"]); + }); + + it("should not skip deleted or unassigned elements, unlike the native method", function () { + var sparseArr = Array(4); + + sparseArr[2] = 3; + + var isOdd = function (n) { return n % 2 !== 0; }; + var result = [void 0, void 0, 3, void 0]; + + expect(lamb.filter(sparseArr, isOdd)).toStrictArrayEqual(result); + expect(lamb.filterWith(isOdd)(sparseArr)).toStrictArrayEqual(result); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.filter("aiuola", isVowel)).toEqual(["a", "i", "u", "o", "a"]); + expect(lamb.filterWith(isVowel)("aiuola")).toEqual(["a", "i", "u", "o", "a"]); + }); + + it("should always return dense arrays", function () { + // eslint-disable-next-line no-sparse-arrays + var sparseArr = [1, , , 4, void 0, 5]; + + expect(lamb.filter(sparseArr, lamb.isUndefined)).toStrictArrayEqual([void 0, void 0, void 0]); + expect(lamb.filterWith(lamb.isUndefined)(sparseArr)).toStrictArrayEqual([void 0, void 0, void 0]); + }); + + it("should throw an exception if the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.filter(arr, value); }).toThrow(); + expect(function () { lamb.filterWith(value)(arr); }).toThrow(); + }); + + expect(function () { lamb.filterWith()(arr); }).toThrow(); + expect(function () { lamb.filter(arr); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(getLowerCaseEls).toThrow(); + expect(lamb.filter).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.filter(null, isLowerCase); }).toThrow(); + expect(function () { lamb.filter(void 0, isLowerCase); }).toThrow(); + expect(function () { getLowerCaseEls(null); }).toThrow(); + expect(function () { getLowerCaseEls(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.filter(value, isLowerCase)).toEqual([]); + expect(getLowerCaseEls(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/find.spec.js b/src/array/__tests__/find.spec.js new file mode 100644 index 0000000..13342c9 --- /dev/null +++ b/src/array/__tests__/find.spec.js @@ -0,0 +1,125 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("find / findWhere / findIndex / findIndexWhere", function () { + // to check "truthy" and "falsy" values returned by predicates + var isVowel = function (char, idx, s) { + expect(s[idx]).toBe(char); + + return ~"aeiouAEIOU".indexOf(char); + }; + + var persons = [ + { name: "Jane", surname: "Doe", age: 12 }, + { name: "John", surname: "Doe", age: 40 }, + { name: "Mario", surname: "Rossi", age: 18 }, + { name: "Paolo", surname: "Bianchi", age: 40 } + ]; + + var testString = "Hello world"; + var is40YO = lamb.hasKeyValue("age", 40); + var is41YO = lamb.hasKeyValue("age", 41); + + describe("find", function () { + it("should find an element in an array-like object by using the given predicate", function () { + expect(lamb.find(persons, is40YO)).toEqual(persons[1]); + expect(lamb.findWhere(is40YO)(persons)).toEqual(persons[1]); + }); + + it("should return `undefined` if there is no element satisfying the predicate", function () { + expect(lamb.find(persons, is41YO)).toBeUndefined(); + expect(lamb.findWhere(is41YO)(persons)).toBeUndefined(); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.find(testString, isVowel)).toBe("e"); + expect(lamb.find("zxc", isVowel)).toBeUndefined(); + expect(lamb.findWhere(isVowel)(testString)).toBe("e"); + expect(lamb.findWhere(isVowel)("zxc")).toBeUndefined(); + }); + + it("should throw an exception if the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.find(persons, value); }).toThrow(); + expect(function () { lamb.findWhere(value)(persons); }).toThrow(); + }); + + expect(function () { lamb.find(persons); }).toThrow(); + expect(function () { lamb.findWhere()(persons); }).toThrow(); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.find).toThrow(); + expect(lamb.findWhere()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.find(null, is40YO); }).toThrow(); + expect(function () { lamb.find(void 0, is40YO); }).toThrow(); + expect(function () { lamb.findWhere(is40YO)(null); }).toThrow(); + expect(function () { lamb.findWhere(is40YO)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.find(value, is40YO)).toBeUndefined(); + expect(lamb.findWhere(is40YO)(value)).toBeUndefined(); + }); + }); + }); + + describe("findIndex", function () { + it("should find the index of an element in an array-like object by using the given predicate", function () { + expect(lamb.findIndex(persons, is40YO)).toBe(1); + expect(lamb.findIndexWhere(is40YO)(persons)).toBe(1); + }); + + it("should return `-1` if there is no element satisfying the predicate", function () { + expect(lamb.findIndex(persons, is41YO)).toBe(-1); + expect(lamb.findIndexWhere(is41YO)(persons)).toBe(-1); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.findIndex(testString, isVowel)).toBe(1); + expect(lamb.findIndex("zxc", isVowel)).toBe(-1); + expect(lamb.findIndexWhere(isVowel)(testString)).toBe(1); + expect(lamb.findIndexWhere(isVowel)("zxc")).toBe(-1); + }); + + it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.findIndex([1, , 3], lamb.isUndefined)).toBe(1); + expect(lamb.findIndexWhere(lamb.isUndefined)([1, , 3])).toBe(1); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.findIndex(persons, value); }).toThrow(); + expect(function () { lamb.findIndexWhere(value)(persons); }).toThrow(); + }); + + expect(function () { lamb.findIndex(persons); }).toThrow(); + expect(function () { lamb.findIndexWhere()(persons); }).toThrow(); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.findIndex).toThrow(); + expect(lamb.findIndexWhere()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.findIndex(null, is40YO); }).toThrow(); + expect(function () { lamb.findIndex(void 0, is40YO); }).toThrow(); + expect(function () { lamb.findIndexWhere(is40YO)(null); }).toThrow(); + expect(function () { lamb.findIndexWhere(is40YO)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.findIndex(value, is40YO)).toBe(-1); + expect(lamb.findIndexWhere(is40YO)(value)).toBe(-1); + }); + }); + }); +}); diff --git a/src/array/__tests__/flatMap.spec.js b/src/array/__tests__/flatMap.spec.js new file mode 100644 index 0000000..41955a9 --- /dev/null +++ b/src/array/__tests__/flatMap.spec.js @@ -0,0 +1,106 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("flatMap / flatMapWith", function () { + it("should behave like map if the mapping function returns a non-array value", function () { + var double = function (n, idx, list) { + expect(list).toBe(arr); + expect(list[idx]).toBe(n); + + return n * 2; + }; + var arr = [1, 2, 3, 4, 5]; + var result = [2, 4, 6, 8, 10]; + + expect(lamb.flatMap(arr, double)).toEqual(result); + expect(lamb.flatMapWith(double)(arr)).toEqual(result); + }); + + it("should concatenate arrays returned by the mapping function to the result", function () { + var splitString = function (s) { + return s.split(""); + }; + var arr = ["foo", "bar", "baz"]; + var result = ["f", "o", "o", "b", "a", "r", "b", "a", "z"]; + + expect(lamb.flatMap(arr, splitString)).toEqual(result); + expect(lamb.flatMapWith(splitString)(arr)).toEqual(result); + }); + + it("should keeps its behaviour consistent even if the received function returns mixed results", function () { + var fn = function (n, idx, list) { + expect(list).toBe(arr); + expect(list[idx]).toBe(n); + + return n % 2 === 0 ? [n, n + 10] : n * 2; + }; + var arr = [1, 2, 3, 4, 5]; + var result = [2, 2, 12, 6, 4, 14, 10]; + + expect(lamb.flatMap(arr, fn)).toEqual(result); + expect(lamb.flatMapWith(fn)(arr)).toEqual(result); + }); + + it("should not flatten nested arrays", function () { + var splitString = function (s) { + return String(s) === s ? s.split("") : [[s, "not a string"]]; + }; + var arr = ["foo", "bar", 5, "baz"]; + var result = ["f", "o", "o", "b", "a", "r", [5, "not a string"], "b", "a", "z"]; + + expect(lamb.flatMap(arr, splitString)).toEqual(result); + expect(lamb.flatMapWith(splitString)(arr)).toEqual(result); + + var arr2 = ["foo", ["bar", ["baz"]]]; + var result2 = ["foo", "bar", ["baz"]]; + + expect(lamb.flatMap(arr2, lamb.identity)).toEqual(result2); + expect(lamb.flatMapWith(lamb.identity)(arr2)).toEqual(result2); + }); + + it("should work on array-like objects", function () { + var toUpperCase = lamb.generic(String.prototype.toUpperCase); + var testString = "hello world"; + var result = ["H", "E", "L", "L", "O", " ", "W", "O", "R", "L", "D"]; + + expect(lamb.flatMap(testString, toUpperCase)).toEqual(result); + expect(lamb.flatMapWith(toUpperCase)(testString)).toEqual(result); + }); + + it("should always return dense arrays", function () { + /* eslint-disable comma-spacing, no-sparse-arrays */ + var fn = function (v) { return [, v, ,]; }; + var arr = [1, , 3]; + /* eslint-enable comma-spacing, no-sparse-arrays */ + + var result = [void 0, 1, void 0, void 0, void 0, void 0, void 0, 3, void 0]; + + expect(lamb.flatMap(arr, fn)).toStrictArrayEqual(result); + expect(lamb.flatMapWith(fn)(arr)).toStrictArrayEqual(result); + }); + + it("should throw an exception if not supplied with a mapper function", function () { + expect(function () {lamb.flatMap([1, 2, 3]);}).toThrow(); + expect(function () {lamb.flatMapWith()([1, 2, 3]);}).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.flatMap).toThrow(); + expect(lamb.flatMapWith(lamb.identity)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.flatMap(null, lamb.identity); }).toThrow(); + expect(function () { lamb.flatMap(void 0, lamb.identity); }).toThrow(); + expect(function () { lamb.flatMapWith(lamb.identity)(null); }).toThrow(); + expect(function () { lamb.flatMapWith(lamb.identity)(void 0); }).toThrow(); + }); + + it("should return an empty array for every other value", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.flatMap(value, lamb.identity)).toEqual([]); + expect(lamb.flatMapWith(lamb.identity)(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/flatten.spec.js b/src/array/__tests__/flatten.spec.js new file mode 100644 index 0000000..6e3ad73 --- /dev/null +++ b/src/array/__tests__/flatten.spec.js @@ -0,0 +1,46 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("flatten", function () { + it("should return a deep flattened array", function () { + var a1 = [[1, [2, [3, 4, ["a", ["b", ["c"]]]]]]]; + var a2 = [1, 2, [3, 4, [5, 6]], 7, 8]; + + expect(lamb.flatten(a1)).toEqual([1, 2, 3, 4, "a", "b", "c"]); + expect(lamb.flatten(a2)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + }); + + it("shouldn't flatten an array that is a value in an object", function () { + var input = [["a", ["b", [{ c: ["d"] }]]]]; + + expect(lamb.flatten(input)).toEqual(["a", "b", { c: ["d"] }]); + }); + + it("should return an array copy of the source object if supplied with an array-like", function () { + expect(lamb.flatten("foo")).toEqual(["f", "o", "o"]); + }); + + it("should always return dense arrays", function () { + // eslint-disable-next-line no-sparse-arrays + var arr = [1, [2, , 4], , [6, , [8, , 10]]]; + var result = [1, 2, void 0, 4, void 0, 6, void 0, 8, void 0, 10]; + + expect(lamb.flatten(arr)).toStrictArrayEqual(result); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.flatten).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.flatten(null); }).toThrow(); + expect(function () { lamb.flatten(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.flatten(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/getIndex.spec.js b/src/array/__tests__/getIndex.spec.js new file mode 100644 index 0000000..85a0dcd --- /dev/null +++ b/src/array/__tests__/getIndex.spec.js @@ -0,0 +1,99 @@ +import * as lamb from "../.."; +import { zeroesAsIntegers, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("getIndex / getAt / head / last", function () { + var arr = [1, 2, 3, 4, 5]; + var arrCopy = arr.slice(); + var s = "abcde"; + var sparseArr = [, , 3, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays + var sparseArrCopy = sparseArr.slice(); + + afterEach(function () { + expect(arr).toStrictArrayEqual(arrCopy); + expect(sparseArr).toStrictArrayEqual(sparseArrCopy); + }); + + it("should retrieve the element at the given index in an array-like object", function () { + var getThird = lamb.getAt(2); + + expect(lamb.getIndex(arr, 1)).toBe(2); + expect(lamb.getIndex(s, 1)).toBe("b"); + expect(getThird(arr)).toBe(3); + expect(getThird(s)).toBe("c"); + expect(lamb.head(arr)).toBe(1); + expect(lamb.head(s)).toBe("a"); + expect(lamb.last(arr)).toBe(5); + expect(lamb.last(s)).toBe("e"); + }); + + it("should allow negative indexes", function () { + expect(lamb.getIndex(arr, -2)).toBe(4); + expect(lamb.getIndex(s, -2)).toBe("d"); + expect(lamb.getAt(-2)(arr)).toBe(4); + expect(lamb.getAt(-2)(s)).toBe("d"); + }); + + it("should work with sparse arrays", function () { + expect(lamb.getIndex(sparseArr, 2)).toBe(3); + expect(lamb.getIndex(sparseArr, -2)).toBe(3); + expect(lamb.getAt(2)(sparseArr)).toBe(3); + expect(lamb.getAt(-2)(sparseArr)).toBe(3); + expect(lamb.head(sparseArr)).toBe(void 0); + expect(lamb.last(sparseArr)).toBe(void 0); + }); + + it("should return `undefined` if the index is out of bounds", function () { + expect(lamb.getIndex(arr, -6)).toBeUndefined(); + expect(lamb.getIndex(arr, 66)).toBeUndefined(); + + expect(lamb.getAt(-6)(arr)).toBeUndefined(); + expect(lamb.getAt(66)(arr)).toBeUndefined(); + + expect(lamb.head([])).toBeUndefined(); + expect(lamb.last([])).toBeUndefined(); + }); + + it("should convert the `index` parameter to integer", function () { + zeroesAsIntegers.forEach(function (value) { + expect(lamb.getIndex(arr, value)).toBe(arr[0]); + expect(lamb.getAt(value)(arr)).toBe(arr[0]); + }); + + [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { + expect(lamb.getIndex(arr, value)).toBe(arr[1]); + expect(lamb.getAt(value)(arr)).toBe(arr[1]); + }); + + expect(lamb.getIndex(arr, new Date())).toBeUndefined(); + expect(lamb.getAt(new Date())(arr)).toBeUndefined(); + + expect(lamb.getIndex(arr)).toBe(arr[0]); + expect(lamb.getAt()(arr)).toBe(arr[0]); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.getIndex).toThrow(); + expect(lamb.getAt(1)).toThrow(); + expect(lamb.head).toThrow(); + expect(lamb.last).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.getIndex(null, 2); }).toThrow(); + expect(function () { lamb.getIndex(void 0, 2); }).toThrow(); + expect(function () { lamb.getAt(2)(null); }).toThrow(); + expect(function () { lamb.getAt(2)(void 0); }).toThrow(); + expect(function () { lamb.head(null); }).toThrow(); + expect(function () { lamb.last(void 0); }).toThrow(); + }); + + it("should return `undefined` for any other value", function () { + wannabeEmptyArrays.forEach(function (v) { + expect(lamb.getIndex(v, 2)).toBeUndefined(); + expect(lamb.getAt(2)(v)).toBeUndefined(); + expect(lamb.head(v)).toBeUndefined(); + expect(lamb.last(v)).toBeUndefined(); + }); + }); +}); diff --git a/src/array/__tests__/group.spec.js b/src/array/__tests__/group.spec.js new file mode 100644 index 0000000..9d7ff76 --- /dev/null +++ b/src/array/__tests__/group.spec.js @@ -0,0 +1,104 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("group / groupBy", function () { + var getCity = lamb.getKey("city"); + + var persons = [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "John", surname: "Doe", age: 40, city: "New York" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + { name: "Paolo", surname: "Bianchi", age: 15 } + ]; + + var personsByAgeGroup = { + under20: [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + { name: "Paolo", surname: "Bianchi", age: 15 } + ], + over20: [ + { name: "John", surname: "Doe", age: 40, city: "New York" } + ] + }; + + var personsByCity = { + "New York": [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "John", surname: "Doe", age: 40, city: "New York" } + ], + Rome: [ + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" } + ], + undefined: [ + { name: "Paolo", surname: "Bianchi", age: 15 } + ] + }; + + var splitByAgeGroup = function (person, idx, list) { + expect(list).toBe(persons); + expect(persons[idx]).toBe(person); + + return person.age > 20 ? "over20" : "under20"; + }; + + it("should build an object using the provided iteratee to group the list with the desired criterion", function () { + expect(lamb.group(persons, splitByAgeGroup)).toEqual(personsByAgeGroup); + expect(lamb.groupBy(splitByAgeGroup)(persons)).toEqual(personsByAgeGroup); + expect(lamb.group(persons, getCity)).toEqual(personsByCity); + expect(lamb.groupBy(getCity)(persons)).toEqual(personsByCity); + }); + + it("should work with array-like objects", function () { + var evenAndOdd = function (n) { return n % 2 === 0 ? "even" : "odd"; }; + var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + var result = { even: [2, 4, 6, 8, 10], odd: [1, 3, 5, 7, 9] }; + var argsTest = function () { + return lamb.group(arguments, evenAndOdd); + }; + + expect(argsTest.apply(null, numbers)).toEqual(result); + }); + + it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { + var arr = [1, , 3, void 0, 5]; // eslint-disable-line no-sparse-arrays + var expected = { false: [1, 3, 5], true: [void 0, void 0] }; + var r1 = lamb.group(arr, lamb.isUndefined); + var r2 = lamb.groupBy(lamb.isUndefined)(arr); + + expect(r1).toEqual(expected); + expect(r1.true).toStrictArrayEqual(expected.true); + expect(r2).toEqual(expected); + expect(r2.true).toStrictArrayEqual(expected.true); + }); + + it("should throw an exception if the iteratee isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.group(persons, value); }).toThrow(); + expect(function () { lamb.groupBy(value)(persons); }).toThrow(); + }); + + expect(function () { lamb.group(persons); }).toThrow(); + expect(function () { lamb.groupBy()(persons); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.group).toThrow(); + expect(lamb.groupBy(lamb.identity)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.group(null, lamb.identity); }).toThrow(); + expect(function () { lamb.group(void 0, lamb.identity); }).toThrow(); + expect(function () { lamb.groupBy(lamb.identity)(null); }).toThrow(); + expect(function () { lamb.groupBy(lamb.identity)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array and return an empty object", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.groupBy(lamb.identity)(value)).toEqual({}); + expect(lamb.group(value, lamb.identity)).toEqual({}); + }); + }); +}); diff --git a/src/array/__tests__/index.spec.js b/src/array/__tests__/index.spec.js new file mode 100644 index 0000000..93a927b --- /dev/null +++ b/src/array/__tests__/index.spec.js @@ -0,0 +1,89 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("index / indexBy", function () { + var getCity = lamb.getKey("city"); + + var persons = [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "John", surname: "Doe", age: 40, city: "New York" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + { name: "Paolo", surname: "Bianchi", age: 15 } + ]; + + var personsByAgeIndex = { + 12: { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + 15: { name: "Paolo", surname: "Bianchi", age: 15 }, + 18: { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + 40: { name: "John", surname: "Doe", age: 40, city: "New York" } + }; + + var personsByCityIndex = { + "New York": { name: "John", surname: "Doe", age: 40, city: "New York" }, + Rome: { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + undefined: { name: "Paolo", surname: "Bianchi", age: 15 } + }; + + it("should build a lookup table with keys generated by the iteratee and one value for each key from the original list", function () { + var indexByAge = function (person, idx, list) { + expect(list).toBe(persons); + expect(persons[idx]).toBe(person); + + return person.age; + }; + + expect(lamb.index(persons, indexByAge)).toEqual(personsByAgeIndex); + expect(lamb.indexBy(indexByAge)(persons)).toEqual(personsByAgeIndex); + }); + + it("should use the last evaluated value when the iteratee produces a duplicate key", function () { + expect(lamb.index(persons, getCity)).toEqual(personsByCityIndex); + expect(lamb.indexBy(getCity)(persons)).toEqual(personsByCityIndex); + }); + + it("should work with array-like objects", function () { + var result = { + h: "h", e: "e", l: "l", o: "o", " ": " ", w: "w", r: "r", d: "d" + }; + + expect(lamb.index("hello world", lamb.identity)).toEqual(result); + expect(lamb.indexBy(lamb.identity)("hello world")).toEqual(result); + }); + + it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { + var arr = [1, , 3, void 0, 5]; // eslint-disable-line no-sparse-arrays + var result = { 0: 1, 1: void 0, 2: 3, 3: void 0, 4: 5 }; + + expect(lamb.index(arr, lamb.getArgAt(1))).toStrictEqual(result); + expect(lamb.indexBy(lamb.getArgAt(1))(arr)).toStrictEqual(result); + }); + + it("should throw an exception if the iteratee isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.index(persons, value); }).toThrow(); + expect(function () { lamb.indexBy(value)(persons); }).toThrow(); + }); + + expect(function () { lamb.index(persons); }).toThrow(); + expect(function () { lamb.indexBy()(persons); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.index).toThrow(); + expect(lamb.indexBy(lamb.identity)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.index(null, lamb.identity); }).toThrow(); + expect(function () { lamb.index(void 0, lamb.identity); }).toThrow(); + expect(function () { lamb.indexBy(lamb.identity)(null); }).toThrow(); + expect(function () { lamb.indexBy(lamb.identity)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array and return an empty object", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.indexBy(lamb.identity)(value)).toEqual({}); + expect(lamb.index(value, lamb.identity)).toEqual({}); + }); + }); +}); diff --git a/src/array/__tests__/init.spec.js b/src/array/__tests__/init.spec.js new file mode 100644 index 0000000..ce1f83d --- /dev/null +++ b/src/array/__tests__/init.spec.js @@ -0,0 +1,39 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("init", function () { + it("should return a copy of the given array-like object without the last element", function () { + var arr = [1, 2, 3, 4, 5]; + + expect(lamb.init(arr)).toEqual([1, 2, 3, 4]); + expect(arr.length).toBe(5); + expect(lamb.init("hello")).toEqual(["h", "e", "l", "l"]); + }); + + it("should return an empty array when called with an empty array or an array holding only one element", function () { + expect(lamb.init([1])).toEqual([]); + expect(lamb.init([])).toEqual([]); + }); + + it("should always return dense arrays", function () { + // eslint-disable-next-line no-sparse-arrays + expect(lamb.init([1, , 3, , 4])).toStrictArrayEqual([1, void 0, 3, void 0]); + expect(lamb.init(Array(2))).toStrictArrayEqual([void 0]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.init).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.init(null); }).toThrow(); + expect(function () { lamb.init(void 0); }).toThrow(); + }); + + it("should return an empty array for every other value", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.init(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/insert.spec.js b/src/array/__tests__/insert.spec.js new file mode 100644 index 0000000..baeb670 --- /dev/null +++ b/src/array/__tests__/insert.spec.js @@ -0,0 +1,89 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays, zeroesAsIntegers } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("insert / insertAt", function () { + var arr = [1, 2, 3, 4, 5]; + var result1 = [1, 2, 3, 99, 4, 5]; + var result2 = [99, 1, 2, 3, 4, 5]; + var result3 = [1, 2, 3, 4, 5, 99]; + + afterEach(function () { + expect(arr).toEqual([1, 2, 3, 4, 5]); + }); + + it("should allow to insert an element in a copy of an array at the specified index", function () { + var r1 = lamb.insert(arr, 3, 99); + var r2 = lamb.insertAt(3, 99)(arr); + + expect(r1).toEqual(result1); + expect(r2).toEqual(result1); + expect(r1).not.toBe(arr); + expect(r2).not.toBe(arr); + }); + + it("should insert the element at the end of the array if the provided index is greater than its length", function () { + expect(lamb.insert(arr, 99, 99)).toEqual(result3); + expect(lamb.insertAt(99, 99)(arr)).toEqual(result3); + }); + + it("should allow the use of negative indexes", function () { + expect(lamb.insert(arr, -2, 99)).toEqual(result1); + expect(lamb.insertAt(-2, 99)(arr)).toEqual(result1); + }); + + it("should insert the element at the start of the array if provided with a negative index which is out of bounds", function () { + expect(lamb.insert(arr, -99, 99)).toEqual(result2); + expect(lamb.insertAt(-99, 99)(arr)).toEqual(result2); + }); + + it("should convert the index to integer following ECMA specifications", function () { + // see https://www.ecma-international.org/ecma-262/7.0/#sec-tointeger + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.insert(arr, value, 99)).toEqual(result2); + expect(lamb.insertAt(value, 99)(arr)).toEqual(result2); + }); + + [[3], -2, 3.6, 3.2, -2.8, -2.2, "-2", "3"].forEach(function (value) { + expect(lamb.insert(arr, value, 99)).toEqual(result1); + expect(lamb.insertAt(value, 99)(arr)).toEqual(result1); + }); + + expect(lamb.insert(arr, new Date(), 99)).toEqual(result3); + expect(lamb.insertAt(new Date(), 99)(arr)).toEqual(result3); + }); + + it("should work with array-like objects", function () { + var s = "12345"; + + expect(lamb.insert(s, -2, "99")).toEqual(result1.map(String)); + expect(lamb.insertAt(3, "99")(s)).toEqual(result1.map(String)); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.insert([1, , 3, 5], 3, 4)).toStrictArrayEqual([1, void 0, 3, 4, 5]); + expect(lamb.insertAt(3, 4)([1, , 3, 5])).toStrictArrayEqual([1, void 0, 3, 4, 5]); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.insert).toThrow(); + expect(lamb.insertAt(3, "99")).toThrow(); + }); + + it("should throw an exception if a `nil` value is passed in place of an array-like object", function () { + expect(function () { lamb.insert(null, 3, 99); }).toThrow(); + expect(function () { lamb.insert(void 0, 3, 99); }).toThrow(); + expect(function () { lamb.insertAt(3, 99)(null); }).toThrow(); + expect(function () { lamb.insertAt(3, 99)(void 0); }).toThrow(); + }); + + it("should consider every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.insert(value, 3, 99)).toEqual([99]); + expect(lamb.insertAt(3, 99)(value)).toEqual([99]); + }); + }); +}); diff --git a/src/array/__tests__/intersection.spec.js b/src/array/__tests__/intersection.spec.js new file mode 100644 index 0000000..bb95917 --- /dev/null +++ b/src/array/__tests__/intersection.spec.js @@ -0,0 +1,75 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("intersection", function () { + var a1 = [0, 1, 2, 3, 4, NaN]; + var a2 = [-0, 2, 2, 3, 4, 5]; + var a3 = [4, 5, 1, NaN]; + var a4 = [6, 7]; + + it("should return an array of every unique item present in all two given arrays", function () { + expect(lamb.intersection(a2, a3)).toEqual([4, 5]); + + expect(lamb.intersection( + a1, + lamb.intersection(a2, a3) + )).toEqual([4]); + + expect(lamb.intersection( + lamb.intersection(a1, a2), + a3 + )).toEqual([4]); + + expect(lamb.intersection( + lamb.intersection(a1, a2), + lamb.intersection(a3, a4) + )).toEqual([]); + }); + + it("should use the SameValueZero comparison and put in the result the first value found when comparing `0` with `-0`", function () { + expect(lamb.intersection(a1, a3)).toEqual([1, 4, NaN]); + expect(lamb.intersection(a2, a1)).toEqual([-0, 2, 3, 4]); + expect(Object.is(-0, lamb.intersection(a2, a1)[0])).toBe(true); + expect(lamb.intersection(a1, a2)).toEqual([0, 2, 3, 4]); + expect(Object.is(0, lamb.intersection(a1, a2)[0])).toBe(true); + }); + + it("should accept array-like objects", function () { + expect(lamb.intersection( + lamb.intersection("123", "23"), + lamb.intersection("432", "321") + )).toEqual(["2", "3"]); + expect(lamb.intersection( + ["1", "2"], + lamb.intersection("23", "42") + )).toEqual(["2"]); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + + expect(lamb.intersection( + lamb.intersection([1, , 3], [void 0, 3]), + [3, , 4] + )).toStrictArrayEqual([void 0, 3]); + + /* eslint-enable no-sparse-arrays */ + + expect(lamb.intersection(Array(2), Array(3))).toStrictArrayEqual([void 0]); + }); + + it("should throw an exception if any of the array-like is `null` or `undefined`", function () { + expect(function () { lamb.intersection(null, [1, 2]); }).toThrow(); + expect(function () { lamb.intersection([1, 2], void 0); }).toThrow(); + + expect(lamb.intersection).toThrow(); + }); + + it("should treat other values as empty arrays", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.intersection([2, 3], value)).toEqual([]); + expect(lamb.intersection(value, [2, 3])).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/isIn.spec.js b/src/array/__tests__/isIn.spec.js new file mode 100644 index 0000000..6cdab15 --- /dev/null +++ b/src/array/__tests__/isIn.spec.js @@ -0,0 +1,54 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("contains / isIn", function () { + var testArray = ["foo", NaN, 0, 12]; + + it("should verify if a value is contained in an array using the \"SameValueZero\" comparison", function () { + expect(lamb.contains(12)(testArray)).toBe(true); + expect(lamb.isIn(testArray, 12)).toBe(true); + expect(lamb.contains(NaN)(testArray)).toBe(true); + expect(lamb.isIn(testArray, NaN)).toBe(true); + expect(lamb.contains(0)(testArray)).toBe(true); + expect(lamb.isIn(testArray, 0)).toBe(true); + expect(lamb.contains(-0)(testArray)).toBe(true); + expect(lamb.isIn(testArray, -0)).toBe(true); + expect(lamb.contains(15)(testArray)).toBe(false); + expect(lamb.isIn(testArray, 15)).toBe(false); + + expect(lamb.contains()([1, 3])).toBe(false); + expect(lamb.contains()([1, 3, void 0])).toBe(true); + }); + + it("should work with array-like objects", function () { + expect(lamb.contains("f")("foo")).toBe(true); + expect(lamb.isIn("foo", "f")).toBe(true); + }); + + it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.contains(void 0)([1, , 3])).toBe(true); + expect(lamb.isIn([1, , 3], void 0)).toBe(true); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if called without the data argument or without arguments at all", function () { + expect(lamb.isIn).toThrow(); + expect(lamb.contains(1)).toThrow(); + expect(lamb.contains()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.isIn(null, 1); }).toThrow(); + expect(function () { lamb.isIn(void 0, 1); }).toThrow(); + expect(function () { lamb.contains(1)(null); }).toThrow(); + expect(function () { lamb.contains(1)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array and return false", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.contains(1)(value)).toBe(false); + expect(lamb.isIn(value, 1)).toBe(false); + }); + }); +}); diff --git a/src/array/__tests__/list.spec.js b/src/array/__tests__/list.spec.js new file mode 100644 index 0000000..e2d4c02 --- /dev/null +++ b/src/array/__tests__/list.spec.js @@ -0,0 +1,14 @@ +import * as lamb from "../.."; + +describe("list", function () { + it("should build an array with the received arguments", function () { + expect(lamb.list(123)).toEqual([123]); + expect(lamb.list(1, 2, 3)).toEqual([1, 2, 3]); + expect(lamb.list(null, void 0)).toEqual([null, void 0]); + expect(lamb.list(void 0)).toEqual([void 0]); + }); + + it("should return an empty array if no arguments are supplied", function () { + expect(lamb.list()).toEqual([]); + }); +}); diff --git a/src/array/__tests__/partition.spec.js b/src/array/__tests__/partition.spec.js new file mode 100644 index 0000000..85652ff --- /dev/null +++ b/src/array/__tests__/partition.spec.js @@ -0,0 +1,104 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("partition / partitionWith", function () { + // to check "truthy" and "falsy" values returned by predicates + var isVowel = function (char, idx, s) { + expect(s[idx]).toBe(char); + + return ~"aeiouAEIOU".indexOf(char); + }; + + it("should split an array in two lists; one with the elements satisfying the predicate, the other with the remaining elements", function () { + var persons = [ + { name: "Jane", surname: "Doe", active: false }, + { name: "John", surname: "Doe", active: true }, + { name: "Mario", surname: "Rossi", active: true }, + { name: "Paolo", surname: "Bianchi", active: false } + ]; + + var fooIsActive = function (el, idx, list) { + expect(list[idx]).toBe(el); + expect(list).toBe(persons); + + return el.active; + }; + + var result = [[ + { name: "John", surname: "Doe", active: true }, + { name: "Mario", surname: "Rossi", active: true } + ], [ + { name: "Jane", surname: "Doe", active: false }, + { name: "Paolo", surname: "Bianchi", active: false } + ]]; + + expect(lamb.partition(persons, fooIsActive)).toEqual(result); + expect(lamb.partitionWith(fooIsActive)(persons)).toEqual(result); + }); + + it("should return two lists even when the predicate is always satisfied or never satisfied", function () { + var isGreaterThanTen = lamb.isGT(10); + var arr1 = [1, 2, 3, 4, 5]; + var arr2 = [11, 12, 13, 14, 15]; + var arr3 = []; + var res1 = [[], arr1.concat()]; + var res2 = [arr2.concat(), []]; + var res3 = [[], []]; + + expect(lamb.partition(arr1, isGreaterThanTen)).toEqual(res1); + expect(lamb.partitionWith(isGreaterThanTen)(arr1)).toEqual(res1); + expect(lamb.partition(arr2, isGreaterThanTen)).toEqual(res2); + expect(lamb.partitionWith(isGreaterThanTen)(arr2)).toEqual(res2); + expect(lamb.partition(arr3, isGreaterThanTen)).toEqual(res3); + expect(lamb.partitionWith(isGreaterThanTen)(arr3)).toEqual(res3); + }); + + it("should work with array-like objects and treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + var testString = "Hello world"; + var result = [["e", "o", "o"], ["H", "l", "l", " ", "w", "r", "l", "d"]]; + + expect(lamb.partition(testString, isVowel)).toEqual(result); + expect(lamb.partitionWith(isVowel)(testString)).toEqual(result); + }); + + it("should always return dense arrays", function () { + var sparseArr = [1, , 3, , 5]; // eslint-disable-line no-sparse-arrays + + expect(lamb.partition(sparseArr, lamb.isUndefined)).toStrictArrayEqual([ + [void 0, void 0], [1, 3, 5] + ]); + expect(lamb.partitionWith(lamb.isUndefined)(sparseArr)).toStrictArrayEqual([ + [void 0, void 0], [1, 3, 5] + ]); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.partition).toThrow(); + expect(lamb.partitionWith(lamb.identity)).toThrow(); + }); + + it("should throw an exception when the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.partition([1, 2], value); }).toThrow(); + expect(function () { lamb.partitionWith(value)([1, 2]); }).toThrow(); + }); + + expect(function () { lamb.partition([1, 2]); }).toThrow(); + expect(function () { lamb.partitionWith()([1, 2]); }).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.partition(null); }).toThrow(); + expect(function () { lamb.partition(void 0); }).toThrow(); + expect(function () { lamb.partitionWith(lamb.identity)(null); }).toThrow(); + expect(function () { lamb.partitionWith(lamb.identity)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.partition(value, lamb.isType("Number"))).toEqual([[], []]); + expect(lamb.partitionWith(lamb.isType("Number"))(value)).toEqual([[], []]); + }); + }); +}); diff --git a/src/array/__tests__/pluck.spec.js b/src/array/__tests__/pluck.spec.js new file mode 100644 index 0000000..2983ceb --- /dev/null +++ b/src/array/__tests__/pluck.spec.js @@ -0,0 +1,67 @@ +import * as lamb from "../.."; +import { nonStrings, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("pluck / pluckKey", function () { + var arr = [ + { bar: 1, baz: 2, qux: 3 }, + { bar: 34, baz: 22, qux: 73 }, + { bar: 45, baz: 21, qux: 83 }, + { bar: 65, baz: 92, qux: 39 } + ]; + + var lists = [[1, 2], [3, 4, 5], [6]]; + var s = "hello"; + + it("should return an array of values taken from the given property of the source array elements", function () { + expect(lamb.pluck(arr, "baz")).toEqual([2, 22, 21, 92]); + expect(lamb.pluckKey("baz")(arr)).toEqual([2, 22, 21, 92]); + expect(lamb.pluck(lists, "length")).toEqual([2, 3, 1]); + expect(lamb.pluckKey("length")(lists)).toEqual([2, 3, 1]); + }); + + it("should work with array-like objects", function () { + expect(lamb.pluck(s, "length")).toEqual([1, 1, 1, 1, 1]); + expect(lamb.pluckKey("length")(s)).toEqual([1, 1, 1, 1, 1]); + }); + + it("should return a list of undefined values if no property is specified or if the property doesn't exist", function () { + var r = [void 0, void 0, void 0, void 0]; + + nonStrings.forEach(function (value) { + expect(lamb.pluck(arr, value)).toEqual(r); + expect(lamb.pluckKey(value)(arr)).toEqual(r); + }); + + expect(lamb.pluck(arr, "foo")).toEqual(r); + expect(lamb.pluckKey("foo")(arr)).toEqual(r); + + expect(lamb.pluck(arr)).toEqual(r); + expect(lamb.pluckKey()(arr)).toEqual(r); + }); + + it("should not skip deleted or unassigned indexes in the array-like and throw an exception", function () { + var a = [, { bar: 2 }]; // eslint-disable-line no-sparse-arrays + + expect(function () { lamb.pluck(a, "bar"); }).toThrow(); + expect(function () { lamb.pluckKey("bar")(a); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.pluck).toThrow(); + expect(lamb.pluckKey("bar")).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.pluck(null, "bar"); }).toThrow(); + expect(function () { lamb.pluck(void 0, "bar"); }).toThrow(); + expect(function () { lamb.pluckKey("bar")(null); }).toThrow(); + expect(function () { lamb.pluckKey("bar")(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.pluck(value, "bar")).toEqual([]); + expect(lamb.pluckKey("bar")(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/pullFrom.spec.js b/src/array/__tests__/pullFrom.spec.js new file mode 100644 index 0000000..8f7a598 --- /dev/null +++ b/src/array/__tests__/pullFrom.spec.js @@ -0,0 +1,99 @@ +import * as lamb from "../.."; +import { nonArrayLikes, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("pull / pullFrom", function () { + it("should create a copy of the given array without the specified values", function () { + var arr = ["foo", "bar", "baz"]; + + expect(lamb.pullFrom(arr, ["foo", "baz"])).toEqual(["bar"]); + expect(lamb.pull(["foo", "baz"])(arr)).toEqual(["bar"]); + + var r1 = lamb.pullFrom(arr, []); + var r2 = lamb.pull([])(arr); + + expect(r1).toEqual(arr); + expect(r1).not.toBe(arr); + expect(r2).toEqual(arr); + expect(r2).not.toBe(arr); + expect(arr).toEqual(["foo", "bar", "baz"]); + }); + + it("should use the \"SameValueZero\" comparison to perform the equality test", function () { + var obj = { a: 1 }; + var arr = [1, 2]; + var mixed = ["bar", obj, arr, "5", 0, NaN, "foo", 5]; + var result = ["bar", "foo", 5]; + var values = [obj, arr, NaN, -0, "5"]; + + expect(lamb.pullFrom(mixed, values)).toEqual(result); + expect(lamb.pull(values)(mixed)).toEqual(result); + }); + + it("should accept array-like objects as the list we want to remove values from", function () { + expect(lamb.pullFrom("hello", ["h", "l"])).toEqual(["e", "o"]); + expect(lamb.pull(["h", "l"])("hello")).toEqual(["e", "o"]); + }); + + it("should accept array-like objects as the list of values we want to remove", function () { + var arr = ["f", "b", "o", "o", "a", "r"]; + var result = ["f", "o", "o"]; + + expect(lamb.pullFrom(arr, "bar")).toEqual(result); + expect(lamb.pull("bar")(arr)).toEqual(result); + }); + + it("should consider non-array-likes received as the list of values as an empty array", function () { + var arr = [1, 2, 3]; + + nonArrayLikes.forEach(function (value) { + var r1 = lamb.pullFrom(arr, value); + var r2 = lamb.pull(value)(arr); + + expect(r1).toEqual(arr); + expect(r1).not.toBe(arr); + expect(r2).toEqual(arr); + expect(r2).not.toBe(arr); + }); + }); + + /* eslint-disable no-sparse-arrays */ + + it("should be able to remove non assigned indexes from sparse arrays", function () { + expect(lamb.pullFrom([1, , 3, , 5], [void 0])).toStrictArrayEqual([1, 3, 5]); + expect(lamb.pull([void 0])([1, , 3, , 5])).toStrictArrayEqual([1, 3, 5]); + }); + + it("should always return dense arrays", function () { + expect(lamb.pullFrom([1, , 3, , 5], [3])).toStrictArrayEqual([1, void 0, void 0, 5]); + expect(lamb.pull([3])([1, , 3, , 5])).toStrictArrayEqual([1, void 0, void 0, 5]); + }); + + it("should accept sparse arrays as the list of values to remove", function () { + expect(lamb.pullFrom([1, , 3, , 5], [1, , 1])).toStrictArrayEqual([3, 5]); + expect(lamb.pullFrom([1, void 0, 3, void 0, 5], [1, , 1])).toStrictArrayEqual([3, 5]); + expect(lamb.pull([1, , 1])([1, , 3, , 5])).toStrictArrayEqual([3, 5]); + expect(lamb.pull([1, void 0, 1])([1, , 3, , 5])).toStrictArrayEqual([3, 5]); + }); + + /* eslint-enable no-sparse-arrays */ + + it("should throw an exception if called without arguments", function () { + expect(lamb.pullFrom).toThrow(); + expect(lamb.pull()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.pullFrom(null, ["foo"]); }).toThrow(); + expect(function () { lamb.pullFrom(void 0, ["foo"]); }).toThrow(); + expect(function () { lamb.pull(["foo"])(null); }).toThrow(); + expect(function () { lamb.pull(["foo"])(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.pullFrom(value, ["lastIndex", "getDay", "valueOf"])).toEqual([]); + expect(lamb.pull(["lastIndex", "getDay", "valueOf"])(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/reduceRight.spec.js b/src/array/__tests__/reduceRight.spec.js new file mode 100644 index 0000000..88c0ae9 --- /dev/null +++ b/src/array/__tests__/reduceRight.spec.js @@ -0,0 +1,96 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("reduceRight / reduceRightWith", function () { + var arr = [1, 2, 3, 4, 5]; + var s = "12345"; + + afterEach(function () { + expect(arr).toEqual([1, 2, 3, 4, 5]); + }); + + it("should fold or reduce the provided array with the given accumulator function starting from the last element", function () { + var subtract = jest.fn(function (prev, current, idx, list) { + expect(list).toBe(arr); + expect(list[idx]).toBe(current); + + return prev - current; + }); + + var prevValues = [ + 5, 1, -2, -4, 0, -5, -9, -12, -14, 10, 5, 1, -2, -4, + 5, 1, -2, -4, 0, -5, -9, -12, -14, 10, 5, 1, -2, -4 + ]; + + expect(lamb.reduceRight(arr, subtract)).toBe(-5); + expect(lamb.reduceRight(arr, subtract, 0)).toBe(-15); + expect(lamb.reduceRight(arr, subtract, 10)).toBe(-5); + expect(lamb.reduceRightWith(subtract)(arr)).toBe(-5); + expect(lamb.reduceRightWith(subtract, 0)(arr)).toBe(-15); + expect(lamb.reduceRightWith(subtract, 10)(arr)).toBe(-5); + + expect(subtract).toHaveBeenCalledTimes(prevValues.length); + + prevValues.forEach(function (prevValue, idx) { + expect(subtract.mock.calls[idx][0]).toEqual(prevValue); + }); + }); + + it("should work with array-like objects", function () { + var fn = lamb.tapArgs(lamb.subtract, [lamb.identity, Number]); + + expect(lamb.reduceRight(s, fn)).toBe(-5); + expect(lamb.reduceRight(s, fn, 0)).toBe(-15); + expect(lamb.reduceRight(s, fn, 10)).toBe(-5); + expect(lamb.reduceRightWith(fn)(s)).toBe(-5); + expect(lamb.reduceRightWith(fn, 0)(s)).toBe(-15); + expect(lamb.reduceRightWith(fn, 10)(s)).toBe(-5); + }); + + it("should not skip deleted or unassigned elements, unlike the native method", function () { + var sum = jest.fn(function (a, b) { return a + b; }); + var sparseArr = Array(3); + + sparseArr[1] = 3; + + expect(lamb.reduceRight(sparseArr, sum, 0)).toEqual(NaN); + expect(lamb.reduceRightWith(sum, 0)(sparseArr)).toEqual(NaN); + expect(sum).toHaveBeenCalledTimes(6); + }); + + it("should build a function throwing an exception if the accumulator isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.reduceRight(arr, value, 0); }).toThrow(); + expect(function () { lamb.reduceRightWith(value, 0)(arr); }).toThrow(); + }); + + expect(function () { lamb.reduceRight(arr); }).toThrow(); + expect(function () { lamb.reduceRightWith()(arr); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.reduceRight).toThrow(); + expect(lamb.reduceRightWith(lamb.sum, 0)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.reduceRight(null, lamb.subtract, 0); }).toThrow(); + expect(function () { lamb.reduceRight(void 0, lamb.subtract, 0); }).toThrow(); + expect(function () { lamb.reduceRightWith(lamb.subtract, 0)(null); }).toThrow(); + expect(function () { lamb.reduceRightWith(lamb.subtract, 0)(void 0); }).toThrow(); + }); + + it("should throw an exception when supplied with an empty array-like without an initial value", function () { + expect(function () { lamb.reduceRight([], lamb.subtract); }).toThrow(); + expect(function () { lamb.reduceRight("", lamb.subtract); }).toThrow(); + expect(function () { lamb.reduceRightWith(lamb.subtract)([]); }).toThrow(); + expect(function () { lamb.reduceRightWith(lamb.subtract)(""); }).toThrow(); + }); + + it("should treat every other value as an empty array and return the initial value", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.reduceRight(value, lamb.subtract, 99)).toEqual(99); + expect(lamb.reduceRightWith(lamb.subtract, 99)(value)).toEqual(99); + }); + }); +}); diff --git a/src/array/__tests__/reverse.spec.js b/src/array/__tests__/reverse.spec.js new file mode 100644 index 0000000..1111d44 --- /dev/null +++ b/src/array/__tests__/reverse.spec.js @@ -0,0 +1,34 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("reverse", function () { + it("should reverse a copy of an array-like object", function () { + var arr = [1, 2, 3, 4, 5]; + var s = "hello"; + + expect(lamb.reverse(arr)).toEqual([5, 4, 3, 2, 1]); + expect(lamb.reverse(s)).toEqual(["o", "l", "l", "e", "h"]); + expect(arr).toEqual([1, 2, 3, 4, 5]); + }); + + it("should always return dense arrays", function () { + // eslint-disable-next-line no-sparse-arrays + expect(lamb.reverse([1, , 3])).toStrictArrayEqual([3, void 0, 1]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.reverse).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.reverse(null); }).toThrow(); + expect(function () { lamb.reverse(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.reverse(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/rotate.spec.js b/src/array/__tests__/rotate.spec.js new file mode 100644 index 0000000..f525c8f --- /dev/null +++ b/src/array/__tests__/rotate.spec.js @@ -0,0 +1,99 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays, zeroesAsIntegers } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("rotate / rotateBy", function () { + var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + afterEach(function () { + expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + }); + + it("should rotate the values of the array by the desired amount", function () { + var r1 = [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var r2 = [7, 8, 9, 10, 1, 2, 3, 4, 5, 6]; + + expect(lamb.rotate(arr, 1)).toEqual(r1); + expect(lamb.rotateBy(1)(arr)).toEqual(r1); + expect(lamb.rotate(arr, 4)).toEqual(r2); + expect(lamb.rotateBy(4)(arr)).toEqual(r2); + expect(lamb.rotate([1], 1)).toEqual([1]); + expect(lamb.rotateBy(1)([1])).toEqual([1]); + }); + + it("should return a copy of the source array if the `amount` is zero or equal to the array length or its additive inverse", function () { + expect(lamb.rotate(arr, 0)).toEqual(arr); + expect(lamb.rotateBy(0)(arr)).toEqual(arr); + expect(lamb.rotate(arr, 10)).toEqual(arr); + expect(lamb.rotateBy(10)(arr)).toEqual(arr); + expect(lamb.rotate(arr, -10)).toEqual(arr); + expect(lamb.rotateBy(-10)(arr)).toEqual(arr); + }); + + it("should accept values greater than the array length or less than its additive inverse and continue \"rotating\" values", function () { + var r1 = [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]; + var r2 = [5, 6, 7, 8, 9, 10, 1, 2, 3, 4]; + + expect(lamb.rotate(arr, 13)).toEqual(r1); + expect(lamb.rotateBy(13)(arr)).toEqual(r1); + expect(lamb.rotate(arr, -14)).toEqual(r2); + expect(lamb.rotateBy(-14)(arr)).toEqual(r2); + }); + + it("should convert to integer the value received as `amount`", function () { + var d = new Date(); + var r1 = [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { + expect(lamb.rotate(arr, value)).toEqual(r1); + expect(lamb.rotateBy(value)(arr)).toEqual(r1); + }); + + expect(lamb.rotate(arr, d)).toEqual(lamb.rotate(arr, d % arr.length)); + expect(lamb.rotateBy(d)(arr)).toEqual(lamb.rotate(arr, d % arr.length)); + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.rotate(arr, value)).toEqual(arr); + expect(lamb.rotateBy(value)(arr)).toEqual(arr); + }); + + expect(lamb.rotate(arr)).toEqual(arr); + expect(lamb.rotateBy()(arr)).toEqual(arr); + }); + + it("should work with array-like objects", function () { + var s = "123456789"; + var r = ["7", "8", "9", "1", "2", "3", "4", "5", "6"]; + + expect(lamb.rotate(s, 3)).toEqual(r); + expect(lamb.rotateBy(3)(s)).toEqual(r); + }); + + it("should always return dense arrays", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + var sparse = [1, , 3, ,]; + var r = [void 0, 3, void 0, 1]; + + expect(lamb.rotate(sparse, 3)).toStrictArrayEqual(r); + expect(lamb.rotateBy(3)(sparse)).toStrictArrayEqual(r); + }); + + it("should throw an exception if called without an array-like", function () { + expect(lamb.rotate).toThrow(); + expect(lamb.rotateBy(10)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.rotate(null, 3); }).toThrow(); + expect(function () { lamb.rotateBy(3)(null); }).toThrow(); + expect(function () { lamb.rotate(void 0, 5); }).toThrow(); + expect(function () { lamb.rotateBy(3)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.rotate(value, 2)).toEqual([]); + expect(lamb.rotateBy(1)(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/setIndex.spec.js b/src/array/__tests__/setIndex.spec.js new file mode 100644 index 0000000..811a54e --- /dev/null +++ b/src/array/__tests__/setIndex.spec.js @@ -0,0 +1,108 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays, zeroesAsIntegers } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("setIndex / setAt", function () { + var arr = [1, 2, 3, 4, 5]; + var arrCopy = arr.slice(); + var s = "abcde"; + var sparseArr = [, , 3, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays + var sparseArrCopy = sparseArr.slice(); + var sparseArrAsDense = [void 0, void 0, 3, void 0]; + + afterEach(function () { + expect(arr).toStrictArrayEqual(arrCopy); + expect(sparseArr).toStrictArrayEqual(sparseArrCopy); + }); + + it("should allow to set a value in a copy of the given array-like object", function () { + var r1 = [1, 2, 99, 4, 5]; + var r2 = ["z", "b", "c", "d", "e"]; + + expect(lamb.setIndex(arr, 2, 99)).toEqual(r1); + expect(lamb.setAt(2, 99)(arr)).toEqual(r1); + expect(lamb.setIndex(s, 0, "z")).toEqual(r2); + expect(lamb.setAt(0, "z")(s)).toEqual(r2); + }); + + it("should allow negative indexes", function () { + var newArr = lamb.setIndex(arr, -1, 99); + var newArr2 = lamb.setAt(-5, 99)(arr); + + expect(newArr).toEqual([1, 2, 3, 4, 99]); + expect(newArr2).toEqual([99, 2, 3, 4, 5]); + }); + + it("should always return dense arrays", function () { + var r1 = [void 0, void 0, 99, void 0]; + var r2 = [void 0, void 0, 3, 99]; + + expect(lamb.setIndex(sparseArr, 2, 99)).toStrictArrayEqual(r1); + expect(lamb.setIndex(sparseArr, -2, 99)).toStrictArrayEqual(r1); + expect(lamb.setIndex(sparseArr, -1, 99)).toStrictArrayEqual(r2); + expect(lamb.setIndex(sparseArr, 3, 99)).toStrictArrayEqual(r2); + expect(lamb.setAt(2, 99)(sparseArr)).toStrictArrayEqual(r1); + expect(lamb.setAt(-2, 99)(sparseArr)).toStrictArrayEqual(r1); + expect(lamb.setAt(-1, 99)(sparseArr)).toStrictArrayEqual(r2); + expect(lamb.setAt(3, 99)(sparseArr)).toStrictArrayEqual(r2); + }); + + it("should return an array copy of the array-like if the index is out of bounds", function () { + var newArr = lamb.setIndex(arr, 5, 99); + var newArr2 = lamb.setAt(-6, 99)(arr); + var newS = lamb.setAt(10, 99)(s); + var newSparseArr = lamb.setIndex(sparseArr, 5, 99); + var newSparseArr2 = lamb.setAt(5, 99)(sparseArr); + + expect(newArr).toEqual([1, 2, 3, 4, 5]); + expect(newArr2).toEqual([1, 2, 3, 4, 5]); + expect(newArr).not.toBe(arr); + expect(newArr2).not.toBe(arr); + expect(newS).toEqual(["a", "b", "c", "d", "e"]); + expect(newSparseArr).toEqual(sparseArrAsDense); + expect(newSparseArr).not.toBe(sparseArr); + expect(newSparseArr2).toEqual(sparseArrAsDense); + expect(newSparseArr2).not.toBe(sparseArr); + }); + + it("should convert the `index` parameter to integer", function () { + var r1 = [99, 2, 3, 4, 5]; + var r2 = [1, 99, 3, 4, 5]; + var r3 = [void 0, 2, 3, 4, 5]; + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.setIndex(arr, value, 99)).toEqual(r1); + expect(lamb.setAt(value, 99)(arr)).toEqual(r1); + }); + + [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { + expect(lamb.setIndex(arr, value, 99)).toEqual(r2); + expect(lamb.setAt(value, 99)(arr)).toEqual(r2); + }); + + expect(lamb.setIndex(arr, new Date(), 99)).toEqual(arr); + expect(lamb.setAt(new Date(), 99)(arr)).toEqual(arr); + + expect(lamb.setIndex(arr)).toEqual(r3); + expect(lamb.setAt()(arr)).toEqual(r3); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.setIndex).toThrow(); + expect(lamb.setAt(1, 1)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.setAt(0, 99)(null); }).toThrow(); + expect(function () { lamb.setAt(0, 99)(void 0); }).toThrow(); + expect(function () { lamb.setIndex(null, 0, 99); }).toThrow(); + expect(function () { lamb.setIndex(void 0, 0, 99); }).toThrow(); + }); + + it("should return an empty array for every other value", function () { + wannabeEmptyArrays.forEach(function (v) { + expect(lamb.setIndex(v, 2, 99)).toEqual([]); + expect(lamb.setAt(2, 99)(v)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/shallowFlatten.spec.js b/src/array/__tests__/shallowFlatten.spec.js new file mode 100644 index 0000000..20e1de8 --- /dev/null +++ b/src/array/__tests__/shallowFlatten.spec.js @@ -0,0 +1,47 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("shallowFlatten", function () { + it("should return a shallow flattened array", function () { + var a1 = [[1, [2, [3, ["a", ["b", ["c"]]]]]]]; + var a2 = [1, 2, [3, 4, [5, 6]], 7, 8]; + + expect(lamb.shallowFlatten(a1)).toEqual([1, [2, [3, ["a", ["b", ["c"]]]]]]); + expect(lamb.shallowFlatten(a2)).toEqual([1, 2, 3, 4, [5, 6], 7, 8]); + }); + + it("shouldn't flatten an array that is a value in an object", function () { + var input = ["a", "b", { c: ["d"] }]; + + expect(lamb.shallowFlatten(input)).toEqual(input); + }); + + it("should return an array copy of the source object if supplied with an array-like", function () { + expect(lamb.shallowFlatten("foo")).toEqual(["f", "o", "o"]); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + var arr = [1, [2, , 4], , [6, , [8, , 10]]]; + var result = [1, 2, void 0, 4, void 0, 6, void 0, [8, , 10]]; + /* eslint-enable no-sparse-arrays */ + + expect(lamb.shallowFlatten(arr)).toStrictArrayEqual(result); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.shallowFlatten).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.shallowFlatten(null); }).toThrow(); + expect(function () { lamb.shallowFlatten(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.shallowFlatten(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/someIn.spec.js b/src/array/__tests__/someIn.spec.js new file mode 100644 index 0000000..1451c3e --- /dev/null +++ b/src/array/__tests__/someIn.spec.js @@ -0,0 +1,98 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("some / someIn", function () { + var a1 = [1, 3, 5, 6, 7, 8]; + var a2 = [1, 3, 5, 7]; + var isEven = function (n, idx, list) { + expect(list[idx]).toBe(n); + + return n % 2 === 0; + }; + + // to check "truthy" and "falsy" values returned by predicates + var isVowel = function (char, idx, s) { + expect(s[idx]).toBe(char); + + return ~"aeiouAEIOU".indexOf(char); + }; + + it("should check if at least one element of an array satisfies the given predicate", function () { + expect(lamb.someIn(a1, isEven)).toBe(true); + expect(lamb.some(isEven)(a1)).toBe(true); + expect(lamb.someIn(a2, isEven)).toBe(false); + expect(lamb.some(isEven)(a2)).toBe(false); + }); + + it("should work with array-like objects", function () { + expect(lamb.someIn("134567", isEven)).toBe(true); + expect(lamb.some(isEven)("134567")).toBe(true); + expect(lamb.someIn("1357", isEven)).toBe(false); + expect(lamb.some(isEven)("1357")).toBe(false); + }); + + it("should always return false for empty array-likes", function () { + expect(lamb.someIn([], isEven)).toBe(false); + expect(lamb.some(isEven)([])).toBe(false); + expect(lamb.someIn("", isEven)).toBe(false); + expect(lamb.some(isEven)("")).toBe(false); + }); + + it("should stop calling the predicate as soon as a `true` value is returned", function () { + var isGreaterThan10 = jest.fn(function (n) { + return n > 10; + }); + var arr = [1, 3, 15, 10, 11]; + + expect(lamb.someIn(arr, isGreaterThan10)).toBe(true); + expect(isGreaterThan10).toHaveBeenCalledTimes(3); + + expect(lamb.some(isGreaterThan10)(arr)).toBe(true); + expect(isGreaterThan10).toHaveBeenCalledTimes(6); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.someIn("hello", isVowel)).toBe(true); + expect(lamb.some(isVowel)("hello")).toBe(true); + expect(lamb.someIn("hll", isVowel)).toBe(false); + expect(lamb.some(isVowel)("hll")).toBe(false); + }); + + it("should not skip deleted or unassigned elements, unlike the native method", function () { + var arr = Array(5); + + arr[2] = 99; + + expect(lamb.someIn(arr, lamb.isUndefined)).toBe(true); + expect(lamb.some(lamb.isUndefined)(arr)).toBe(true); + }); + + it("should throw an exception if the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.someIn(a1, value); }).toThrow(); + expect(function () { lamb.some(value)(a1); }).toThrow(); + }); + + expect(function () { lamb.someIn(a1); }).toThrow(); + expect(function () { lamb.some()(a1); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.some(isEven)).toThrow(); + expect(lamb.someIn).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.someIn(null, isEven); }).toThrow(); + expect(function () { lamb.someIn(void 0, isEven); }).toThrow(); + expect(function () { lamb.some(isEven)(null); }).toThrow(); + expect(function () { lamb.some(isEven)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array and return `false`", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.someIn(value, isEven)).toBe(false); + expect(lamb.some(isEven)(value)).toBe(false); + }); + }); +}); diff --git a/src/array/__tests__/sort.spec.js b/src/array/__tests__/sort.spec.js new file mode 100644 index 0000000..e3f405f --- /dev/null +++ b/src/array/__tests__/sort.spec.js @@ -0,0 +1,182 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("sort / sortWith", function () { + var descSorter = lamb.sorterDesc(); + + var persons = [ + { name: "John", surname: "Doe" }, + { name: "Mario", surname: "Rossi" }, + { name: "John", surname: "Moe" }, + { name: "Jane", surname: "Foe" } + ]; + + var personsByNameAsc = [ + { name: "Jane", surname: "Foe" }, + { name: "John", surname: "Doe" }, + { name: "John", surname: "Moe" }, + { name: "Mario", surname: "Rossi" } + ]; + + // eslint-disable-next-line id-length + var personsByNameAscSurnameDesc = [ + { name: "Jane", surname: "Foe" }, + { name: "John", surname: "Moe" }, + { name: "John", surname: "Doe" }, + { name: "Mario", surname: "Rossi" } + ]; + + var personsByNameReversed = personsByNameAsc.slice().reverse(); + + var numbers = [6, -0, 4, 5, 7, 4, 3, 4, 0, 1, 2]; + + var mixed = ["1", NaN, null, "10", null, {}, 1, void 0, NaN, false, [], "20", "15", -100]; + var mixedAsNumbersAsc = [-100, null, null, false, [], "1", 1, "10", "15", "20", NaN, {}, void 0, NaN]; + var mixedAsNumbersDesc = mixedAsNumbersAsc.slice().reverse(); + + var nameAsc = lamb.sorter(lamb.getKey("name")); + var nameDesc = lamb.sorterDesc(lamb.getKey("name")); + var surnameDesc = lamb.sorterDesc(lamb.getKey("surname")); + + it("should build a sorted copy of the provided array-like object", function () { + var sortedNumbersA = lamb.sort(numbers, [descSorter]); + var sortedNumbersB = lamb.sortWith([descSorter])(numbers); + var numbersResult = [7, 6, 5, 4, 4, 4, 3, 2, 1, 0, -0]; + + expect(sortedNumbersA).toEqual(numbersResult); + expect(sortedNumbersA).not.toBe(numbers); + expect(sortedNumbersB).toEqual(numbersResult); + expect(sortedNumbersB).not.toBe(numbers); + }); + + it("should perform a stable ascending sort", function () { + var myPersonsA = lamb.sort(persons, [nameAsc]); + var myPersonsB = lamb.sortWith([nameAsc])(persons); + + expect(myPersonsA).toEqual(personsByNameAsc); + expect(myPersonsB).toEqual(personsByNameAsc); + }); + + it("should perform descending sort as the reverse of an ascending one", function () { + var myPersonsA = lamb.sort(persons, [nameDesc]); + var myPersonsB = lamb.sortWith([nameDesc])(persons); + var mixedDescA = lamb.sort(mixed, [lamb.sorterDesc(Number)]); + var mixedDescB = lamb.sortWith([lamb.sorterDesc(Number)])(mixed); + + expect(myPersonsA).toEqual(personsByNameReversed); + expect(myPersonsB).toEqual(personsByNameReversed); + expect(mixedDescA).toEqual(mixedAsNumbersDesc); + expect(mixedDescB).toEqual(mixedAsNumbersDesc); + }); + + it("should be able to perform multi sorting with the specified criteria", function () { + var myPersonsA = lamb.sort(persons, [nameAsc, surnameDesc]); + var myPersonsB = lamb.sortWith([nameAsc, surnameDesc])(persons); + + expect(myPersonsA).toEqual(personsByNameAscSurnameDesc); + expect(myPersonsB).toEqual(personsByNameAscSurnameDesc); + }); + + it("should automatically build a default sorting criterion if supplied only with a reader", function () { + var stringNumbers = ["2", "1", "10", "5"]; + var stringNumbersAsc = ["1", "2", "5", "10"]; + + expect(lamb.sort(stringNumbers, [Number])).toEqual(stringNumbersAsc); + expect(lamb.sortWith([Number])(stringNumbers)).toEqual(stringNumbersAsc); + expect(lamb.sort(mixed, [Number])).toEqual(mixedAsNumbersAsc); + expect(lamb.sortWith([Number])(mixed)).toEqual(mixedAsNumbersAsc); + }); + + it("should treat values as strings if different types are received by the default comparer", function () { + var numbersAndStrings = [200, NaN, "0", -0, "10", "1", 1, "20", "15", -100, 2]; + var resultA = [-100, "0", -0, "1", 1, "10", "15", 2, "20", 200, NaN]; + var resultB = resultA.slice().reverse(); + + expect(lamb.sort(numbersAndStrings)).toEqual(resultA); + expect(lamb.sortWith()(numbersAndStrings)).toEqual(resultA); + expect(lamb.sort(numbersAndStrings, [descSorter])).toEqual(resultB); + expect(lamb.sortWith([descSorter])(numbersAndStrings)).toEqual(resultB); + }); + + it("should be able to use custom comparers", function () { + var localeSorter = new Intl.Collator("it"); + var localeSorterDesc = lamb.sorterDesc(lamb.identity, localeSorter.compare); + var chars = ["a", "è", "à", "é", "c", "b", "e"]; + var charsAsc = ["a", "à", "b", "c", "e", "é", "è"]; + var charsDesc = charsAsc.concat().reverse(); + + expect(lamb.sort(chars, [localeSorter])).toEqual(charsAsc); + expect(lamb.sortWith([localeSorter])(chars)).toEqual(charsAsc); + expect(lamb.sort(chars, [localeSorterDesc])).toEqual(charsDesc); + expect(lamb.sortWith([localeSorterDesc])(chars)).toEqual(charsDesc); + }); + + it("should use a default ascending sorter if no sorters are supplied", function () { + var sortedNumbersA = lamb.sort(numbers); + var sortedNumbersB = lamb.sortWith()(numbers); + var numbersResult = [-0, 0, 1, 2, 3, 4, 4, 4, 5, 6, 7]; + var stringResult = ["a", "b", "c", "d"]; + + expect(sortedNumbersA).toEqual(numbersResult); + expect(sortedNumbersB).toEqual(numbersResult); + expect(lamb.sort("cadb")).toEqual(stringResult); + expect(lamb.sortWith()("cadb")).toEqual(stringResult); + }); + + it("should use a default ascending sorter if the sorters parameter isn't an array or is an empty array", function () { + var numbersResult = [-0, 0, 1, 2, 3, 4, 4, 4, 5, 6, 7]; + var stringResult = ["a", "b", "c", "d"]; + + expect(lamb.sort(numbers, [])).toEqual(numbersResult); + expect(lamb.sortWith([])(numbers)).toEqual(numbersResult); + expect(lamb.sort("cadb", [])).toEqual(stringResult); + expect(lamb.sortWith([])("cadb")).toEqual(stringResult); + + nonFunctions.forEach(function (value) { + expect(lamb.sort(numbers, value)).toEqual(numbersResult); + expect(lamb.sortWith(value)(numbers)).toEqual(numbersResult); + expect(lamb.sort("cadb", value)).toEqual(stringResult); + expect(lamb.sortWith(value)("cadb")).toEqual(stringResult); + }); + }); + + it("should treat non-function values received as sorters as ascending sorters", function () { + var numbersResult = [-0, 0, 1, 2, 3, 4, 4, 4, 5, 6, 7]; + var numbersResult2 = [7, 6, 5, 4, 4, 4, 3, 2, 1, -0, 0]; // a descending sort with zeroes swapped + + nonFunctions.forEach(function (value) { + expect(lamb.sort(numbers, [value])).toEqual(numbersResult); + expect(lamb.sort(numbers, [descSorter, value])).toEqual(numbersResult2); + expect(lamb.sortWith([value])(numbers)).toEqual(numbersResult); + expect(lamb.sortWith([descSorter, value])(numbers)).toEqual(numbersResult2); + }); + }); + + it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { + var arr = ["b", , "c", void 0, "a"]; // eslint-disable-line no-sparse-arrays + var result = ["a", "b", "c", void 0, void 0]; + + expect(lamb.sort(arr, [String])).toStrictArrayEqual(result); + expect(lamb.sortWith([String])(arr)).toStrictArrayEqual(result); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.sort(null); }).toThrow(); + expect(function () { lamb.sort(void 0); }).toThrow(); + expect(function () { lamb.sortWith()(null); }).toThrow(); + expect(function () { lamb.sortWith()(void 0); }).toThrow(); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.sort).toThrow(); + expect(lamb.sortWith()).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.sort(value)).toEqual([]); + expect(lamb.sortWith()(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/sortedInsert.spec.js b/src/array/__tests__/sortedInsert.spec.js new file mode 100644 index 0000000..731b6bf --- /dev/null +++ b/src/array/__tests__/sortedInsert.spec.js @@ -0,0 +1,139 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("sortedInsert", function () { + var descSorter = lamb.sorterDesc(); + + // eslint-disable-next-line id-length + var personsByCaseInsensitiveNameAsc = [ + { name: "jane", surname: "doe" }, + { name: "John", surname: "Doe" }, + { name: "john", surname: "doe" }, + { name: "John", surname: "Moe" }, + { name: "Mario", surname: "Rossi" } + ]; + + var toLowerCase = lamb.invoker("toLowerCase"); + var getLowerCaseName = lamb.compose(toLowerCase, lamb.getKey("name")); + + it("should insert an element in a copy of a sorted array respecting the order", function () { + var expectedResult = [ + { name: "jane", surname: "doe" }, + { name: "John", surname: "Doe" }, + { name: "john", surname: "doe" }, + { name: "John", surname: "Moe" }, + { name: "marco", surname: "Rossi" }, + { name: "Mario", surname: "Rossi" } + ]; + + var result = lamb.sortedInsert( + personsByCaseInsensitiveNameAsc, + { name: "marco", surname: "Rossi" }, + [lamb.sorter(getLowerCaseName)] + ); + + expect(result).toEqual(expectedResult); + expect(result).not.toBe(personsByCaseInsensitiveNameAsc); + expect(personsByCaseInsensitiveNameAsc.length).toBe(5); + + // references are not broken + expect(personsByCaseInsensitiveNameAsc[0]).toBe(result[0]); + }); + + it("should be able to insert an element in a multi-sorted array", function () { + var expectedResult = [ + { name: "jane", surname: "doe" }, + { name: "John", surname: "Doe" }, + { name: "john", surname: "doe" }, + { name: "John", surname: "Foe" }, + { name: "John", surname: "Moe" }, + { name: "Mario", surname: "Rossi" } + ]; + + var getLowerCaseSurname = lamb.compose(toLowerCase, lamb.getKey("surname")); + + var result = lamb.sortedInsert( + personsByCaseInsensitiveNameAsc, + { name: "John", surname: "Foe" }, + [getLowerCaseName, getLowerCaseSurname] + ); + + expect(result).toEqual(expectedResult); + expect(result).not.toBe(personsByCaseInsensitiveNameAsc); + expect(personsByCaseInsensitiveNameAsc.length).toBe(5); + + // references are not broken + expect(personsByCaseInsensitiveNameAsc[0]).toBe(result[0]); + }); + + it("should allow inserting in a descending sorted array", function () { + expect(lamb.sortedInsert([3, 2, 1], 1.5, [descSorter])).toEqual([3, 2, 1.5, 1]); + expect(lamb.sortedInsert([3, 2, 1], 2, [descSorter])).toEqual([3, 2, 2, 1]); + }); + + it("should be able to insert values at the beginning and at the end of the array", function () { + var arr = [1, 2, 3]; + + expect(lamb.sortedInsert(arr, 0)).toEqual([0, 1, 2, 3]); + expect(lamb.sortedInsert(arr, 4)).toEqual([1, 2, 3, 4]); + }); + + it("should accept an empty list", function () { + expect(lamb.sortedInsert([], 1)).toEqual([1]); + }); + + it("should accept array-like objects", function () { + var s = "abdefg"; + var result = ["a", "b", "c", "d", "e", "f", "g"]; + + expect(lamb.sortedInsert(s, "c", [lamb.sorter()])).toEqual(result); + }); + + it("should automatically build a default sorting criterion if supplied only with a reader", function () { + expect(lamb.sortedInsert([1, 2, 3], "2.5", [Number])).toEqual([1, 2, "2.5", 3]); + }); + + it("should use a default ascending sorter if no sorters are supplied", function () { + expect(lamb.sortedInsert([1, 2, 3], 2.5)).toEqual([1, 2, 2.5, 3]); + }); + + it("should use a default ascending sorter if any of the received criteria isn't a function or a Sorter", function () { + nonFunctions.forEach(function (value) { + expect(lamb.sortedInsert([1, 2, 3], 2.5, [value])).toEqual([1, 2, 2.5, 3]); + }); + }); + + it("should return an array copy of the array-like if there is no element to insert", function () { + expect(lamb.sortedInsert([1, 2, 3])).toEqual([1, 2, 3]); + expect(lamb.sortedInsert("abc")).toEqual(["a", "b", "c"]); + }); + + it("should allow to insert `nil` values if the value is passed explicitly", function () { + expect(lamb.sortedInsert([1, 2, 3], null)).toEqual([1, 2, 3, null]); + expect(lamb.sortedInsert([1, 2, 3], void 0)).toEqual([1, 2, 3, void 0]); + }); + + it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + var arr = ["a", "b", "c", , ,]; + var result = ["a", "b", "c", void 0, void 0, "z"]; + + expect(lamb.sortedInsert(arr, "z", String)).toStrictArrayEqual(result); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () {lamb.sortedInsert(null, 99); }).toThrow(); + expect(function () {lamb.sortedInsert(void 0, 99); }).toThrow(); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.sortedInsert).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.sortedInsert(value, 99)).toEqual([99]); + }); + }); +}); diff --git a/src/array/__tests__/sorter.spec.js b/src/array/__tests__/sorter.spec.js new file mode 100644 index 0000000..acf3f50 --- /dev/null +++ b/src/array/__tests__/sorter.spec.js @@ -0,0 +1,69 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("sorter / sorterDesc", function () { + var myComparer = jest.fn().mockReturnValue("foo"); + var myReader = jest.fn(lamb.getKey("a")); + var foo = { a: 1 }; + var bar = { a: 2 }; + + afterEach(function () { + myComparer.mockClear(); + myReader.mockClear(); + }); + + it("should build a sorting criterion", function () { + var sorterAsc = lamb.sorter(); + var sorterDesc = lamb.sorterDesc(); + + expect(sorterAsc.isDescending).toBe(false); + expect(sorterDesc.isDescending).toBe(true); + expect(typeof sorterAsc.compare).toBe("function"); + expect(typeof sorterDesc.compare).toBe("function"); + expect(sorterAsc.compare.length).toBe(2); + expect(sorterDesc.compare.length).toBe(2); + expect(sorterAsc.compare("a", "b")).toBe(-1); + expect(sorterDesc.compare("a", "b")).toBe(-1); + }); + + it("should use a custom comparer if supplied with one", function () { + expect(lamb.sorter(null, myComparer).compare(foo, bar)).toBe("foo"); + }); + + it("should use a custom reader if supplied with one", function () { + lamb.sorter(myReader, myComparer).compare(foo, bar); + + expect(myReader).toHaveBeenCalledTimes(2); + expect(myReader.mock.calls[0][0]).toBe(foo); + expect(myReader.mock.calls[1][0]).toBe(bar); + expect(myComparer).toHaveBeenCalledTimes(1); + expect(myComparer.mock.calls[0]).toEqual([1, 2]); + }); + + it("should pass values directly to the comparer if there's no reader function or if the reader is the identity function", function () { + lamb.sorter(lamb.identity, myComparer).compare(foo, bar); + lamb.sorterDesc(null, myComparer).compare(foo, bar); + + expect(myComparer).toHaveBeenCalledTimes(2); + expect(myComparer.mock.calls[0][0]).toBe(foo); + expect(myComparer.mock.calls[0][1]).toBe(bar); + expect(myComparer.mock.calls[1][0]).toBe(foo); + expect(myComparer.mock.calls[1][1]).toBe(bar); + }); + + it("should build a default sorting criterion if the comparer isn't a function", function () { + nonFunctions.forEach(function (value) { + var sorterAsc = lamb.sorter(lamb.identity, value); + var sorterDesc = lamb.sorterDesc(lamb.identity, value); + + expect(sorterAsc.isDescending).toBe(false); + expect(sorterDesc.isDescending).toBe(true); + expect(typeof sorterAsc.compare).toBe("function"); + expect(typeof sorterDesc.compare).toBe("function"); + expect(sorterAsc.compare.length).toBe(2); + expect(sorterDesc.compare.length).toBe(2); + expect(sorterAsc.compare("a", "b")).toBe(-1); + expect(sorterDesc.compare("a", "b")).toBe(-1); + }); + }); +}); diff --git a/src/array/__tests__/tail.spec.js b/src/array/__tests__/tail.spec.js new file mode 100644 index 0000000..844edfd --- /dev/null +++ b/src/array/__tests__/tail.spec.js @@ -0,0 +1,40 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("tail", function () { + it("should return a copy of the given array-like object without the first element", function () { + var arr = [1, 2, 3, 4, 5]; + + expect(lamb.tail(arr)).toEqual([2, 3, 4, 5]); + expect(arr.length).toBe(5); + expect(lamb.tail("shell")).toEqual(["h", "e", "l", "l"]); + }); + + it("should return an empty array when called with an empty array or an array holding only one element", function () { + expect(lamb.tail([1])).toEqual([]); + expect(lamb.tail([])).toEqual([]); + }); + + it("should always return dense arrays", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + expect(lamb.tail([1, , 3, ,])).toStrictArrayEqual([void 0, 3, void 0]); + expect(lamb.tail(Array(2))).toStrictArrayEqual([void 0]); + expect(lamb.tail(Array(1))).toStrictArrayEqual([]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.tail).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.tail(null); }).toThrow(); + expect(function () { lamb.tail(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.tail(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/takeFrom.spec.js b/src/array/__tests__/takeFrom.spec.js new file mode 100644 index 0000000..c1f0d6b --- /dev/null +++ b/src/array/__tests__/takeFrom.spec.js @@ -0,0 +1,79 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays, zeroesAsIntegers } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("take / takeFrom", function () { + it("should retrieve the first `n` elements of an array or array-like object", function () { + expect(lamb.takeFrom(["a", "b"], 1)).toEqual(["a"]); + expect(lamb.take(3)([1, 2, 3, 4])).toEqual([1, 2, 3]); + }); + + it("should work with array-like objects", function () { + expect(lamb.takeFrom("abcd", 2)).toEqual(["a", "b"]); + expect(lamb.take(2)("abcd")).toEqual(["a", "b"]); + }); + + it("should accept a negative `n`", function () { + expect(lamb.takeFrom([1, 2, 3, 4], -1)).toEqual([1, 2, 3]); + expect(lamb.take(-3)("abcd")).toEqual(["a"]); + }); + + it("should return a copy of the source array when `n` is greater than or equal to the array-like length", function () { + expect(lamb.takeFrom(["a", "b"], 3)).toEqual(["a", "b"]); + expect(lamb.takeFrom([1, 2, 3, 4], 4)).toEqual([1, 2, 3, 4]); + expect(lamb.take(10)([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); + }); + + it("should return an empty array when `n` is 0 or less or equal than the additive inverse of the array-like length", function () { + expect(lamb.takeFrom([1, 2, 3, 4], 0)).toEqual([]); + expect(lamb.take(-4)([1, 2, 3, 4])).toEqual([]); + expect(lamb.take(-10)([1, 2, 3, 4])).toEqual([]); + }); + + it("should convert to integer the value received as `n`", function () { + var arr = [1, 2, 3, 4, 5]; + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.take(value)(arr)).toEqual([]); + expect(lamb.takeFrom(arr, value)).toEqual([]); + }); + + [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { + expect(lamb.take(value)(arr)).toEqual([1]); + expect(lamb.takeFrom(arr, value)).toEqual([1]); + }); + + expect(lamb.take(new Date())(arr)).toEqual(arr); + expect(lamb.takeFrom(arr, new Date())).toEqual(arr); + + expect(lamb.take()(arr)).toEqual([]); + expect(lamb.takeFrom(arr)).toEqual([]); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.takeFrom([1, , 3], 2)).toStrictArrayEqual([1, void 0]); + expect(lamb.take(2)([1, , 3])).toStrictArrayEqual([1, void 0]); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if called without the data argument or without arguments at all", function () { + expect(lamb.takeFrom).toThrow(); + expect(lamb.take(1)).toThrow(); + expect(lamb.take()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.takeFrom(null, 0); }).toThrow(); + expect(function () { lamb.takeFrom(void 0, 0); }).toThrow(); + expect(function () { lamb.take(0)(null); }).toThrow(); + expect(function () { lamb.take(0)(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.takeFrom(value, 0)).toEqual([]); + expect(lamb.take(0)(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/takeWhile.spec.js b/src/array/__tests__/takeWhile.spec.js new file mode 100644 index 0000000..39ac072 --- /dev/null +++ b/src/array/__tests__/takeWhile.spec.js @@ -0,0 +1,66 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("takeWhile", function () { + // to check "truthy" and "falsy" values returned by predicates + var isVowel = function (char, idx, s) { + expect(s[idx]).toBe(char); + + return ~"aeiouAEIOU".indexOf(char); + }; + + var isEven = function (n, idx, list) { + expect(list[idx]).toBe(n); + + return n % 2 === 0; + }; + var takeWhileIsEven = lamb.takeWhile(isEven); + + it("should build a function that takes the first n elements satisfying a predicate from an array or array-like object", function () { + expect(takeWhileIsEven([])).toEqual([]); + expect(takeWhileIsEven([1, 3, 5, 7])).toEqual([]); + expect(takeWhileIsEven([2, 3, 4, 6, 8])).toEqual([2]); + expect(takeWhileIsEven([2, 4, 6, 7, 8])).toEqual([2, 4, 6]); + expect(takeWhileIsEven([2, 4, 6, 8])).toEqual([2, 4, 6, 8]); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + var takeWhileisVowel = lamb.takeWhile(isVowel); + + expect(takeWhileisVowel("aiuola")).toEqual(["a", "i", "u", "o"]); + }); + + it("should build a function throwing an exception if the predicate isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.takeWhile(value)([1, 2]); }).toThrow(); + }); + + expect(function () { lamb.takeWhile()([1, 2]); }).toThrow(); + }); + + it("should always return dense arrays", function () { + var takeWhileIsUndefined = lamb.takeWhile(lamb.isUndefined); + var r = [void 0, void 0, void 0, void 0, void 0]; + + /* eslint-disable no-sparse-arrays */ + expect(takeWhileIsUndefined([, , void 0, , void 0, 5, 6])).toStrictArrayEqual(r); + expect(takeWhileIsEven([2, 4, , , 6])).toStrictArrayEqual([2, 4]); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if called without the data argument", function () { + expect(takeWhileIsEven).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { takeWhileIsEven(null); }).toThrow(); + expect(function () { takeWhileIsEven(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(takeWhileIsEven(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/transpose-zip.spec.js b/src/array/__tests__/transpose-zip.spec.js new file mode 100644 index 0000000..bf08e57 --- /dev/null +++ b/src/array/__tests__/transpose-zip.spec.js @@ -0,0 +1,93 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("transpose / zip", function () { + var a1 = [1, 2, 3, 4]; + var a2 = [5, 6, 7]; + var a3 = [8, 9]; + + var r1 = [[1], [2], [3], [4]]; + var r2 = [[1, 5], [2, 6], [3, 7]]; + var r3 = [[1, 5, 8], [2, 6, 9]]; + var r4 = [[5, 8], [6, 9]]; + + describe("transpose", function () { + it("should transpose a matrix", function () { + expect(lamb.transpose([])).toEqual([]); + expect(lamb.transpose([1, 2, 3])).toEqual([]); + expect(lamb.transpose(r1)).toEqual([a1]); + expect(lamb.transpose(r2)).toEqual([[1, 2, 3], [5, 6, 7]]); + expect(lamb.transpose(r3)).toEqual([[1, 2], [5, 6], [8, 9]]); + }); + + it("should work with array-like objects", function () { + var fn = function () { + return lamb.transpose(arguments); + }; + + expect(fn("abc", [1, 2, 3])).toEqual([["a", 1], ["b", 2], ["c", 3]]); + }); + + it("should build dense arrays when sparse ones are received", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + expect(lamb.transpose([[1, , 3], [4, 5, void 0], [6, 7, ,]])).toStrictArrayEqual([ + [1, 4, 6], + [void 0, 5, 7], + [3, void 0, void 0] + ]); + }); + + it("should throw an exception when a value in the array-like is `nil`", function () { + expect(function () { lamb.transpose([null, [1, 2, 3]]); }).toThrow(); + expect(function () { lamb.transpose([[1, 2, 3], null]); }).toThrow(); + expect(function () { lamb.transpose([void 0, [1, 2, 3]]); }).toThrow(); + expect(function () { lamb.transpose([[1, 2, 3], void 0]); }).toThrow(); + }); + + it("should consider other non-array-like values contained in the main array-like as empty arrays", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.transpose([[1, 2, 3], value])).toEqual([]); + expect(lamb.transpose([value, [1, 2, 3]])).toEqual([]); + }); + }); + }); + + describe("zip", function () { + it("should pair items with the same index in the received lists", function () { + expect(lamb.zip(a1, a2)).toEqual(r2); + expect(lamb.zip(a2, a3)).toEqual(r4); + expect(lamb.zip([], a1)).toEqual([]); + }); + + it("should work with array-like objects", function () { + expect(lamb.zip(a1, "abc")).toEqual([[1, "a"], [2, "b"], [3, "c"]]); + }); + + it("should build dense arrays when sparse ones are received", function () { + expect(lamb.zip(a2, Array(4))).toStrictArrayEqual([[5, void 0], [6, void 0], [7, void 0]]); + }); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.transpose(null); }).toThrow(); + expect(function () { lamb.transpose(void 0); }).toThrow(); + expect(function () { lamb.zip(null); }).toThrow(); + expect(function () { lamb.zip(void 0); }).toThrow(); + expect(function () { lamb.zip([1, 2], null); }).toThrow(); + expect(function () { lamb.zip([1, 2], void 0); }).toThrow(); + expect(function () { lamb.zip([], null); }).toThrow(); + expect(function () { lamb.zip([], void 0); }).toThrow(); + + expect(lamb.transpose).toThrow(); + expect(lamb.zip).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.transpose(value)).toEqual([]); + expect(lamb.zip(value, [1, 2])).toEqual([]); + expect(lamb.zip([1, 2], value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/union.spec.js b/src/array/__tests__/union.spec.js new file mode 100644 index 0000000..a06df01 --- /dev/null +++ b/src/array/__tests__/union.spec.js @@ -0,0 +1,120 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("union / unionBy", function () { + var a1 = [1, 2, 3]; + var a2 = [2, 3, 5]; + var a3 = [3, 4, 5, [2, 3]]; + var a4 = [2, 3, 4, [2, 3]]; + + var r1 = [1, 2, 3, 5, 4, [2, 3], [2, 3]]; + var r2 = [1, 2, 3, 5, 4, [2, 3]]; + var r3 = [1, 2, 3, 5]; + + var data = [ + [{ id: "1", name: "foo" }], + [{ id: "1", name: "Foo" }, { id: "2", name: "bar" }, { id: "3", name: "baz" }], + [{ id: "2", name: "Bar" }, { id: "1", name: "FOO" }] + ]; + + var dataUnionById = [ + { id: "1", name: "foo" }, + { id: "2", name: "bar" }, + { id: "3", name: "baz" } + ]; + + var unionByIdentity = lamb.unionBy(lamb.identity); + var unionAsStrings = lamb.unionBy(String); + var unionById = lamb.unionBy(lamb.getKey("id")); + + describe("unionBy", function () { + it("should build a function throwing an exception if the `iteratee` is not a function or if is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.unionBy(value)(a1, a2); }).toThrow(); + }); + + expect(function () { lamb.unionBy()(a1, a2); }).toThrow(); + }); + }); + + it("should return a list of every unique element present in the two given arrays", function () { + expect(lamb.union([], [])).toEqual([]); + expect(lamb.union([1, 2], [2, 3])).toEqual([1, 2, 3]); + expect(lamb.union( + lamb.union(a1, a2), + lamb.union(a3, a4) + )).toEqual(r1); + expect(unionAsStrings([], [])).toEqual([]); + expect(unionAsStrings([1, 2], [2, 3])).toEqual([1, 2, 3]); + expect(unionAsStrings( + unionAsStrings(a1, a2), + unionAsStrings(a3, a4) + )).toEqual(r2); + expect(unionById( + unionById(data[0], data[1]), + data[2] + )).toEqual(dataUnionById); + }); + + it("should ignore extra arguments", function () { + expect(lamb.union(a1, a2, a3)).toEqual(r3); + expect(unionAsStrings(a1, a2, a3)).toEqual(r3); + }); + + it("should work with array-like objects", function () { + expect(lamb.union("abc", "bcd")).toEqual(["a", "b", "c", "d"]); + expect(unionByIdentity("abc", "bcd")).toEqual(["a", "b", "c", "d"]); + }); + + it("should use the \"SameValueZero\" comparison and keep the first encountered value in case of equality", function () { + expect(lamb.union([-0, 2, 3, NaN], [1, 0, NaN, 1])).toEqual([-0, 2, 3, NaN, 1]); + expect(unionAsStrings([-0, 2, 3, NaN], [1, 0, NaN, 1])).toEqual([-0, 2, 3, NaN, 1]); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.union( + lamb.union([1, , 3], [3, 5]), + [6, 7]) + ).toStrictArrayEqual([1, void 0, 3, 5, 6, 7]); + expect(lamb.union( + [1, , 3], + lamb.union([3, 5], [void 0, 7]) + )).toStrictArrayEqual([1, void 0, 3, 5, 7]); + + expect(unionAsStrings( + unionAsStrings([1, , 3], [3, 5]), + [6, 7] + )).toStrictArrayEqual([1, void 0, 3, 5, 6, 7]); + expect(unionAsStrings( + [1, , 3], + unionAsStrings([3, 5], [void 0, 7]) + )).toStrictArrayEqual([1, void 0, 3, 5, 7]); + /* eslint-enable no-sparse-arrays */ + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.union(null, a1); }).toThrow(); + expect(function () { lamb.union(void 0, a1); }).toThrow(); + expect(function () { lamb.union(a1, null); }).toThrow(); + expect(function () { lamb.union(a1, void 0); }).toThrow(); + expect(function () { lamb.union(a1); }).toThrow(); + + expect(function () { unionAsStrings(null, a2); }).toThrow(); + expect(function () { unionAsStrings(void 0, a2); }).toThrow(); + expect(function () { unionAsStrings(a2, null); }).toThrow(); + expect(function () { unionAsStrings(a2, void 0); }).toThrow(); + expect(function () { unionAsStrings(a2); }).toThrow(); + + expect(lamb.union).toThrow(); + expect(unionAsStrings).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.union(value, [3, 5])).toEqual([3, 5]); + expect(lamb.union([3, 5], value)).toEqual([3, 5]); + }); + }); +}); diff --git a/src/array/__tests__/uniques.spec.js b/src/array/__tests__/uniques.spec.js new file mode 100644 index 0000000..9596ea8 --- /dev/null +++ b/src/array/__tests__/uniques.spec.js @@ -0,0 +1,114 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("uniques / uniquesBy", function () { + var data = [ + { id: "1", name: "foo" }, + { id: "1", name: "Foo" }, + { id: "2", name: "bar" }, + { id: "3", name: "baz" }, + { id: "2", name: "Bar" }, + { id: "1", name: "FOO" } + ]; + + var dataUniques = [ + { id: "1", name: "foo" }, + { id: "2", name: "bar" }, + { id: "3", name: "baz" } + ]; + + var uniquesByIdentity = lamb.uniquesBy(lamb.identity); + + describe("uniques", function () { + it("should return the unique elements of an array of simple values", function () { + expect(lamb.uniques([0, 2, 3, 4, 0, 4, 3, 5, 2, 1, 1])).toEqual([0, 2, 3, 4, 5, 1]); + expect(lamb.uniques(["foo", "bar", "bar", "baz"])).toEqual(["foo", "bar", "baz"]); + expect(lamb.uniques(Array(3))).toEqual([void 0]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.uniques).toThrow(); + }); + }); + + describe("uniquesBy", function () { + it("should use the provided iteratee to extract the values to compare", function () { + var chars = ["b", "A", "r", "B", "a", "z"]; + + expect(lamb.uniquesBy(lamb.invoker("toUpperCase"))(chars)).toEqual(["b", "A", "r", "z"]); + expect(lamb.uniquesBy(lamb.getKey("id"))(data)).toEqual(dataUniques); + }); + + it("should build a function throwing an exception if the itereatee isn't a function or if is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.uniquesBy(value)([1, 2, 3]); }).toThrow(); + }); + + expect(function () { lamb.uniquesBy()([1, 2, 3]); }).toThrow(); + }); + }); + + it("should use the SameValueZero comparison", function () { + expect(lamb.uniques([0, 1, 2, NaN, 1, 2, -0, NaN])).toEqual([0, 1, 2, NaN]); + }); + + it("should return a copy of the source array if it's empty or already contains unique values", function () { + var a1 = []; + var r1 = lamb.uniques(a1); + var a2 = [1, 2, 3, 4, 5]; + var r2 = lamb.uniques(a2); + + expect(r1).toEqual([]); + expect(r1).not.toBe(a1); + expect(r2).toEqual(a2); + expect(r2).not.toBe(a2); + }); + + it("should work with array-like objects", function () { + var s = "hello world"; + var r = ["h", "e", "l", "o", " ", "w", "r", "d"]; + + expect(lamb.uniques(s)).toEqual(r); + expect(uniquesByIdentity(s)).toEqual(r); + }); + + it("should prefer the first encountered value if two values are considered equal", function () { + expect(Object.is(0, lamb.uniques([0, -0])[0])).toBe(true); + expect(lamb.uniques([2, -0, 3, 3, 0, 1])).toEqual([2, -0, 3, 1]); + + var r = lamb.uniquesBy(lamb.getKey("id"))(data); + + expect(r).toEqual(dataUniques); + expect(r[0]).toBe(data[0]); + expect(r[1]).toBe(data[2]); + }); + + it("should always return dense arrays", function () { + /* eslint-disable no-sparse-arrays */ + var a1 = [1, , 1, 3]; + var a2 = [1, , 1, void 0, 3]; + /* eslint-enable no-sparse-arrays */ + + var r = [1, void 0, 3]; + + expect(lamb.uniques(a1)).toStrictArrayEqual(r); + expect(lamb.uniques(a2)).toStrictArrayEqual(r); + expect(uniquesByIdentity(a1)).toStrictArrayEqual(r); + expect(uniquesByIdentity(a2)).toStrictArrayEqual(r); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.uniques(null); }).toThrow(); + expect(function () { lamb.uniques(void 0); }).toThrow(); + expect(function () { uniquesByIdentity(null); }).toThrow(); + expect(function () { uniquesByIdentity(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.uniques(value)).toEqual([]); + expect(uniquesByIdentity(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/updateIndex.spec.js b/src/array/__tests__/updateIndex.spec.js new file mode 100644 index 0000000..13380a1 --- /dev/null +++ b/src/array/__tests__/updateIndex.spec.js @@ -0,0 +1,124 @@ +import * as lamb from "../.."; +import { + nonFunctions, + wannabeEmptyArrays, + zeroesAsIntegers +} from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("updateIndex / updateAt", function () { + var arr = [1, 2, 3, 4, 5]; + var arrCopy = arr.slice(); + var fn99 = lamb.always(99); + var s = "abcde"; + var sparseArr = [, , 3, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays + var sparseArrCopy = sparseArr.slice(); + var sparseArrAsDense = [void 0, void 0, 3, void 0]; + + afterEach(function () { + expect(arr).toStrictArrayEqual(arrCopy); + expect(sparseArr).toStrictArrayEqual(sparseArrCopy); + }); + + it("should allow to update a value in a copy of the given array-like object with the provided function", function () { + var inc = jest.fn(function (n) { return n + 1; }); + + expect(lamb.updateIndex(arr, 2, inc)).toEqual([1, 2, 4, 4, 5]); + expect(lamb.updateAt(2, inc)(arr)).toEqual([1, 2, 4, 4, 5]); + expect(inc).toHaveBeenCalledTimes(2); + expect(inc.mock.calls[0].length).toBe(1); + expect(inc.mock.calls[1].length).toBe(1); + expect(inc.mock.calls[0]).toEqual([3]); + expect(inc.mock.calls[1]).toEqual([3]); + expect(lamb.updateIndex(s, 0, lamb.always("z"))).toEqual(["z", "b", "c", "d", "e"]); + expect(lamb.updateAt(0, lamb.always("z"))(s)).toEqual(["z", "b", "c", "d", "e"]); + expect(lamb.updateAt(1, fn99)([1, void 0, 3])).toEqual([1, 99, 3]); + }); + + it("should allow negative indexes", function () { + var newArr = lamb.updateIndex(arr, -1, fn99); + var newArr2 = lamb.updateAt(-5, fn99)(arr); + + expect(newArr).toEqual([1, 2, 3, 4, 99]); + expect(newArr2).toEqual([99, 2, 3, 4, 5]); + }); + + it("should always return dense arrays", function () { + var r1 = [void 0, void 0, 99, void 0]; + var r2 = [void 0, void 0, 3, 99]; + + expect(lamb.updateIndex(sparseArr, 2, fn99)).toStrictArrayEqual(r1); + expect(lamb.updateIndex(sparseArr, -2, fn99)).toStrictArrayEqual(r1); + expect(lamb.updateIndex(sparseArr, -1, fn99)).toStrictArrayEqual(r2); + expect(lamb.updateIndex(sparseArr, 3, fn99)).toStrictArrayEqual(r2); + expect(lamb.updateAt(2, fn99)(sparseArr)).toStrictArrayEqual(r1); + expect(lamb.updateAt(-2, fn99)(sparseArr)).toStrictArrayEqual(r1); + expect(lamb.updateAt(-1, fn99)(sparseArr)).toStrictArrayEqual(r2); + expect(lamb.updateAt(3, fn99)(sparseArr)).toStrictArrayEqual(r2); + }); + + it("should return an array copy of the array-like if the index is out of bounds", function () { + var newArr = lamb.updateIndex(arr, 5, fn99); + var newArr2 = lamb.updateAt(-6, fn99)(arr); + var newS = lamb.updateAt(10, fn99)(s); + var newSparseArr = lamb.updateIndex(sparseArr, 5, fn99); + var newSparseArr2 = lamb.updateAt(5, fn99)(sparseArr); + + expect(newArr).toEqual([1, 2, 3, 4, 5]); + expect(newArr2).toEqual([1, 2, 3, 4, 5]); + expect(newArr).not.toBe(arr); + expect(newArr2).not.toBe(arr); + expect(newS).toEqual(["a", "b", "c", "d", "e"]); + expect(newSparseArr).toEqual(sparseArrAsDense); + expect(newSparseArr).not.toBe(sparseArr); + expect(newSparseArr2).toEqual(sparseArrAsDense); + expect(newSparseArr2).not.toBe(sparseArr); + }); + + it("should convert the `index` parameter to integer", function () { + var r1 = [99, 2, 3, 4, 5]; + var r2 = [1, 99, 3, 4, 5]; + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.updateIndex(arr, value, fn99)).toEqual(r1); + expect(lamb.updateAt(value, fn99)(arr)).toEqual(r1); + }); + + [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { + expect(lamb.updateIndex(arr, value, fn99)).toEqual(r2); + expect(lamb.updateAt(value, fn99)(arr)).toEqual(r2); + }); + + expect(lamb.updateIndex(arr, new Date(), fn99)).toEqual(arr); + expect(lamb.updateAt(new Date(), fn99)(arr)).toEqual(arr); + }); + + it("should throw an exception if the `updater` isn't a function or if is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.updateIndex(arr, 0, value); }).toThrow(); + expect(function () { lamb.updateAt(0, value)(arr); }).toThrow(); + }); + + expect(function () { lamb.updateIndex(arr, 0); }).toThrow(); + expect(function () { lamb.updateAt(0)(arr); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.updateIndex).toThrow(); + expect(lamb.updateAt(1, fn99)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.updateIndex(null, 0, fn99); }).toThrow(); + expect(function () { lamb.updateIndex(void 0, 0, fn99); }).toThrow(); + expect(function () { lamb.updateAt(0, fn99)(null); }).toThrow(); + expect(function () { lamb.updateAt(0, fn99)(void 0); }).toThrow(); + }); + + it("should return an empty array for every other value", function () { + wannabeEmptyArrays.forEach(function (v) { + expect(lamb.updateIndex(v, 2, fn99)).toEqual([]); + expect(lamb.updateAt(2, fn99)(v)).toEqual([]); + }); + }); +}); diff --git a/src/array/__tests__/zipWithIndex.spec.js b/src/array/__tests__/zipWithIndex.spec.js new file mode 100644 index 0000000..5657f09 --- /dev/null +++ b/src/array/__tests__/zipWithIndex.spec.js @@ -0,0 +1,39 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("zipWithIndex", function () { + it("should pair the received values with their index", function () { + expect(lamb.zipWithIndex([])).toEqual([]); + expect(lamb.zipWithIndex([1, 2, 3, 4])).toEqual([[1, 0], [2, 1], [3, 2], [4, 3]]); + }); + + it("should work with array-like objects", function () { + expect(lamb.zipWithIndex("abcd")).toEqual([["a", 0], ["b", 1], ["c", 2], ["d", 3]]); + }); + + it("should not skip deleted or unassigned indexes in sparse arrays", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + expect(lamb.zipWithIndex([1, , 3, ,])).toStrictArrayEqual([ + [1, 0], + [void 0, 1], + [3, 2], + [void 0, 3] + ]); + }); + + it("should throw an error if no arguments are supplied", function () { + expect(lamb.zipWithIndex).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.zipWithIndex(null); }).toThrow(); + expect(function () { lamb.zipWithIndex(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.zipWithIndex(value)).toEqual([]); + }); + }); +}); diff --git a/src/array/append.js b/src/array/append.js new file mode 100644 index 0000000..2c292b8 --- /dev/null +++ b/src/array/append.js @@ -0,0 +1,24 @@ +import _curry2 from "../privates/_curry2"; +import appendTo from "./appendTo"; + +/** + * A curried version of {@link module:lamb.appendTo|appendTo} that uses the value to append + * to build a function expecting the array-like object to act upon. + * @example + * var arr = [1, 2, 3, 4]; + * + * _.append(5)(arr) // => [1, 2, 3, 4, 5] + * _.append([5])(arr) // => [1, 2, 3, 4, [5]] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.appendTo|appendTo} + * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} + * @since 0.44.0 + * @param {*} value + * @returns {Function} + */ +var append = _curry2(appendTo, true); + +export default append; diff --git a/src/array/appendTo.js b/src/array/appendTo.js new file mode 100644 index 0000000..fd648e9 --- /dev/null +++ b/src/array/appendTo.js @@ -0,0 +1,24 @@ +import slice from "../core/slice"; + +/** + * Appends the given value at the end of a copy of the provided array-like object. + * @example + * var arr = [1, 2, 3, 4]; + * + * _.appendTo(arr, 5) // => [1, 2, 3, 4, 5] + * _.appendTo(arr, [5]) // => [1, 2, 3, 4, [5]] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.append|append} + * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} + * @since 0.44.0 + * @param {ArrayLike} arrayLike + * @param {*} value + * @returns {Array} + */ +function appendTo (arrayLike, value) { + return slice(arrayLike, 0, arrayLike.length).concat([value]); +} + +export default appendTo; diff --git a/src/array/contains.js b/src/array/contains.js new file mode 100644 index 0000000..1b33c80 --- /dev/null +++ b/src/array/contains.js @@ -0,0 +1,24 @@ +import _curry2 from "../privates/_curry2"; +import isIn from "./isIn"; + +/** + * Builds a predicate to check if an array-like object contains the given value.
+ * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can + * check for NaN, but 0 and -0 are the same value.
+ * See also {@link module:lamb.isIn|isIn} for an uncurried version. + * @example + * var containsNaN = _.contains(NaN); + * + * containsNaN([0, 1, 2, 3, NaN]) // => true + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.isIn|isIn} + * @since 0.13.0 + * @param {*} value + * @returns {Function} + */ +var contains = _curry2(isIn, true); + +export default contains; diff --git a/src/array/count.js b/src/array/count.js new file mode 100644 index 0000000..2b45a26 --- /dev/null +++ b/src/array/count.js @@ -0,0 +1,32 @@ +import _groupWith from "../privates/_groupWith"; + +/** + * Transforms an array-like object in a lookup table with the keys generated by the provided + * iteratee, having as values the count of matches for the key. + * @example + * var persons = [ + * {"name": "Jane", "age": 12}, + * {"name": "John", "age": 40}, + * {"name": "Mario", "age": 17}, + * {"name": "Paolo", "age": 15} + * ]; + * var getAgeStatus = function (person) { return person.age >= 18 ? "adult" : "minor"; }; + * + * _.count(persons, getAgeStatus) // => {"adult": 1, "minor": 3} + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.21.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Object} + */ +var count = _groupWith(function (a) { + return a ? ++a : 1; +}); + +export default count; diff --git a/src/array/countBy.js b/src/array/countBy.js new file mode 100644 index 0000000..57d9aaf --- /dev/null +++ b/src/array/countBy.js @@ -0,0 +1,31 @@ +import _curry2 from "../privates/_curry2"; +import count from "./count"; + +/** + * A curried version of {@link module:lamb.count|count} that uses the provided iteratee to + * build a function expecting the array-like object to act upon. + * @example + * var persons = [ + * {"name": "Jane", "city": "New York"}, + * {"name": "John", "city": "New York"}, + * {"name": "Mario", "city": "Rome"}, + * {"name": "Paolo"} + * ]; + * var getCityOrUnknown = _.adapter([_.getKey("city"), _.always("Unknown")]); + * var countByCity = _.countBy(getCityOrUnknown); + * + * countByCity(persons) // => {"New York": 2, "Rome": 1, "Unknown": 1} + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.count|count} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.21.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} + */ +var countBy = _curry2(count, true); + +export default countBy; diff --git a/src/array/difference.js b/src/array/difference.js new file mode 100644 index 0000000..90b5ba8 --- /dev/null +++ b/src/array/difference.js @@ -0,0 +1,36 @@ +import partial from "../core/partial"; +import filter from "./filter"; +import isIn from "./isIn"; +import not from "../logic/not"; +import uniques from "./uniques"; + +/** + * Returns an array of unique items present only in the first of the two given + * array-like objects. To determine uniqueness the function uses the + * ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * @example + * var a1 = [1, 2, 1, 3, 4]; + * var a2 = [2, 4, 5, 6]; + * var a3 = [3, 4, 5, 2, 1]; + * + * _.difference(a1, a2) // => [1, 3] + * _.difference(a2, a3) // => [6] + * _.difference(a1, a3) // => [] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.intersection|intersection} + * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} + * @see {@link module:lamb.pull|pull}, {@link module:lamb.pullFrom|pullFrom} + * @since 0.6.0 + * @param {ArrayLike} arrayLike + * @param {ArrayLike} other + * @returns {Array} + */ +function difference (arrayLike, other) { + var isNotInOther = partial(not(isIn), [other]); + + return uniques(filter(arrayLike, isNotInOther)); +} + +export default difference; diff --git a/src/array/drop.js b/src/array/drop.js new file mode 100644 index 0000000..a8c2915 --- /dev/null +++ b/src/array/drop.js @@ -0,0 +1,26 @@ +import _curry2 from "../privates/_curry2"; +import dropFrom from "./dropFrom"; + +/** + * A curried version of {@link module:lamb.dropFrom|dropFrom} that expects the number of elements + * to drop to build a function waiting for the list to take the elements from.
+ * See the note and examples for {@link module:lamb.dropFrom|dropFrom} about passing a + * negative n. + * @example + * var drop2 = _.drop(2); + * + * drop2([1, 2, 3, 4, 5]) // => [3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @function + * @since 0.5.0 + * @see {@link module:lamb.dropFrom|dropFrom} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @param {Number} n + * @returns {Function} + */ +var drop = _curry2(dropFrom, true); + +export default drop; diff --git a/src/array/dropFrom.js b/src/array/dropFrom.js new file mode 100644 index 0000000..073fcb8 --- /dev/null +++ b/src/array/dropFrom.js @@ -0,0 +1,28 @@ +import slice from "../core/slice"; + +/** + * Builds an array without the first n elements of the given array or array-like object. + * Note that, being this only a shortcut for a specific use case of {@link module:lamb.slice|slice}, + * n can be a negative number. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.dropFrom(arr, 2) // => [3, 4, 5] + * _.dropFrom(arr, -1) // => [5] + * _.dropFrom(arr, -10) // => [1, 2, 3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @since 0.51.0 + * @param {ArrayLike} arrayLike + * @param {Number} n + * @returns {Array} + */ +function dropFrom (arrayLike, n) { + return slice(arrayLike, n, arrayLike.length); +} + +export default dropFrom; diff --git a/src/array/dropWhile.js b/src/array/dropWhile.js new file mode 100644 index 0000000..821ae78 --- /dev/null +++ b/src/array/dropWhile.js @@ -0,0 +1,29 @@ +import _getNumConsecutiveHits from "../privates/_getNumConsecutiveHits"; +import slice from "../core/slice"; + +/** + * Builds a function that drops the first n elements satisfying a predicate + * from an array or array-like object. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var dropWhileIsEven = _.dropWhile(isEven); + * + * dropWhileIsEven([2, 4, 6, 8]) // => [] + * dropWhileIsEven([2, 4, 7, 8]) // => [7, 8] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.takeWhile|takeWhile} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @since 0.5.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +function dropWhile (predicate) { + return function (arrayLike) { + return slice(arrayLike, _getNumConsecutiveHits(arrayLike, predicate), arrayLike.length); + }; +} + +export default dropWhile; diff --git a/src/array/every.js b/src/array/every.js new file mode 100644 index 0000000..2edc466 --- /dev/null +++ b/src/array/every.js @@ -0,0 +1,27 @@ +import _curry2 from "../privates/_curry2"; +import everyIn from "./everyIn"; + +/** + * A curried version of {@link module:lamb.everyIn|everyIn} that expects a predicate + * to build a function waiting for the array-like to act upon. + * @example + * var data = [2, 3, 5, 6, 8]; + * var isEven = function (n) { return n % 2 === 0; }; + * var allEvens = _.every(isEven); + * var allIntegers = _.every(_.isInteger); + * + * allEvens(data) // => false + * allIntegers(data) // => true + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.everyIn|everyIn} + * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn} + * @since 0.39.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +var every = _curry2(everyIn, true); + +export default every; diff --git a/src/array/everyIn.js b/src/array/everyIn.js new file mode 100644 index 0000000..575774a --- /dev/null +++ b/src/array/everyIn.js @@ -0,0 +1,44 @@ +import _makeArrayChecker from "../privates/_makeArrayChecker"; + +/** + * Checks if all the elements in an array-like object satisfy the given predicate.
+ * The function will stop calling the predicate as soon as it returns a falsy value.
+ * Note that an empty array-like will always produce a true result regardless of the + * predicate because of [vacuous truth]{@link https://en.wikipedia.org/wiki/Vacuous_truth}.
+ * Also note that unlike the native + * [Array.prototype.every]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every}, + * this function won't skip deleted or unassigned indexes. + * @example + * var persons = [ + * {"name": "Jane", "age": 12, active: true}, + * {"name": "John", "age": 40, active: true}, + * {"name": "Mario", "age": 17, active: true}, + * {"name": "Paolo", "age": 15, active: true} + * ]; + * var isAdult = _.keySatisfies(_.isGTE(18), "age"); + * var isActive = _.hasKeyValue("active", true); + * + * _.everyIn(persons, isAdult) // => false + * _.everyIn(persons, isActive) // => true + * + * @example Showing the difference with Array.prototype.every: + * var isDefined = _.not(_.isUndefined); + * var arr = new Array(5); + * arr[3] = 99; + * + * arr.every(isDefined) // => true + * _.everyIn(arr, isDefined) // => false + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.every|every} + * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn} + * @since 0.39.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Boolean} + */ +var everyIn = _makeArrayChecker(true); + +export default everyIn; diff --git a/src/array/filter.js b/src/array/filter.js new file mode 100644 index 0000000..0709097 --- /dev/null +++ b/src/array/filter.js @@ -0,0 +1,32 @@ +/** + * Builds an array comprised of all values of the array-like object passing the predicate + * test.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example + * var isLowerCase = function (s) { return s.toLowerCase() === s; }; + * + * _.filter(["Foo", "bar", "baZ"], isLowerCase) // => ["bar"] + * + * // the function will work with any array-like object + * _.filter("fooBAR", isLowerCase) // => ["f", "o", "o"] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.filterWith|filterWith} + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @since 0.1.0 + * @returns {Array} + */ +function filter (arrayLike, predicate) { + var len = arrayLike.length; + var result = []; + + for (var i = 0; i < len; i++) { + predicate(arrayLike[i], i, arrayLike) && result.push(arrayLike[i]); + } + + return result; +} + +export default filter; diff --git a/src/array/filterWith.js b/src/array/filterWith.js new file mode 100644 index 0000000..b0c4a55 --- /dev/null +++ b/src/array/filterWith.js @@ -0,0 +1,26 @@ +import _curry2 from "../privates/_curry2"; +import filter from "./filter"; + +/** + * A curried version of {@link module:lamb.filter|filter} that uses the given predicate + * to build a function expecting the array-like object to act upon. + * @example + * var isLowerCase = function (s) { return s.toLowerCase() === s; }; + * var getLowerCaseEntries = _.filterWith(isLowerCase); + * + * getLowerCaseEntries(["Foo", "bar", "baZ"]) // => ["bar"] + * + * // array-like objects can be used as well + * getLowerCaseEntries("fooBAR") // => ["f", "o", "o"] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.filter|filter} + * @since 0.9.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +var filterWith = _curry2(filter, true); + +export default filterWith; diff --git a/src/array/find.js b/src/array/find.js new file mode 100644 index 0000000..99e7b6c --- /dev/null +++ b/src/array/find.js @@ -0,0 +1,32 @@ +import findIndex from "./findIndex"; + +/** + * Searches for an element satisfying the predicate in the given array-like object and returns it if + * the search is successful. Returns undefined otherwise. + * @example + * var persons = [ + * {"name": "Jane", "surname": "Doe", "age": 12}, + * {"name": "John", "surname": "Doe", "age": 40}, + * {"name": "Mario", "surname": "Rossi", "age": 18}, + * {"name": "Paolo", "surname": "Bianchi", "age": 40} + * ]; + * + * _.find(persons, _.hasKeyValue("age", 40)) // => {"name": "John", "surname": "Doe", "age": 40} + * _.find(persons, _.hasKeyValue("age", 41)) // => undefined + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.findWhere|findWhere} + * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} + * @since 0.7.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {*} + */ +function find (arrayLike, predicate) { + var idx = findIndex(arrayLike, predicate); + + return idx === -1 ? void 0 : arrayLike[idx]; +} + +export default find; diff --git a/src/array/findIndex.js b/src/array/findIndex.js new file mode 100644 index 0000000..3d47d8a --- /dev/null +++ b/src/array/findIndex.js @@ -0,0 +1,37 @@ +/** + * Searches for an element satisfying the predicate in the given array-like object and returns its + * index if the search is successful. Returns -1 otherwise. + * @example + * var persons = [ + * {"name": "Jane", "surname": "Doe", "age": 12}, + * {"name": "John", "surname": "Doe", "age": 40}, + * {"name": "Mario", "surname": "Rossi", "age": 18}, + * {"name": "Paolo", "surname": "Bianchi", "age": 40} + * ]; + * + * _.findIndex(persons, _.hasKeyValue("age", 40)) // => 1 + * _.findIndex(persons, _.hasKeyValue("age", 41)) // => -1 + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.findIndexWhere|findIndexWhere} + * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} + * @since 0.7.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Number} + */ +function findIndex (arrayLike, predicate) { + var result = -1; + + for (var i = 0, len = arrayLike.length; i < len; i++) { + if (predicate(arrayLike[i], i, arrayLike)) { + result = i; + break; + } + } + + return result; +} + +export default findIndex; diff --git a/src/array/findIndexWhere.js b/src/array/findIndexWhere.js new file mode 100644 index 0000000..63450b7 --- /dev/null +++ b/src/array/findIndexWhere.js @@ -0,0 +1,25 @@ +import _curry2 from "../privates/_curry2"; +import findIndex from "./findIndex"; + +/** + * A curried version of {@link module:lamb.findIndex|findIndex} that uses the given predicate + * to build a function expecting the array-like object to search. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var findEvenIdx = _.findIndexWhere(isEven); + * + * findEvenIdx([1, 3, 4, 5, 7]) // => 2 + * findEvenIdx([1, 3, 5, 7]) // => -1 + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.findIndex|findIndex} + * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} + * @since 0.41.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +var findIndexWhere = _curry2(findIndex, true); + +export default findIndexWhere; diff --git a/src/array/findWhere.js b/src/array/findWhere.js new file mode 100644 index 0000000..47d8341 --- /dev/null +++ b/src/array/findWhere.js @@ -0,0 +1,25 @@ +import _curry2 from "../privates/_curry2"; +import find from "./find"; + +/** + * A curried version of {@link module:lamb.find|find} expecting the array-like object + * to search. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var findEven = _.findWhere(isEven); + * + * findEven([1, 3, 4, 5, 7]) // => 4 + * findEven([1, 3, 5, 7]) // => undefined + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.find|find} + * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} + * @since 0.41.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +var findWhere = _curry2(find, true); + +export default findWhere; diff --git a/src/array/flatMap.js b/src/array/flatMap.js new file mode 100644 index 0000000..b90adab --- /dev/null +++ b/src/array/flatMap.js @@ -0,0 +1,38 @@ +import reduce from "../core/reduce"; + +/** + * Similar to {@link module:lamb.map|map}, but if the mapping function returns an array this will + * be concatenated, rather than pushed, to the final result. + * @example Showing the difference with map: + * var words = ["foo", "bar"]; + * var toCharArray = function (s) { return s.split(""); }; + * + * _.map(words, toCharArray) // => [["f", "o", "o"], ["b", "a", "r"]] + * _.flatMap(words, toCharArray) // => ["f", "o", "o", "b", "a", "r"] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.flatMapWith|flatMapWith} + * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} + * @since 0.1.0 + * @param {Array} array + * @param {ListIteratorCallback} iteratee + * @returns {Array} + */ +function flatMap (array, iteratee) { + return reduce(array, function (result, el, idx, arr) { + var v = iteratee(el, idx, arr); + + if (!Array.isArray(v)) { + v = [v]; + } + + for (var i = 0, len = v.length, rLen = result.length; i < len; i++) { + result[rLen + i] = v[i]; + } + + return result; + }, []); +} + +export default flatMap; diff --git a/src/array/flatMapWith.js b/src/array/flatMapWith.js new file mode 100644 index 0000000..f1df7d7 --- /dev/null +++ b/src/array/flatMapWith.js @@ -0,0 +1,24 @@ +import _curry2 from "../privates/_curry2"; +import flatMap from "./flatMap"; + +/** + * A curried version of {@link module:lamb.flatMap|flatMap} that uses provided iteratee + * to build a function expecting the array to act upon. + * @example + * var toCharArray = function (s) { return s.split(""); }; + * var wordsToCharArray = _.flatMapWith(toCharArray); + * + * wordsToCharArray(["foo", "bar"]) // => ["f", "o", "o", "b", "a", "r"] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.flatMap|flatMap} + * @see {@link module:lamb.map|map}, {@link module:lamb.mapWith|mapWith} + * @since 0.11.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} + */ +var flatMapWith = _curry2(flatMap, true); + +export default flatMapWith; diff --git a/src/array/flatten.js b/src/array/flatten.js new file mode 100644 index 0000000..239c88b --- /dev/null +++ b/src/array/flatten.js @@ -0,0 +1,21 @@ +import _makeArrayFlattener from "../privates/_makeArrayFlattener"; + +/** + * Flattens an array. + * @example Showing the difference with shallowFlatten: + * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; + * + * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] + * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.shallowFlatten|shallowFlatten} + * @since 0.1.0 + * @param {Array} array + * @returns {Array} + */ +var flatten = _makeArrayFlattener(true); + +export default flatten; diff --git a/src/array/getAt.js b/src/array/getAt.js new file mode 100644 index 0000000..0e310c2 --- /dev/null +++ b/src/array/getAt.js @@ -0,0 +1,30 @@ +import _curry2 from "../privates/_curry2"; +import getIndex from "./getIndex"; + +/** + * A curried version of {@link module:lamb.getIndex|getIndex} that uses the provided index + * to build a function expecting the array-like object holding the element we want to retrieve. + * @example + * var getFifthElement = _.getAt(4); + * + * getFifthElement([1, 2, 3, 4, 5]) // => 5 + * getFifthElement("foo bar") // => "b" + * getFifthElement([]) // => undefined + * getFifthElement("foo") // => undefined + * + * @example Using negative indexes: + * _.getAt(-2)([1, 2, 3]) // => 2 + * _.getAt(-3)("foo") // => "f" + * + * @memberof module:lamb + * @category Array + * @function + * @since 0.16.0 + * @see {@link module:lamb.getIndex|getIndex} + * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. + * @param {Number} index + * @returns {Function} + */ +var getAt = _curry2(getIndex, true); + +export default getAt; diff --git a/src/array/getIndex.js b/src/array/getIndex.js new file mode 100644 index 0000000..e7844d6 --- /dev/null +++ b/src/array/getIndex.js @@ -0,0 +1,33 @@ +import _toArrayLength from "../privates/_toArrayLength"; +import _toNaturalIndex from "../privates/_toNaturalIndex"; + +/** + * Retrieves the element at the given index in an array-like object.
+ * Like {@link module:lamb.slice|slice} the index can be negative.
+ * If the index isn't supplied, or if its value isn't an integer within the array-like bounds, + * the function will return undefined.
+ * getIndex will throw an exception when receives null or + * undefined in place of an array-like object, but returns undefined + * for any other value. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.getIndex(arr, 1) // => 2 + * _.getIndex(arr, -1) // => 5 + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.getAt|getAt} + * @see {@link module:lamb.head|head} and {@link module:lamb.last|last} for common use cases shortcuts. + * @since 0.23.0 + * @param {ArrayLike} arrayLike + * @param {Number} index + * @returns {*} + */ +function getIndex (arrayLike, index) { + var idx = _toNaturalIndex(index, _toArrayLength(arrayLike.length)); + + return idx === idx ? arrayLike[idx] : void 0; // eslint-disable-line no-self-compare +} + +export default getIndex; diff --git a/src/array/group.js b/src/array/group.js new file mode 100644 index 0000000..c913620 --- /dev/null +++ b/src/array/group.js @@ -0,0 +1,71 @@ +import _groupWith from "../privates/_groupWith"; + +/** + * Transforms an array-like object into a lookup table using the provided iteratee as a grouping + * criterion to generate keys and values. + * @example + * var persons = [ + * {"name": "Jane", "city": "New York"}, + * {"name": "John", "city": "New York"}, + * {"name": "Mario", "city": "Rome"}, + * {"name": "Paolo"} + * ]; + * var getCity = _.getKey("city"); + * var personsByCity = _.group(persons, getCity); + * + * // "personsByCity" holds: + * // { + * // "New York": [ + * // {"name": "Jane", "city": "New York"}, + * // {"name": "John", "city": "New York"} + * // ], + * // "Rome": [ + * // {"name": "Mario", "city": "Rome"} + * // ], + * // "undefined": [ + * // {"name": "Paolo"} + * // ] + * // } + * + * @example Adding a custom value for missing keys: + * + * var getCityOrUnknown = _.adapter([getCity, _.always("Unknown")]); + * + * var personsByCity = _.group(persons, getCityOrUnknown); + * + * // "personsByCity" holds: + * // { + * // "New York": [ + * // {"name": "Jane", "city": "New York"}, + * // {"name": "John", "city": "New York"} + * // ], + * // "Rome": [ + * // {"name": "Mario", "city": "Rome"} + * // ], + * // "Unknown": [ + * // {"name": "Paolo"} + * // ] + * // } + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.groupBy|groupBy} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.7.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Object} + */ +var group = _groupWith(function (a, b) { + if (!a) { + return [b]; + } + + a[a.length] = b; + + return a; +}); + +export default group; diff --git a/src/array/groupBy.js b/src/array/groupBy.js new file mode 100644 index 0000000..81f81ca --- /dev/null +++ b/src/array/groupBy.js @@ -0,0 +1,44 @@ +import _curry2 from "../privates/_curry2"; +import group from "./group"; + +/** + * A curried version of {@link module:lamb.group|group} that uses the provided iteratee + * to build a function expecting the array-like object to act upon. + * @example + * var persons = [ + * {"name": "Jane", "age": 12}, + * {"name": "John", "age": 40}, + * {"name": "Mario", "age": 18}, + * {"name": "Paolo", "age": 15} + * ]; + * + * var getAgeStatus = function (person) { return person.age > 20 ? "over 20" : "under 20"; }; + * var groupByAgeStatus = _.groupBy(getAgeStatus); + * + * var personsByAgeStatus = groupByAgeStatus(persons); + * + * // "personsByAgeStatus" holds: + * // { + * // "under 20": [ + * // {"name": "Jane", "age": 12}, + * // {"name": "Mario", "age": 18}, + * // {"name": "Paolo", "age": 15} + * // ], + * // "over 20": [ + * // {"name": "John", "age": 40} + * // ] + * // } + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.group|group} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} + * @since 0.7.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} + */ +var groupBy = _curry2(group, true); + +export default groupBy; diff --git a/src/array/head.js b/src/array/head.js new file mode 100644 index 0000000..4905cf2 --- /dev/null +++ b/src/array/head.js @@ -0,0 +1,22 @@ +import getAt from "./getAt"; + +/** + * Retrieves the first element of an array-like object.
+ * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. + * @example + * _.head([1, 2, 3]) // => 1 + * _.head("hello") // => "h" + * _.head([]) // => undefined + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.last|last} + * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {*} + */ +var head = getAt(0); + +export default head; diff --git a/src/array/index.js b/src/array/index.js new file mode 100644 index 0000000..2a82fd7 --- /dev/null +++ b/src/array/index.js @@ -0,0 +1,58 @@ +import _groupWith from "../privates/_groupWith"; + +/** + * Similar to {@link module:lamb.group|group}, but the generated lookup table will have + * only one element of the original array-like object for each value.
+ * Should be used only when you're sure that your iteratee won't produce + * duplicate keys, otherwise only the last evaluated element will be in the result. + * @example + * var users = [ + * {id: 1, name: "John"}, + * {id: 2, name: "Jane"}, + * {id: 3, name: "Mario"}, + * {id: 4, name: "John"} + * ]; + * + * var indexedUsers = _.index(users, _.getKey("id")); + * + * // "indexedUsers" holds: + * // { + * // "1": {id: 1, name: "John"}, + * // "2": {id: 2, name: "Jane"}, + * // "3": {id: 3, name: "Mario"}, + * // "4": {id: 4, name: "John"} + * // } + * + * @example Result of an iteratee producing a duplicate key: + * var users = [ + * {id: 1, name: "John"}, + * {id: 2, name: "Jane"}, + * {id: 3, name: "Mario"}, + * {id: 4, name: "John"} + * ]; + * + * var indexedUsers = _.index(users, _.getKey("name")); + * + * // "indexedUsers" holds: + * // { + * // "John": {"id": 4, "name": "John"}, + * // "Jane": {"id": 2, "name": "Jane"}, + * // "Mario": {"id": 3, "name": "Mario"} + * // } + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.indexBy|indexBy} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @since 0.21.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Object} + */ +var index = _groupWith(function (a, b) { + return b; +}); + +export default index; diff --git a/src/array/indexBy.js b/src/array/indexBy.js new file mode 100644 index 0000000..aaf89c1 --- /dev/null +++ b/src/array/indexBy.js @@ -0,0 +1,36 @@ +import _curry2 from "../privates/_curry2"; +import index from "./index"; + +/** + * A curried version of {@link module:lamb.index|index} that uses the provided iteratee + * to build a function expecting the array-like object to act upon. + * @example + * var users = [ + * {id: 1, name: "John"}, + * {id: 2, name: "Jane"}, + * {id: 3, name: "Mario"} + * ]; + * var indexByID = _.indexBy(_.getKey("id")); + * + * var indexedUsers = indexByID(users); + * + * // "indexedUsers" holds: + * // { + * // "1": {id: 1, name: "John"}, + * // "2": {id: 2, name: "Jane"}, + * // "3": {id: 3, name: "Mario"} + * // } + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.index|index} + * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} + * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} + * @since 0.21.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} + */ +var indexBy = _curry2(index, true); + +export default indexBy; diff --git a/src/array/init.js b/src/array/init.js new file mode 100644 index 0000000..08a7d9b --- /dev/null +++ b/src/array/init.js @@ -0,0 +1,23 @@ +import __ from "../core/__"; +import partial from "../core/partial"; +import slice from "../core/slice"; + +/** + * Returns a copy of the given array-like object without the last element. + * @example + * _.init([1, 2, 3, 4]) // => [1, 2, 3] + * _.init([1]) // => [] + * _.init([]) // => [] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.tail|tail} + * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {Array} + */ +var init = partial(slice, [__, 0, -1]); + +export default init; diff --git a/src/array/insert.js b/src/array/insert.js new file mode 100644 index 0000000..4af4f8c --- /dev/null +++ b/src/array/insert.js @@ -0,0 +1,37 @@ +import slice from "../core/slice"; + +/** + * Inserts the provided element in a copy of an array-like object at the + * specified index.
+ * If the index is greater than the length of the array-like, the element + * will be appended at the end.
+ * Negative indexes are allowed; when a negative index is out of bounds + * the element will be inserted at the start of the resulting array. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.insert(arr, 3, 99) // => [1, 2, 3, 99, 4, 5] + * _.insert(arr, -2, 99) // => [1, 2, 3, 99, 4, 5] + * _.insert(arr, 10, 99) // => [1, 2, 3, 4, 5, 99] + * _.insert(arr, -10, 99) // => [99, 1, 2, 3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.insertAt|insertAt} + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {Number} index + * @param {*} element + * @returns {Array} + */ +function insert (arrayLike, index, element) { + var result = slice(arrayLike, 0, arrayLike.length); + + result.splice(index, 0, element); + + return result; +} + +export default insert; diff --git a/src/array/insertAt.js b/src/array/insertAt.js new file mode 100644 index 0000000..29458a1 --- /dev/null +++ b/src/array/insertAt.js @@ -0,0 +1,28 @@ +import _makePartial3 from "../privates/_makePartial3"; +import insert from "./insert"; + +/** + * Builds a partial application of {@link module:lamb.insert|insert} + * expecting the array-like object to act upon. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.insertAt(3, 99)(arr) // => [1, 2, 3, 99, 4, 5] + * _.insertAt(-2, 99)(arr) // => [1, 2, 3, 99, 4, 5] + * _.insertAt(10, 99)(arr) // => [1, 2, 3, 4, 5, 99] + * _.insertAt(-10, 99)(arr) // => [99, 1, 2, 3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.insert|insert} + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.append|append}, {@link module:lamb.appendTo|appendTo} + * @since 0.27.0 + * @param {Number} index + * @param {*} element + * @returns {Function} + */ +var insertAt = _makePartial3(insert); + +export default insertAt; diff --git a/src/array/intersection.js b/src/array/intersection.js new file mode 100644 index 0000000..5dc300f --- /dev/null +++ b/src/array/intersection.js @@ -0,0 +1,38 @@ +import isIn from "./isIn"; + +/** + * Returns an array of every unique item that is included in all two given arrays + * or array-like objects.
+ * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * @example + * var a1 = [1, 2, 3, 4]; + * var a2 = [2, 5, 4, 2, 6]; + * var a3 = [5, 6, 7]; + * + * _.intersection(a1, a2) // => [2, 4] + * _.intersection(a2, a3) // => [5, 6] + * _.intersection(a1, a3) // => [] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.difference|difference} + * @see {@link module:lamb.union|union}, {@link module:lamb.unionBy|unionBy} + * @since 0.5.0 + * @param {ArrayLike} a + * @param {ArrayLike} b + * @returns {Array} + */ +function intersection (a, b) { + var result = []; + var lenA = a.length; + + if (lenA && b.length) { + for (var i = 0; i < lenA; i++) { + !isIn(result, a[i]) && isIn(b, a[i]) && result.push(a[i]); + } + } + + return result; +} + +export default intersection; diff --git a/src/array/isIn.js b/src/array/isIn.js new file mode 100644 index 0000000..3ef3f90 --- /dev/null +++ b/src/array/isIn.js @@ -0,0 +1,38 @@ +import areSVZ from "../core/areSVZ"; + +/** + * Checks if an array-like object contains the given value.
+ * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can + * check for NaN, but 0 and -0 are the same value.
+ * See also {@link module:lamb.contains|contains} for a curried version building a predicate. + * @example + * var numbers = [0, 1, 2, 3, NaN]; + * + * _.isIn(numbers, 1) // => true + * _.isIn(numbers, 0) // => true + * _.isIn(numbers, -0) // => true + * _.isIn(numbers, NaN) // => true + * _.isIn(numbers, 5) // => false + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.contains|contains} + * @since 0.13.0 + * @param {ArrayLike} arrayLike + * @param {*} value + * @returns {Boolean} + */ +function isIn (arrayLike, value) { + var result = false; + + for (var i = 0, len = arrayLike.length; i < len; i++) { + if (areSVZ(value, arrayLike[i])) { + result = true; + break; + } + } + + return result; +} + +export default isIn; diff --git a/src/array/last.js b/src/array/last.js new file mode 100644 index 0000000..10ab8ea --- /dev/null +++ b/src/array/last.js @@ -0,0 +1,22 @@ +import getAt from "./getAt"; + +/** + * Retrieves the last element of an array-like object.
+ * Just a common use case of {@link module:lamb.getAt|getAt} exposed for convenience. + * @example + * _.last([1, 2, 3]) // => 3 + * _.last("hello") // => "o" + * _.last([]) // => undefined + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.head|head} + * @see {@link module:lamb.getIndex|getIndex}, {@link module:lamb.getAt|getAt} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {*} + */ +var last = getAt(-1); + +export default last; diff --git a/src/array/list.js b/src/array/list.js new file mode 100644 index 0000000..6a026aa --- /dev/null +++ b/src/array/list.js @@ -0,0 +1,18 @@ +import _argsToArrayFrom from "../privates/_argsToArrayFrom"; + +/** + * Generates an array with the values passed as arguments.
+ * Behaves like ES6's [Array.of]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of}. + * @example + * _.list(1, 2, 3) // => [1, 2, 3] + * + * @memberof module:lamb + * @category Array + * @function + * @since 0.1.0 + * @param {...*} value + * @returns {Array} + */ +var list = _argsToArrayFrom(0); + +export default list; diff --git a/src/array/partition.js b/src/array/partition.js new file mode 100644 index 0000000..0f2c340 --- /dev/null +++ b/src/array/partition.js @@ -0,0 +1,30 @@ +/** + * Splits an array-like object in two lists: the first with the elements satisfying the given predicate, + * the others with the remaining elements. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + * + * _.partition(numbers, isEven) // => [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.partitionWith|partitionWith} + * @since 0.11.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Array} + */ +function partition (arrayLike, predicate) { + var result = [[], []]; + var len = arrayLike.length; + + for (var i = 0, el; i < len; i++) { + el = arrayLike[i]; + result[predicate(el, i, arrayLike) ? 0 : 1].push(el); + } + + return result; +} + +export default partition; diff --git a/src/array/partitionWith.js b/src/array/partitionWith.js new file mode 100644 index 0000000..db84793 --- /dev/null +++ b/src/array/partitionWith.js @@ -0,0 +1,36 @@ +import _curry2 from "../privates/_curry2"; +import partition from "./partition"; + +/** + * A curried version of {@link module:lamb.partition|partition} that uses the provided + * predicate to build a function expecting the array-like object to act upon. + * @example + * var users = [ + * {"name": "Jane", "surname": "Doe", "active": false}, + * {"name": "John", "surname": "Doe", "active": true}, + * {"name": "Mario", "surname": "Rossi", "active": true}, + * {"name": "Paolo", "surname": "Bianchi", "active": false} + * ]; + * var isActive = _.hasKeyValue("active", true); + * var splitByActiveStatus = _.partitionWith(isActive); + * + * splitByActiveStatus(users) // => + * // [[ + * // {"name": "John", "surname": "Doe", "active": true}, + * // {"name": "Mario", "surname": "Rossi", "active": true} + * // ], [ + * // {"name": "Jane", "surname": "Doe", "active": false}, + * // {"name": "Paolo", "surname": "Bianchi", "active": false} + * // ]] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.partition|partition} + * @since 0.11.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +var partitionWith = _curry2(partition, true); + +export default partitionWith; diff --git a/src/array/pluck.js b/src/array/pluck.js new file mode 100644 index 0000000..01d5dd5 --- /dev/null +++ b/src/array/pluck.js @@ -0,0 +1,36 @@ +import map from "../core/map"; +import getKey from "../object/getKey"; + +/** + * "Plucks" the values of the specified key from a list of objects. + * @example + * var persons = [ + * {"name": "Jane", "surname": "Doe", "age": 12}, + * {"name": "John", "surname": "Doe", "age": 40}, + * {"name": "Mario", "surname": "Rossi", "age": 18}, + * {"name": "Paolo", "surname": "Bianchi", "age": 15} + * ]; + * + * _.pluck(persons, "age") // => [12, 40, 18, 15] + * + * var lists = [ + * [1, 2], + * [3, 4, 5], + * [6] + * ]; + * + * _.pluck(lists, "length") // => [2, 3, 1] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.pluckKey|pluckKey} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {String} key + * @returns {Array} + */ +function pluck (arrayLike, key) { + return map(arrayLike, getKey(key)); +} + +export default pluck; diff --git a/src/array/pluckKey.js b/src/array/pluckKey.js new file mode 100644 index 0000000..0a1a26e --- /dev/null +++ b/src/array/pluckKey.js @@ -0,0 +1,29 @@ +import compose from "../core/compose"; +import mapWith from "../core/mapWith"; +import getKey from "../object/getKey"; + +/** + * A curried version of {@link module:lamb.pluck|pluck} expecting the key to retrieve to + * build a function waiting for the array-like object to act upon. + * @example + * var persons = [ + * {"name": "Jane", "surname": "Doe", "age": 12}, + * {"name": "John", "surname": "Doe", "age": 40}, + * {"name": "Mario", "surname": "Rossi", "age": 18}, + * {"name": "Paolo", "surname": "Bianchi", "age": 15} + * ]; + * var getAges = _.pluckKey("age"); + * + * getAges(persons) // => [12, 40, 18, 15] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.pluck|pluck} + * @since 0.12.0 + * @param {String} key + * @returns {Function} + */ +var pluckKey = compose(mapWith, getKey); + +export default pluckKey; diff --git a/src/array/pull.js b/src/array/pull.js new file mode 100644 index 0000000..2daba27 --- /dev/null +++ b/src/array/pull.js @@ -0,0 +1,30 @@ +import _curry2 from "../privates/_curry2"; +import pullFrom from "./pullFrom"; + +/** + * A curried version of {@link module:lamb.pullFrom|pullFrom} expecting + * a list of values to build a function waiting for an array-like object.
+ * The new function will create an array copy of the array-like without + * the specified values.
+ * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
+ * See examples in {@link module:lamb.pullFrom|pullFrom} about the + * relationship with {@link module:lamb.difference|difference}. + * @example + * var scores = [40, 20, 30, 10]; + * var newScores = [30, 10]; + * var pullNewScores = _.pull(newScores); + * + * pullNewScores(scores) // => [40, 20] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.pullFrom|pullFrom} + * @see {@link module:lamb.difference|difference} + * @since 0.45.0 + * @param {ArrayLike} values + * @returns {Function} + */ +var pull = _curry2(pullFrom, true); + +export default pull; diff --git a/src/array/pullFrom.js b/src/array/pullFrom.js new file mode 100644 index 0000000..27abb6e --- /dev/null +++ b/src/array/pullFrom.js @@ -0,0 +1,36 @@ +import slice from "../core/slice"; +import filter from "./filter"; +import isIn from "./isIn"; + +/** + * Creates an array copy of the given array-like object without the + * specified values.
+ * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.pullFrom(arr, [2, 5]) // => [1, 3, 4] + * + * @example It's not the same as {@link module:lamb.difference|difference}: + * + * var arr = [1,1,2,3,4,4,5]; + * + * _.pullFrom(arr, [1, 2]) // => [3, 4, 4, 5] + * _.difference(arr, [1, 2]) // => [3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.pull|pull} + * @see {@link module:lamb.difference|difference} + * @since 0.45.0 + * @param {ArrayLike} arrayLike + * @param {ArrayLike} values + * @returns {Array} + */ +function pullFrom (arrayLike, values) { + return values ? filter(arrayLike, function (element) { + return !isIn(values, element); + }) : slice(arrayLike, 0, arrayLike.length); +} + +export default pullFrom; diff --git a/src/array/reduceRight.js b/src/array/reduceRight.js new file mode 100644 index 0000000..a58b37d --- /dev/null +++ b/src/array/reduceRight.js @@ -0,0 +1,20 @@ +import _makeReducer from "../privates/_makeReducer"; + +/** + * Same as {@link module:lamb.reduce|reduce}, but starts the fold operation from the last + * element instead.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.reduce|reduce} + * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] + * @returns {*} + */ +var reduceRight = _makeReducer(-1); + +export default reduceRight; diff --git a/src/array/reduceRightWith.js b/src/array/reduceRightWith.js new file mode 100644 index 0000000..c52ad24 --- /dev/null +++ b/src/array/reduceRightWith.js @@ -0,0 +1,27 @@ +import _makePartial3 from "../privates/_makePartial3"; +import reduceRight from "./reduceRight"; + +/** + * A partial application of {@link module:lamb.reduce|reduceRight} that uses the + * provided accumulator and the optional initialValue to + * build a function expecting the array-like object to act upon. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.reduceRightWith(_.sum)(arr) // => 15 + * _.reduceRightWith(_.subtract)(arr) // => -5 + * _.reduceRightWith(_.subtract, 0)(arr) // => -15 + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.reduceWith|reduceWith} + * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} + * @since 0.27.0 + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] + * @returns {Function} + */ +var reduceRightWith = _makePartial3(reduceRight, true); + +export default reduceRightWith; diff --git a/src/array/reverse.js b/src/array/reverse.js new file mode 100644 index 0000000..68221fd --- /dev/null +++ b/src/array/reverse.js @@ -0,0 +1,29 @@ +import _toArrayLength from "../privates/_toArrayLength"; + +/** + * Reverses a copy of the given array-like object. + * @example + * var arr = [1, 2, 3]; + * + * _.reverse(arr) // => [3, 2, 1]; + * + * // `arr` still is [1, 2, 3] + * + * @memberof module:lamb + * @category Array + * @since 0.19.0 + * @param {ArrayLike} arrayLike + * @returns {Array} + */ +function reverse (arrayLike) { + var len = _toArrayLength(arrayLike.length); + var result = Array(len); + + for (var i = 0, ofs = len - 1; i < len; i++) { + result[i] = arrayLike[ofs - i]; + } + + return result; +} + +export default reverse; diff --git a/src/array/rotate.js b/src/array/rotate.js new file mode 100644 index 0000000..e725cec --- /dev/null +++ b/src/array/rotate.js @@ -0,0 +1,28 @@ +import slice from "../core/slice"; + +/** + * Returns a copy of the given array-like with the element rotated by the desired amount. + * Negative indexes are allowed. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.rotate(arr, 3) // => [3, 4, 5, 1, 2] + * _.rotate(arr, -3) // => [4, 5, 1, 2, 3] + * _.rotate(arr, 11) // => [5, 1, 2, 3, 4] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.rotateBy|rotateBy} + * @since 0.55.0 + * @param {ArrayLike} arrayLike + * @param {Number} amount + * @returns {Array} + */ +function rotate (arrayLike, amount) { + var len = arrayLike.length; + var shift = amount % len; + + return slice(arrayLike, -shift, len).concat(slice(arrayLike, 0, -shift)); +} + +export default rotate; diff --git a/src/array/rotateBy.js b/src/array/rotateBy.js new file mode 100644 index 0000000..8dbdb16 --- /dev/null +++ b/src/array/rotateBy.js @@ -0,0 +1,23 @@ +import _curry2 from "../privates/_curry2"; +import rotate from "./rotate"; + +/** + * A curried version of {@link module:lamb.rotate|rotate}.
+ * Uses the given amount to build a function expecting the array to rotate by that amount. + * @example + * var arr = [1, 2, 3, 4, 5]; + * var rotateByTwo = _.rotateBy(2); + * + * rotateByTwo(arr) // => [4, 5, 1, 2, 3] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.rotate|rotate} + * @since 0.55.0 + * @param {Number} amount + * @returns {Function} + */ +var rotateBy = _curry2(rotate, true); + +export default rotateBy; diff --git a/src/array/setAt.js b/src/array/setAt.js new file mode 100644 index 0000000..73a7a87 --- /dev/null +++ b/src/array/setAt.js @@ -0,0 +1,33 @@ +import _makePartial3 from "../privates/_makePartial3"; +import _setIndex from "../privates/_setIndex"; + +/** + * A curried version of {@link module:lamb.setIndex|setIndex} that builds + * a function that creates a copy of an array-like object with the given + * index changed to the desired value.
+ * If the index is not an integer or if it's out of bounds, the function + * will return a copy of the original array.
+ * Negative indexes are allowed. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.setAt(2, 99)(arr) // => [1, 2, 99, 4, 5] + * arr // => [1, 2, 3, 4, 5] + * + * _.setAt(10, 99)(arr) // => [1, 2, 3, 4, 5] (not a reference to `arr`) + * + * @example Using negative indexes: + * _.setAt(-1, 99)(arr) // => [1, 2, 3, 4, 99] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.setIndex|setIndex} + * @since 0.17.0 + * @param {Number} index + * @param {*} value + * @returns {Function} + */ +var setAt = _makePartial3(_setIndex); + +export default setAt; diff --git a/src/array/setIndex.js b/src/array/setIndex.js new file mode 100644 index 0000000..d5e59a3 --- /dev/null +++ b/src/array/setIndex.js @@ -0,0 +1,29 @@ +import _setIndex from "../privates/_setIndex"; +import aritize from "../function/aritize"; + +/** + * Creates a copy of an array-like object with the given index changed to + * the desired value.
+ * If the index is not an integer or if it's out of bounds, the function + * will return a copy of the original array.
+ * Negative indexes are allowed. + * @example + * var arr = [1, 2, 3]; + * + * _.setIndex(arr, 1, 99) // => [1, 99, 3] + * _.setIndex(arr, -1, 99) // => [1, 2, 99] + * _.setIndex(arr, 10, 99) // => [1, 2, 3] (not a reference to `arr`) + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.setAt|setAt} + * @since 0.23.0 + * @param {ArrayLike} arrayLike + * @param {Number} index + * @param {*} value + * @returns {Array} + */ +var setIndex = aritize(_setIndex, 3); + +export default setIndex; diff --git a/src/array/shallowFlatten.js b/src/array/shallowFlatten.js new file mode 100644 index 0000000..a681054 --- /dev/null +++ b/src/array/shallowFlatten.js @@ -0,0 +1,21 @@ +import _makeArrayFlattener from "../privates/_makeArrayFlattener"; + +/** + * Flattens the "first level" of an array. + * @example Showing the difference with flatten: + * var arr = [1, 2, [3, 4, [5, 6]], 7, 8]; + * + * _.flatten(arr) // => [1, 2, 3, 4, 5, 6, 7, 8] + * _.shallowFlatten(arr) // => [1, 2, 3, 4, [5, 6], 7, 8] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.flatten|flatten} + * @since 0.9.0 + * @param {Array} array + * @returns {Array} + */ +var shallowFlatten = _makeArrayFlattener(false); + +export default shallowFlatten; diff --git a/src/array/some.js b/src/array/some.js new file mode 100644 index 0000000..b828a84 --- /dev/null +++ b/src/array/some.js @@ -0,0 +1,27 @@ +import _curry2 from "../privates/_curry2"; +import someIn from "./someIn"; + +/** + * A curried version of {@link module:lamb.someIn|someIn} that uses the given predicate to + * build a function waiting for the array-like to act upon. + * @example + * var data = [1, 3, 5, 6, 7, 8]; + * var isEven = function (n) { return n % 2 === 0; }; + * var containsEvens = _.some(isEven); + * var containsStrings = _.some(_.isType("String")); + * + * containsEvens(data) // => true + * containsStrings(data) // => false + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.someIn|someIn} + * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} + * @since 0.39.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +var some = _curry2(someIn, true); + +export default some; diff --git a/src/array/someIn.js b/src/array/someIn.js new file mode 100644 index 0000000..d648775 --- /dev/null +++ b/src/array/someIn.js @@ -0,0 +1,41 @@ +import _makeArrayChecker from "../privates/_makeArrayChecker"; + +/** + * Checks if at least one element in an array-like object satisfies the given predicate.
+ * The function will stop calling the predicate as soon as it returns a truthy value.
+ * Note that unlike the native + * [Array.prototype.some]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some}, + * this function won't skip deleted or unassigned indexes. + * @example + * var persons = [ + * {"name": "Jane", "age": 12, active: false}, + * {"name": "John", "age": 40, active: false}, + * {"name": "Mario", "age": 17, active: false}, + * {"name": "Paolo", "age": 15, active: false} + * ]; + * var isAdult = _.keySatisfies(_.isGTE(18), "age"); + * var isActive = _.hasKeyValue("active", true); + * + * _.someIn(persons, isAdult) // => true + * _.someIn(persons, isActive) // => false + * + * @example Showing the difference with Array.prototype.some: + * var arr = new Array(5); + * arr[3] = 99; + * + * arr.some(_.isUndefined) // => false + * _.someIn(arr, _.isUndefined) // => true + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.some|some} + * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} + * @since 0.39.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Boolean} + */ +var someIn = _makeArrayChecker(false); + +export default someIn; diff --git a/src/array/sort.js b/src/array/sort.js new file mode 100644 index 0000000..efc0946 --- /dev/null +++ b/src/array/sort.js @@ -0,0 +1,84 @@ +import _compareWith from "../privates/_compareWith"; +import _makeCriteria from "../privates/_makeCriteria"; +import _toArrayLength from "../privates/_toArrayLength"; + +/** + * Returns a [stably]{@link https://en.wikipedia.org/wiki/Sorting_algorithm#Stability} sorted + * copy of an array-like object using the given criteria.
+ * Sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, but you + * can also pass simple "reader" functions and default ascending sorters will be built for you.
+ * A "reader" is a function that evaluates the array element and supplies the value to be used + * in the comparison.
+ * Please note that if the arguments received by the default comparer aren't of the same type, + * they will be compared as strings. + * + * @example Stable sort: + * var persons = [ + * {"name": "John", "surname" :"Doe"}, + * {"name": "Mario", "surname": "Rossi"}, + * {"name": "John", "surname" :"Moe"}, + * {"name": "Jane", "surname": "Foe"} + * ]; + * + * var personsByName = _.sort(persons, [_.getKey("name")]); + * + * // personsByName holds: + * // [ + * // {"name": "Jane", "surname": "Foe"}, + * // {"name": "John", "surname" :"Doe"}, + * // {"name": "John", "surname" :"Moe"}, + * // {"name": "Mario", "surname": "Rossi"} + * // ] + * + * @example Stable multi-sort: + * var personsByNameAscSurnameDesc = _.sort(persons, [ + * _.getKey("name"), + * _.sorterDesc(_.getKey("surname")) + * ]); + * + * // personsByNameAscSurnameDesc holds: + * // [ + * // {"name": "Jane", "surname": "Foe"}, + * // {"name": "John", "surname" :"Moe"}, + * // {"name": "John", "surname" :"Doe"}, + * // {"name": "Mario", "surname": "Rossi"} + * // ] + * + * @example Using custom comparers: + * var localeSorter = new Intl.Collator("it"); + * var chars = ["a", "è", "à", "é", "c", "b", "e"]; + * + * _.sort(chars, [localeSorter]) // => ["a", "à", "b", "c", "e", "é", "è"] + * + * var localeSorterDesc = _.sorterDesc(_.identity, localeSorter.compare); + * + * _.sort(chars, [localeSorterDesc]) // => ["è", "é", "e", "c", "b", "à", "a"] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} + * @since 0.15.0 + * @param {ArrayLike} arrayLike + * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] + * @returns {Array} + */ +function sort (arrayLike, sorters) { + var criteria = _makeCriteria(sorters); + var len = _toArrayLength(arrayLike.length); + var result = Array(len); + + for (var i = 0; i < len; i++) { + result[i] = { value: arrayLike[i], index: i }; + } + + result.sort(_compareWith(criteria)); + + for (i = 0; i < len; i++) { + result[i] = result[i].value; + } + + return result; +} + +export default sort; diff --git a/src/array/sortWith.js b/src/array/sortWith.js new file mode 100644 index 0000000..a128c86 --- /dev/null +++ b/src/array/sortWith.js @@ -0,0 +1,30 @@ +import _curry2 from "../privates/_curry2"; +import sort from "./sort"; + +/** + * Builds a partial application of {@link module:lamb.sort|sort} using the provided criteria. + * The returned function expects the array-like object to sort. + * As usual, sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, + * but you can also pass simple "reader" functions and default ascending sorters will be built.
+ * A "reader" is a function that evaluates the array element and supplies the value to be used in + * the comparison.
+ * See {@link module:lamb.sort|sort} for more examples. + * + * @example + * var sortAsNumbers = _.sortWith([parseFloat]); + * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; + * + * sortAsNumbers(weights) // => ["1 Kg", "2 Kg", "7 Kg", "10 Kg"] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.sort|sort} + * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} + * @since 0.15.0 + * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] + * @returns {Function} + */ +var sortWith = _curry2(sort, true); + +export default sortWith; diff --git a/src/array/sortedInsert.js b/src/array/sortedInsert.js new file mode 100644 index 0000000..6b51c10 --- /dev/null +++ b/src/array/sortedInsert.js @@ -0,0 +1,67 @@ +import slice from "../core/slice"; +import _compareWith from "../privates/_compareWith"; +import _getInsertionIndex from "../privates/_getInsertionIndex"; +import _makeCriteria from "../privates/_makeCriteria"; + +/** + * Inserts an element in a copy of a sorted array respecting the sort order. + * @example With simple values: + * _.sortedInsert([], 1) // => [1] + * _.sortedInsert([2, 4, 6], 5) // => [2, 4, 5, 6] + * _.sortedInsert([4, 2, 1], 3, _.sorterDesc()) // => [4, 3, 2, 1] + * + * @example With complex values: + * var persons = [ + * {"name": "jane", "surname": "doe"}, + * {"name": "John", "surname": "Doe"}, + * {"name": "Mario", "surname": "Rossi"} + * ]; + * + * var getLowerCaseName = _.compose( + * _.invoker("toLowerCase"), + * _.getKey("name") + * ); + * + * var result = _.sortedInsert( + * persons, + * {"name": "marco", "surname": "Rossi"}, + * getLowerCaseName + * ); + * + * // `result` holds: + * // [ + * // {"name": "jane", "surname": "doe"}, + * // {"name": "John", "surname": "Doe"}, + * // {"name": "marco", "surname": "Rossi"}, + * // {"name": "Mario", "surname": "Rossi"} + * // ] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} + * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} to insert the element + * at a specific index + * @since 0.27.0 + * @param {ArrayLike} arrayLike + * @param {*} element + * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - The sorting criteria + * used to sort the array. + * @returns {Array} + */ +function sortedInsert (arrayLike, element, sorters) { + var result = slice(arrayLike, 0, arrayLike.length); + + if (arguments.length === 1) { + return result; + } + + var criteria = _makeCriteria(sorters); + var idx = _getInsertionIndex(result, element, _compareWith(criteria), 0, result.length); + + result.splice(idx, 0, element); + + return result; +} + +export default sortedInsert; diff --git a/src/array/sorter.js b/src/array/sorter.js new file mode 100644 index 0000000..189c00e --- /dev/null +++ b/src/array/sorter.js @@ -0,0 +1,25 @@ +import __ from "../core/__"; +import partial from "../core/partial"; +import _sorter from "../privates/_sorter"; + +/** + * Creates an ascending sort criterion with the provided reader and + * comparer.
+ * See {@link module:lamb.sort|sort} for various examples. + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorterDesc|sorterDesc} + * @since 0.1.0 + * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a + * simple value from a complex one. The function should evaluate the array element and supply the + * value to be passed to the comparer. + * @param {Function} [comparer] An optional custom comparer function. + * @returns {Sorter} + */ +var sorter = partial(_sorter, [__, false, __]); + +export default sorter; diff --git a/src/array/sorterDesc.js b/src/array/sorterDesc.js new file mode 100644 index 0000000..cc75205 --- /dev/null +++ b/src/array/sorterDesc.js @@ -0,0 +1,25 @@ +import __ from "../core/__"; +import partial from "../core/partial"; +import _sorter from "../privates/_sorter"; + +/** + * Creates a descending sort criterion with the provided reader and + * comparer.
+ * See {@link module:lamb.sort|sort} for various examples. + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.sortedInsert|sortedInsert} + * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} + * @see {@link module:lamb.sorter|sorter} + * @since 0.15.0 + * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a + * simple value from a complex one. The function should evaluate the array element and supply the + * value to be passed to the comparer. + * @param {Function} [comparer] An optional custom comparer function. + * @returns {Sorter} + */ +var sorterDesc = partial(_sorter, [__, true, __]); + +export default sorterDesc; diff --git a/src/array/tail.js b/src/array/tail.js new file mode 100644 index 0000000..0008590 --- /dev/null +++ b/src/array/tail.js @@ -0,0 +1,21 @@ +import drop from "./drop"; + +/** + * Returns a copy of the given array-like object without the first element. + * @example + * _.tail([1, 2, 3, 4]) // => [2, 3, 4] + * _.tail([1]) // => [] + * _.tail([]) // => [] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.init|init} + * @see {@link module:lamb.head|head}, {@link module:lamb.last|last} + * @since 0.16.0 + * @param {ArrayLike} arrayLike + * @returns {Array} + */ +var tail = drop(1); + +export default tail; diff --git a/src/array/take.js b/src/array/take.js new file mode 100644 index 0000000..afd060a --- /dev/null +++ b/src/array/take.js @@ -0,0 +1,26 @@ +import _curry2 from "../privates/_curry2"; +import takeFrom from "./takeFrom"; + +/** + * A curried version of {@link module:lamb.takeFrom|takeFrom} that expects the number of elements + * to retrieve to build a function waiting for the list to take the elements from.
+ * See the note and examples for {@link module:lamb.takeFrom|takeFrom} about passing a + * negative n. + * @example + * var take2 = _.take(2); + * + * take2([1, 2, 3, 4, 5]) // => [1, 2] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.takeFrom|takeFrom} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @since 0.5.0 + * @param {Number} n + * @returns {Function} + */ +var take = _curry2(takeFrom, true); + +export default take; diff --git a/src/array/takeFrom.js b/src/array/takeFrom.js new file mode 100644 index 0000000..e146faa --- /dev/null +++ b/src/array/takeFrom.js @@ -0,0 +1,28 @@ +import slice from "../core/slice"; + +/** + * Retrieves the first n elements from an array or array-like object.
+ * Note that, being this a shortcut for a common use case of {@link module:lamb.slice|slice}, + * n can be a negative number. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.takeFrom(arr, 3) // => [1, 2, 3] + * _.takeFrom(arr, -1) // => [1, 2, 3, 4] + * _.takeFrom(arr, -10) // => [] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.take|take} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @see {@link module:lamb.takeWhile|takeWhile}, {@link module:lamb.dropWhile|dropWhile} + * @since 0.51.0 + * @param {ArrayLike} arrayLike + * @param {Number} n + * @returns {Array} + */ +function takeFrom (arrayLike, n) { + return slice(arrayLike, 0, n); +} + +export default takeFrom; diff --git a/src/array/takeWhile.js b/src/array/takeWhile.js new file mode 100644 index 0000000..f0314c0 --- /dev/null +++ b/src/array/takeWhile.js @@ -0,0 +1,29 @@ +import _getNumConsecutiveHits from "../privates/_getNumConsecutiveHits"; +import slice from "../core/slice"; + +/** + * Builds a function that takes the first n elements satisfying a predicate from + * an array or array-like object. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var takeWhileIsEven = _.takeWhile(isEven); + * + * takeWhileIsEven([1, 2, 4, 6, 8]) // => [] + * takeWhileIsEven([2, 4, 7, 8]) // => [2, 4] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.dropWhile|dropWhile} + * @see {@link module:lamb.takeFrom|takeFrom}, {@link module:lamb.take|take} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @since 0.5.0 + * @param {ListIteratorCallback} predicate + * @returns {Function} + */ +function takeWhile (predicate) { + return function (arrayLike) { + return slice(arrayLike, 0, _getNumConsecutiveHits(arrayLike, predicate)); + }; +} + +export default takeWhile; diff --git a/src/array/transpose.js b/src/array/transpose.js new file mode 100644 index 0000000..6937476 --- /dev/null +++ b/src/array/transpose.js @@ -0,0 +1,61 @@ +import { MAX_ARRAY_LENGTH } from "../privates/_constants"; +import _toArrayLength from "../privates/_toArrayLength"; + +/** + * Transposes a matrix. Can also be used to reverse a {@link module:lamb.zip|zip} operation.
+ * Just like {@link module:lamb.zip|zip}, the received array-like objects will be truncated to the + * shortest length. + * @example Transposing a matrix: + * _.transpose([ + * [1, 2, 3], + * [4, 5, 6], + * [7, 8, 9] + * ]) // => + * // [ + * // [1, 4, 7], + * // [2, 5, 8], + * // [3, 6, 9] + * // ] + * + * @example Showing the relationship with zip: + * var zipped = _.zip(["a", "b", "c"], [1, 2, 3]); // => [["a", 1], ["b", 2], ["c", 3]] + * + * _.transpose(zipped) // => [["a", "b", "c"], [1, 2, 3]] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.zip|zip} + * @since 0.14.0 + * @param {ArrayLike} arrayLike + * @returns {Array} + */ +function transpose (arrayLike) { + var minLen = MAX_ARRAY_LENGTH; + var len = _toArrayLength(arrayLike.length); + + if (len === 0) { + return []; + } + + for (var j = 0, elementLen; j < len; j++) { + elementLen = _toArrayLength(arrayLike[j].length); + + if (elementLen < minLen) { + minLen = elementLen; + } + } + + var result = Array(minLen); + + for (var i = 0, el; i < minLen; i++) { + el = result[i] = Array(len); + + for (j = 0; j < len; j++) { + el[j] = arrayLike[j][i]; + } + } + + return result; +} + +export default transpose; diff --git a/src/array/union.js b/src/array/union.js new file mode 100644 index 0000000..842c829 --- /dev/null +++ b/src/array/union.js @@ -0,0 +1,29 @@ +import identity from "../core/identity"; +import unionBy from "./unionBy"; + +/** + * Returns a list of every unique element present in the two given array-like objects.
+ * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} + * to test the equality of values.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.unionBy|unionBy} if you need to transform the values before + * the comparison or if you have to extract them from complex ones. + * @example + * _.union([1, 2, 3, 2], [2, 3, 4]) // => [1, 2, 3, 4] + * _.union("abc", "bcd") // => ["a", "b", "c", "d"] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.unionBy|unionBy} + * @see {@link module:lamb.difference|difference} + * @see {@link module:lamb.intersection|intersection} + * @since 0.5.0 + * @param {ArrayLike} a + * @param {ArrayLike} b + * @returns {Array} + */ +var union = unionBy(identity); + +export default union; diff --git a/src/array/unionBy.js b/src/array/unionBy.js new file mode 100644 index 0000000..81973a1 --- /dev/null +++ b/src/array/unionBy.js @@ -0,0 +1,34 @@ +import binary from "../core/binary"; +import pipe from "../function/pipe"; +import drop from "./drop"; +import flatMapWith from "./flatMapWith"; +import list from "./list"; +import uniquesBy from "./uniquesBy"; + +/** + * Using the provided iteratee to transform values, builds a function that will + * return an array of the unique elements in the two provided array-like objects.
+ * Uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} + * to test the equality of values.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.union|union} if you don't need to compare transformed values. + * @example + * var unionByFloor = _.unionBy(Math.floor); + * + * unionByFloor([2.8, 3.2, 1.5], [3.5, 1.2, 4]) // => [2.8, 3.2, 1.5, 4] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.union|union} + * @see {@link module:lamb.difference|difference} + * @see {@link module:lamb.intersection|intersection} + * @since 0.51.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} + */ +function unionBy (iteratee) { + return pipe([binary(list), flatMapWith(drop(0)), uniquesBy(iteratee)]); +} + +export default unionBy; diff --git a/src/array/uniques.js b/src/array/uniques.js new file mode 100644 index 0000000..2fdac9a --- /dev/null +++ b/src/array/uniques.js @@ -0,0 +1,25 @@ +import identity from "../core/identity"; +import uniquesBy from "./uniquesBy"; + +/** + * Returns an array comprised of the unique elements of the given array-like object.
+ * Note that this function uses the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ} + * to test the equality of values.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.uniquesBy|uniquesBy} if you need to transform your values before + * the comparison or if you have to extract them from complex ones. + * @example + * _.uniques([-0, 1, 2, 0, 2, 3, 4, 3, 5, 1]) // => [-0, 1, 2, 3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.uniquesBy|uniquesBy} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @returns {Array} + */ +var uniques = uniquesBy(identity); + +export default uniques; diff --git a/src/array/uniquesBy.js b/src/array/uniquesBy.js new file mode 100644 index 0000000..5f0c11d --- /dev/null +++ b/src/array/uniquesBy.js @@ -0,0 +1,48 @@ +import isIn from "./isIn"; + +/** + * Using the provided iteratee, builds a function that will return an array comprised of the + * unique elements of an array-like object. The values being compared are the ones returned by + * the iteratee.
+ * The equality test is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
+ * When two values are considered equal, the first occurence will be the one included + * in the result array.
+ * See also {@link module:lamb.uniques|uniques} if you don't need to transform your values before the + * comparison. + * @example + * var data = [ + * {id: "1", name: "John"}, + * {id: "4", name: "Jane"}, + * {id: "5", name: "Joe"}, + * {id: "1", name: "Mario"}, + * {id: "5", name: "Paolo"}, + * ]; + * var uniquesById = _.uniquesBy(_.getKey("id")); + * + * uniquesById(data) // => [{id: "1", name: "John"}, {id: "4", name: "Jane"}, {id: "5", name: "Joe"}] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.uniques|uniques} + * @since 0.51.0 + * @param {ListIteratorCallback} iteratee + * @returns {Function} + */ +function uniquesBy (iteratee) { + return function (arrayLike) { + var result = []; + + for (var i = 0, len = arrayLike.length, seen = [], value; i < len; i++) { + value = iteratee(arrayLike[i], i, arrayLike); + + if (!isIn(seen, value)) { + seen.push(value); + result.push(arrayLike[i]); + } + } + + return result; + }; +} + +export default uniquesBy; diff --git a/src/array/updateAt.js b/src/array/updateAt.js new file mode 100644 index 0000000..8226a95 --- /dev/null +++ b/src/array/updateAt.js @@ -0,0 +1,31 @@ +import _setIndex from "../privates/_setIndex"; + +/** + * Builds a function that creates a copy of an array-like object with the given index + * changed by applying the provided function to its value.
+ * If the index is not an integer or if it's out of bounds, the function will return + * a copy of the original array.
+ * Negative indexes are allowed. + * @example + * var arr = ["a", "b", "c"]; + * var toUpperCase = _.invoker("toUpperCase"); + * + * _.updateAt(1, toUpperCase)(arr) // => ["a", "B", "c"] + * _.updateAt(-1, toUpperCase)(arr) // => ["a", "b", "C"] + * _.updateAt(10, toUpperCase)(arr) // => ["a", "b", "c"] (not a reference to `arr`) + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.updateIndex|updateIndex} + * @since 0.22.0 + * @param {Number} index + * @param {Function} updater + * @returns {Function} + */ +function updateAt (index, updater) { + return function (arrayLike) { + return _setIndex(arrayLike, index, null, updater); + }; +} + +export default updateAt; diff --git a/src/array/updateIndex.js b/src/array/updateIndex.js new file mode 100644 index 0000000..37c5298 --- /dev/null +++ b/src/array/updateIndex.js @@ -0,0 +1,31 @@ +import __ from "../core/__"; +import partial from "../core/partial"; +import _setIndex from "../privates/_setIndex"; + +/** + * Creates a copy of an array-like object with the given index changed by applying the + * provided function to its value.
+ * If the index is not an integer or if it's out of bounds, the function will return + * a copy of the original array.
+ * Negative indexes are allowed. + * @example + * var arr = ["a", "b", "c"]; + * var toUpperCase = _.invoker("toUpperCase"); + * + * _.updateIndex(arr, 1, toUpperCase) // => ["a", "B", "c"] + * _.updateIndex(arr, -1, toUpperCase) // => ["a", "b", "C"] + * _.updateIndex(arr, 10, toUpperCase) // => ["a", "b", "c"] (not a reference to `arr`) + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.updateAt|updateAt} + * @since 0.23.0 + * @param {ArrayLike} arrayLike + * @param {Number} index + * @param {Function} updater + * @returns {Array} + */ +var updateIndex = partial(_setIndex, [__, __, null, __]); + +export default updateIndex; diff --git a/src/array/zip.js b/src/array/zip.js new file mode 100644 index 0000000..27a7353 --- /dev/null +++ b/src/array/zip.js @@ -0,0 +1,28 @@ +import transpose from "./transpose"; + +/** + * Builds a list of arrays out of the two given array-like objects by pairing items with + * the same index.
+ * The received array-like objects will be truncated to the shortest length. + * @example + * _.zip( + * ["a", "b", "c"], + * [1, 2, 3] + * ) // => [["a", 1], ["b", 2], ["c", 3]] + * + * _.zip([1, 2, 3, 4], [5, 6, 7]) // => [[1, 5], [2, 6], [3, 7]] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.transpose|transpose} for the reverse operation + * @see {@link module:lamb.zipWithIndex|zipWithIndex} + * @since 0.14.0 + * @param {ArrayLike} a + * @param {ArrayLike} b + * @returns {Array} + */ +function zip (a, b) { + return transpose([a, b]); +} + +export default zip; diff --git a/src/array/zipWithIndex.js b/src/array/zipWithIndex.js new file mode 100644 index 0000000..6cb2d77 --- /dev/null +++ b/src/array/zipWithIndex.js @@ -0,0 +1,20 @@ +import binary from "../core/binary"; +import mapWith from "../core/mapWith"; +import list from "./list"; + +/** + * "{@link module:lamb.zip|Zips}" an array-like object by pairing its values with their index. + * @example + * _.zipWithIndex(["a", "b", "c"]) // => [["a", 0], ["b", 1], ["c", 2]] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.zip|zip} + * @since 0.14.0 + * @param {ArrayLike} arrayLike + * @returns {Array>} + */ +var zipWithIndex = mapWith(binary(list)); + +export default zipWithIndex; diff --git a/src/array_basics.js b/src/array_basics.js deleted file mode 100644 index 98a5437..0000000 --- a/src/array_basics.js +++ /dev/null @@ -1,636 +0,0 @@ -/** - * Builds a predicate to check if an array-like object contains the given value.
- * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can - * check for NaN, but 0 and -0 are the same value.
- * See also {@link module:lamb.isIn|isIn} for an uncurried version. - * @example - * var containsNaN = _.contains(NaN); - * - * containsNaN([0, 1, 2, 3, NaN]) // => true - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.isIn|isIn} - * @since 0.13.0 - * @param {*} value - * @returns {Function} - */ -var contains = _curry2(isIn, true); - -/** - * Checks if all the elements in an array-like object satisfy the given predicate.
- * The function will stop calling the predicate as soon as it returns a falsy value.
- * Note that an empty array-like will always produce a true result regardless of the - * predicate because of [vacuous truth]{@link https://en.wikipedia.org/wiki/Vacuous_truth}.
- * Also note that unlike the native - * [Array.prototype.every]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every}, - * this function won't skip deleted or unassigned indexes. - * @example - * var persons = [ - * {"name": "Jane", "age": 12, active: true}, - * {"name": "John", "age": 40, active: true}, - * {"name": "Mario", "age": 17, active: true}, - * {"name": "Paolo", "age": 15, active: true} - * ]; - * var isAdult = _.keySatisfies(_.isGTE(18), "age"); - * var isActive = _.hasKeyValue("active", true); - * - * _.everyIn(persons, isAdult) // => false - * _.everyIn(persons, isActive) // => true - * - * @example Showing the difference with Array.prototype.every: - * var isDefined = _.not(_.isUndefined); - * var arr = new Array(5); - * arr[3] = 99; - * - * arr.every(isDefined) // => true - * _.everyIn(arr, isDefined) // => false - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.every|every} - * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn} - * @since 0.39.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Boolean} - */ -var everyIn = _makeArrayChecker(true); - -/** - * A curried version of {@link module:lamb.everyIn|everyIn} that expects a predicate - * to build a function waiting for the array-like to act upon. - * @example - * var data = [2, 3, 5, 6, 8]; - * var isEven = function (n) { return n % 2 === 0; }; - * var allEvens = _.every(isEven); - * var allIntegers = _.every(_.isInteger); - * - * allEvens(data) // => false - * allIntegers(data) // => true - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.everyIn|everyIn} - * @see {@link module:lamb.some|some}, {@link module:lamb.someIn|someIn} - * @since 0.39.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -var every = _curry2(everyIn, true); - -/** - * Builds an array comprised of all values of the array-like object passing the predicate - * test.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @example - * var isLowerCase = function (s) { return s.toLowerCase() === s; }; - * - * _.filter(["Foo", "bar", "baZ"], isLowerCase) // => ["bar"] - * - * // the function will work with any array-like object - * _.filter("fooBAR", isLowerCase) // => ["f", "o", "o"] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.filterWith|filterWith} - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @since 0.1.0 - * @returns {Array} - */ -function filter (arrayLike, predicate) { - var len = arrayLike.length; - var result = []; - - for (var i = 0; i < len; i++) { - predicate(arrayLike[i], i, arrayLike) && result.push(arrayLike[i]); - } - - return result; -} - -/** - * A curried version of {@link module:lamb.filter|filter} that uses the given predicate - * to build a function expecting the array-like object to act upon. - * @example - * var isLowerCase = function (s) { return s.toLowerCase() === s; }; - * var getLowerCaseEntries = _.filterWith(isLowerCase); - * - * getLowerCaseEntries(["Foo", "bar", "baZ"]) // => ["bar"] - * - * // array-like objects can be used as well - * getLowerCaseEntries("fooBAR") // => ["f", "o", "o"] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.filter|filter} - * @since 0.9.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -var filterWith = _curry2(filter, true); - -/** - * Searches for an element satisfying the predicate in the given array-like object and returns it if - * the search is successful. Returns undefined otherwise. - * @example - * var persons = [ - * {"name": "Jane", "surname": "Doe", "age": 12}, - * {"name": "John", "surname": "Doe", "age": 40}, - * {"name": "Mario", "surname": "Rossi", "age": 18}, - * {"name": "Paolo", "surname": "Bianchi", "age": 40} - * ]; - * - * _.find(persons, _.hasKeyValue("age", 40)) // => {"name": "John", "surname": "Doe", "age": 40} - * _.find(persons, _.hasKeyValue("age", 41)) // => undefined - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.findWhere|findWhere} - * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} - * @since 0.7.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {*} - */ -function find (arrayLike, predicate) { - var idx = findIndex(arrayLike, predicate); - - return idx === -1 ? void 0 : arrayLike[idx]; -} - -/** - * Searches for an element satisfying the predicate in the given array-like object and returns its - * index if the search is successful. Returns -1 otherwise. - * @example - * var persons = [ - * {"name": "Jane", "surname": "Doe", "age": 12}, - * {"name": "John", "surname": "Doe", "age": 40}, - * {"name": "Mario", "surname": "Rossi", "age": 18}, - * {"name": "Paolo", "surname": "Bianchi", "age": 40} - * ]; - * - * _.findIndex(persons, _.hasKeyValue("age", 40)) // => 1 - * _.findIndex(persons, _.hasKeyValue("age", 41)) // => -1 - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.findIndexWhere|findIndexWhere} - * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} - * @since 0.7.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Number} - */ -function findIndex (arrayLike, predicate) { - var result = -1; - - for (var i = 0, len = arrayLike.length; i < len; i++) { - if (predicate(arrayLike[i], i, arrayLike)) { - result = i; - break; - } - } - - return result; -} - -/** - * A curried version of {@link module:lamb.findIndex|findIndex} that uses the given predicate - * to build a function expecting the array-like object to search. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var findEvenIdx = _.findIndexWhere(isEven); - * - * findEvenIdx([1, 3, 4, 5, 7]) // => 2 - * findEvenIdx([1, 3, 5, 7]) // => -1 - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.findIndex|findIndex} - * @see {@link module:lamb.find|find}, {@link module:lamb.findWhere|findWhere} - * @since 0.41.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -var findIndexWhere = _curry2(findIndex, true); - -/** - * A curried version of {@link module:lamb.find|find} expecting the array-like object - * to search. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var findEven = _.findWhere(isEven); - * - * findEven([1, 3, 4, 5, 7]) // => 4 - * findEven([1, 3, 5, 7]) // => undefined - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.find|find} - * @see {@link module:lamb.findIndex|findIndex}, {@link module:lamb.findIndexWhere|findIndexWhere} - * @since 0.41.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -var findWhere = _curry2(find, true); - -/** - * Executes the provided iteratee for each element of the given array-like object.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @example Adding a CSS class to all elements of a NodeList in a browser environment: - * var addClass = _.curry(function (className, element) { - * element.classList.add(className); - * }); - * var paragraphs = document.querySelectorAll("#some-container p"); - * - * _.forEach(paragraphs, addClass("main")); - * // each "p" element in the container will have the "main" class now - * - * @memberof module:lamb - * @category Array - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Undefined} - */ -function forEach (arrayLike, iteratee) { - for (var i = 0, len = _toArrayLength(arrayLike.length); i < len; i++) { - iteratee(arrayLike[i], i, arrayLike); - } -} - -/** - * Checks if an array-like object contains the given value.
- * Please note that the equality test is made with {@link module:lamb.areSVZ|areSVZ}; so you can - * check for NaN, but 0 and -0 are the same value.
- * See also {@link module:lamb.contains|contains} for a curried version building a predicate. - * @example - * var numbers = [0, 1, 2, 3, NaN]; - * - * _.isIn(numbers, 1) // => true - * _.isIn(numbers, 0) // => true - * _.isIn(numbers, -0) // => true - * _.isIn(numbers, NaN) // => true - * _.isIn(numbers, 5) // => false - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.contains|contains} - * @since 0.13.0 - * @param {ArrayLike} arrayLike - * @param {*} value - * @returns {Boolean} - */ -function isIn (arrayLike, value) { - var result = false; - - for (var i = 0, len = arrayLike.length; i < len; i++) { - if (areSVZ(value, arrayLike[i])) { - result = true; - break; - } - } - - return result; -} - -/** - * Generates an array with the values passed as arguments.
- * Behaves like ES6's [Array.of]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of}. - * @example - * _.list(1, 2, 3) // => [1, 2, 3] - * - * @memberof module:lamb - * @category Array - * @function - * @since 0.1.0 - * @param {...*} value - * @returns {Array} - */ -var list = _argsToArrayFrom(0); - -/** - * Builds a new array by applying the iteratee function to each element of the - * received array-like object.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @example - * _.map(["Joe", "Mario", "Jane"], _.invoker("toUpperCase")) // => ["JOE", "MARIO", "JANE"] - * - * _.map([4, 9, 16], Math.sqrt); // => [2, 3, 4] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.mapWith|mapWith} - * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Array} - */ -function map (arrayLike, iteratee) { - var len = _toArrayLength(arrayLike.length); - var result = Array(len); - - for (var i = 0; i < len; i++) { - result[i] = iteratee(arrayLike[i], i, arrayLike); - } - - return result; -} - -/** - * A curried version of {@link module:lamb.map|map} that uses the provided iteratee to - * build a function expecting the array-like object to act upon. - * @example - * var square = function (n) { return n * n; }; - * var getSquares = _.mapWith(square); - * - * getSquares([1, 2, 3, 4, 5]) // => [1, 4, 9, 16, 25] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.map|map} - * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} - * @since 0.1.0 - * @param {ListIteratorCallback} iteratee - * @returns {function} - */ -var mapWith = _curry2(map, true); - -/** - * Reduces (or folds) the values of an array-like object, starting from the first, to a new - * value using the provided accumulator function.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @example - * _.reduce([1, 2, 3, 4], _.sum) // => 10 - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.reduceRight|reduceRight} - * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] - * @returns {*} - */ -var reduce = _makeReducer(1); - -/** - * Same as {@link module:lamb.reduce|reduce}, but starts the fold operation from the last - * element instead.
- * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.reduce|reduce} - * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] - * @returns {*} - */ -var reduceRight = _makeReducer(-1); - -/** - * A partial application of {@link module:lamb.reduce|reduceRight} that uses the - * provided accumulator and the optional initialValue to - * build a function expecting the array-like object to act upon. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.reduceRightWith(_.sum)(arr) // => 15 - * _.reduceRightWith(_.subtract)(arr) // => -5 - * _.reduceRightWith(_.subtract, 0)(arr) // => -15 - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.reduceWith|reduceWith} - * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} - * @since 0.27.0 - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] - * @returns {Function} - */ -var reduceRightWith = _makePartial3(reduceRight, true); - -/** - * A partial application of {@link module:lamb.reduce|reduce} that uses the - * provided accumulator and the optional initialValue to - * build a function expecting the array-like object to act upon. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.reduceWith(_.sum)(arr) // => 15 - * _.reduceWith(_.subtract)(arr) // => -13 - * _.reduceWith(_.subtract, 0)(arr) // => -15 - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.reduceRightWith|reduceRightWith} - * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} - * @since 0.27.0 - * @param {AccumulatorCallback} accumulator - * @param {*} [initialValue] - * @returns {Function} - */ -var reduceWith = _makePartial3(reduce, true); - -/** - * Reverses a copy of the given array-like object. - * @example - * var arr = [1, 2, 3]; - * - * _.reverse(arr) // => [3, 2, 1]; - * - * // `arr` still is [1, 2, 3] - * - * @memberof module:lamb - * @category Array - * @since 0.19.0 - * @param {ArrayLike} arrayLike - * @returns {Array} - */ -function reverse (arrayLike) { - var len = _toArrayLength(arrayLike.length); - var result = Array(len); - - for (var i = 0, ofs = len - 1; i < len; i++) { - result[i] = arrayLike[ofs - i]; - } - - return result; -} - -/** - * Builds an array by extracting a portion of an array-like object.
- * Note that unlike the native array method this function ensures that dense - * arrays are returned.
- * Also, unlike the native method, the start and end - * parameters aren't optional and will be simply converted to integer.
- * See {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a - * slice to the end of the array-like. - * @example - * var arr = [1, 2, 3, 4, 5]; - * - * _.slice(arr, 0, 2) // => [1, 2] - * _.slice(arr, 2, -1) // => [3, 4] - * _.slice(arr, -3, 5) // => [3, 4, 5] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.sliceAt|sliceAt} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @since 0.1.0 - * @param {ArrayLike} arrayLike - Any array like object. - * @param {Number} start - Index at which to begin extraction. - * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. - * @returns {Array} - */ -function slice (arrayLike, start, end) { - var len = _toArrayLength(arrayLike.length); - var begin = _toInteger(start); - var upTo = _toInteger(end); - - if (begin < 0) { - begin = begin < -len ? 0 : begin + len; - } - - if (upTo < 0) { - upTo = upTo < -len ? 0 : upTo + len; - } else if (upTo > len) { - upTo = len; - } - - var resultLen = upTo - begin; - var result = resultLen > 0 ? Array(resultLen) : []; - - for (var i = 0; i < resultLen; i++) { - result[i] = arrayLike[begin + i]; - } - - return result; -} - -/** - * Given the start and end bounds, builds a partial application - * of {@link module:lamb.slice|slice} expecting the array-like object to slice.
- * See also {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a - * slice to the end of the array-like. - * @example - * var arr = [1, 2, 3, 4, 5]; - * var s = "hello"; - * var dropFirstAndLast = _.sliceAt(1, -1); - * - * dropFirstAndLast(arr) // => [2, 3, 4] - * dropFirstAndLast(s) // => ["e", "l", "l"] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.slice|slice} - * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} - * @since 0.48.0 - * @param {Number} start - Index at which to begin extraction. - * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. - * @returns {Function} - */ -var sliceAt = _makePartial3(slice); - -/** - * Checks if at least one element in an array-like object satisfies the given predicate.
- * The function will stop calling the predicate as soon as it returns a truthy value.
- * Note that unlike the native - * [Array.prototype.some]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some}, - * this function won't skip deleted or unassigned indexes. - * @example - * var persons = [ - * {"name": "Jane", "age": 12, active: false}, - * {"name": "John", "age": 40, active: false}, - * {"name": "Mario", "age": 17, active: false}, - * {"name": "Paolo", "age": 15, active: false} - * ]; - * var isAdult = _.keySatisfies(_.isGTE(18), "age"); - * var isActive = _.hasKeyValue("active", true); - * - * _.someIn(persons, isAdult) // => true - * _.someIn(persons, isActive) // => false - * - * @example Showing the difference with Array.prototype.some: - * var arr = new Array(5); - * arr[3] = 99; - * - * arr.some(_.isUndefined) // => false - * _.someIn(arr, _.isUndefined) // => true - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.some|some} - * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} - * @since 0.39.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Boolean} - */ -var someIn = _makeArrayChecker(false); - -/** - * A curried version of {@link module:lamb.someIn|someIn} that uses the given predicate to - * build a function waiting for the array-like to act upon. - * @example - * var data = [1, 3, 5, 6, 7, 8]; - * var isEven = function (n) { return n % 2 === 0; }; - * var containsEvens = _.some(isEven); - * var containsStrings = _.some(_.isType("String")); - * - * containsEvens(data) // => true - * containsStrings(data) // => false - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.someIn|someIn} - * @see {@link module:lamb.every|every}, {@link module:lamb.everyIn|everyIn} - * @since 0.39.0 - * @param {ListIteratorCallback} predicate - * @returns {Function} - */ -var some = _curry2(someIn, true); - -lamb.contains = contains; -lamb.every = every; -lamb.everyIn = everyIn; -lamb.filter = filter; -lamb.filterWith = filterWith; -lamb.find = find; -lamb.findIndex = findIndex; -lamb.findIndexWhere = findIndexWhere; -lamb.findWhere = findWhere; -lamb.forEach = forEach; -lamb.isIn = isIn; -lamb.list = list; -lamb.map = map; -lamb.mapWith = mapWith; -lamb.reduce = reduce; -lamb.reduceRight = reduceRight; -lamb.reduceRightWith = reduceRightWith; -lamb.reduceWith = reduceWith; -lamb.reverse = reverse; -lamb.slice = slice; -lamb.sliceAt = sliceAt; -lamb.some = some; -lamb.someIn = someIn; diff --git a/src/core.js b/src/core.js deleted file mode 100644 index 55dfd5b..0000000 --- a/src/core.js +++ /dev/null @@ -1,223 +0,0 @@ - -/** - * Builds a function that returns a constant value. - * It's actually the simplest form of the K combinator or Kestrel. - * @example - * var truth = _.always(true); - * - * truth() // => true - * truth(false) // => true - * truth(1, 2) // => true - * - * // the value being returned is actually the - * // very same value passed to the function - * var foo = {bar: "baz"}; - * var alwaysFoo = _.always(foo); - * - * alwaysFoo() === foo // => true - * - * @memberof module:lamb - * @category Function - * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus} - * @since 0.1.0 - * @param {*} value - * @returns {Function} - */ -function always (value) { - return function () { - return value; - }; -} - -/** - * Returns a function that is the composition of the functions given as parameters. - * The first function consumes the result of the function that follows. - * @example - * var sayHi = function (name) { return "Hi, " + name; }; - * var capitalize = function (s) { - * return s[0].toUpperCase() + s.substr(1).toLowerCase(); - * }; - * var fixNameAndSayHi = _.compose(sayHi, capitalize); - * - * sayHi("bOb") // => "Hi, bOb" - * fixNameAndSayHi("bOb") // "Hi, Bob" - * - * var users = [{name: "fred"}, {name: "bOb"}]; - * var sayHiToUser = _.compose(fixNameAndSayHi, _.getKey("name")); - * - * _.map(users, sayHiToUser) // ["Hi, Fred", "Hi, Bob"] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.pipe|pipe} - * @since 0.1.0 - * @param {Function} a - * @param {Function} b - * @returns {Function} - */ -function compose (a, b) { - return arguments.length ? function () { - return a.call(this, b.apply(this, arguments)); - } : identity; -} - -/** - * Creates generic functions out of methods. - * @author A very little change on a great idea by [Irakli Gozalishvili]{@link https://github.com/Gozala/}. - * Thanks for this *beautiful* one-liner (never liked your "unbind" naming choice, though). - * @memberof module:lamb - * @category Function - * @function - * @example - * var join = _.generic(Array.prototype.join); - * - * join([1, 2, 3, 4, 5], "-") // => "1-2-3-4-5" - * - * // the function will work with any array-like object - * join("hello", "-") // => "h-e-l-l-o" - * - * @since 0.1.0 - * @param {Function} method - * @returns {Function} - */ -var generic = Function.bind.bind(Function.call); - -/** - * The I combinator. Any value passed to the function is simply returned as it is. - * @example - * var foo = {bar: "baz"}; - * - * _.identity(foo) === foo // true - * - * @memberof module:lamb - * @category Function - * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus} - * @since 0.1.0 - * @param {*} value - * @returns {*} The value passed as parameter. - */ -function identity (value) { - return value; -} - -/** - * Builds a partially applied function. The lamb object itself can be - * used as a placeholder argument and it's useful to alias it with a short symbol - * such as _.
- * You can use a custom placeholder by setting the - * {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} property. - * @example - * var users = [ - * {id: 1, name: "John", active: true, confirmedMail: true}, - * {id: 2, name: "Jane", active: true, confirmedMail: false}, - * {id: 3, name: "Mario", active: false, confirmedMail: false} - * ]; - * var isKeyTrue = _.partial(_.hasKeyValue, [_, true]); - * var isActive = isKeyTrue("active"); - * var hasConfirmedMail = isKeyTrue("confirmedMail"); - * - * _.map(users, isActive) // => [true, true, false] - * _.map(users, hasConfirmedMail) // => [true, false, false] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} - * @since 0.1.0 - * @param {Function} fn - * @param {Array} args - * @returns {Function} - */ -function partial (fn, args) { - return function () { - if (!Array.isArray(args)) { - return fn.apply(this, arguments); - } - - var lastIdx = 0; - var newArgs = []; - var argsLen = args.length; - - for (var i = 0, boundArg; i < argsLen; i++) { - boundArg = args[i]; - newArgs[i] = _isPlaceholder(boundArg) ? arguments[lastIdx++] : boundArg; - } - - for (var len = arguments.length; lastIdx < len; lastIdx++) { - newArgs[i++] = arguments[lastIdx]; - } - - return fn.apply(this, newArgs); - }; -} - -/** - * Like {@link module:lamb.partial|partial} will build a partially applied function and - * it will accept placeholders.
- * The difference is that the bound arguments will be appended to the ones received by - * the resulting function. - * @example - * Explaining the difference with partial: - * var f1 = _.partial(_.list, ["a", "b", "c"]); - * var f2 = _.partialRight(_.list, ["a", "b", "c"]); - * - * f1("d", "e") // => ["a", "b", "c", "d", "e"] - * f2("d", "e") // => ["d", "e", "a", "b", "c"] - * - * @example - * Explaining placeholder substitutions: - * var f1 = _.partial(_.list, ["a", _, _, "d"]); - * var f2 = _.partialRight(_.list, ["a", _, _, "d"]); - * - * f1("b", "c", "e") // => ["a", "b", "c", "d", "e"] - * f2("b", "c", "e") // => ["b", "a", "c", "e", "d"] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.partial|partial} - * @see {@link module:lamb.asPartial|asPartial} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} - * @param {Function} fn - * @param {Array} args - * @since 0.52.0 - * @returns {Function} - */ -function partialRight (fn, args) { - return function () { - if (!Array.isArray(args)) { - return fn.apply(this, arguments); - } - - var lastIdx = arguments.length - 1; - var argsLen = args.length; - var boundArgs = Array(argsLen); - var newArgs = []; - - for (var i = argsLen - 1, boundArg; i > -1; i--) { - boundArg = args[i]; - boundArgs[i] = _isPlaceholder(boundArg) ? arguments[lastIdx--] : boundArg; - } - - for (i = 0; i <= lastIdx; i++) { - newArgs[i] = arguments[i]; - } - - for (var j = 0; j < argsLen; j++) { - newArgs[i++] = boundArgs[j]; - } - - return fn.apply(this, newArgs); - }; -} - -lamb.always = always; -lamb.compose = compose; -lamb.generic = generic; -lamb.identity = identity; -lamb.partial = partial; -lamb.partialRight = partialRight; diff --git a/src/core/__.js b/src/core/__.js new file mode 100644 index 0000000..dad07ee --- /dev/null +++ b/src/core/__.js @@ -0,0 +1,11 @@ +/** + * The placeholder object used in partial application. + * @memberof module:lamb + * @alias module:lamb.__ + * @category Special properties + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.57.0 + * @type {Object} + */ +export default {}; diff --git a/src/core/__tests__/always.spec.js b/src/core/__tests__/always.spec.js new file mode 100644 index 0000000..50bb2d0 --- /dev/null +++ b/src/core/__tests__/always.spec.js @@ -0,0 +1,20 @@ +import * as lamb from "../.."; + +describe("lamb.core", function () { + describe("always", function () { + it("should return a function that returns a constant value", function () { + var o = { a: 1 }; + var fn = lamb.always(o); + var r1 = fn("foo"); + var r2 = fn(); + + expect(r1).toBe(o); + expect(r2).toBe(o); + expect(r1).toBe(r2); + }); + + it("should build a function returning `undefined` if called without arguments", function () { + expect(lamb.always()()).toBeUndefined(); + }); + }); +}); diff --git a/src/core/__tests__/areSVZ.spec.js b/src/core/__tests__/areSVZ.spec.js new file mode 100644 index 0000000..2c67e63 --- /dev/null +++ b/src/core/__tests__/areSVZ.spec.js @@ -0,0 +1,21 @@ +import * as lamb from "../.."; + +describe("areSVZ / isSVZ", function () { + it("should verify the equality of two values using the \"SameValueZero\" comparison", function () { + var o = { foo: "bar" }; + + expect(lamb.areSVZ(o, o)).toBe(true); + expect(lamb.areSVZ(o, { foo: "bar" })).toBe(false); + expect(lamb.areSVZ(42, 42)).toBe(true); + expect(lamb.areSVZ([], [])).toBe(false); + expect(lamb.areSVZ(0, -0)).toBe(true); + expect(lamb.areSVZ(NaN, NaN)).toBe(true); + + expect(lamb.isSVZ(o)(o)).toBe(true); + expect(lamb.isSVZ(o)({ foo: "bar" })).toBe(false); + expect(lamb.isSVZ(42)(42)).toBe(true); + expect(lamb.isSVZ([])([])).toBe(false); + expect(lamb.isSVZ(0)(-0)).toBe(true); + expect(lamb.isSVZ(NaN)(NaN)).toBe(true); + }); +}); diff --git a/src/core/__tests__/binary.spec.js b/src/core/__tests__/binary.spec.js new file mode 100644 index 0000000..1f824cd --- /dev/null +++ b/src/core/__tests__/binary.spec.js @@ -0,0 +1,42 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("binary", function () { + var listSpy = jest.fn(lamb.list); + var binaryList = lamb.binary(listSpy); + + afterEach(function () { + listSpy.mockClear(); + }); + + it("should build a function that passes only two arguments to the given one", function () { + expect(binaryList.length).toBe(2); + expect(binaryList(1, 2, 3)).toEqual([1, 2]); + expect(listSpy.mock.calls[0]).toEqual([1, 2]); + }); + + it("should add `undefined` arguments if the received parameters aren't two", function () { + expect(binaryList()).toEqual([void 0, void 0]); + expect(binaryList(1)).toEqual([1, void 0]); + }); + + it("should not modify the function's context", function () { + var fn = function () { + this.values = this.values.concat(lamb.slice(arguments, 0, arguments.length)); + }; + + var obj = { values: [1, 2, 3], addValues: lamb.binary(fn) }; + + obj.addValues(4, 5, 6, 7); + + expect(obj.values).toEqual([1, 2, 3, 4, 5]); + }); + + it("should build a function throwing an exception if the `fn` parameter isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.binary(value)).toThrow(); + }); + + expect(lamb.binary()).toThrow(); + }); +}); diff --git a/src/core/__tests__/clamp.spec.js b/src/core/__tests__/clamp.spec.js new file mode 100644 index 0000000..5c3ccba --- /dev/null +++ b/src/core/__tests__/clamp.spec.js @@ -0,0 +1,75 @@ +import * as lamb from "../.."; +import { wannabeNaNs } from "../../__tests__/commons"; + +describe("clamp / clampWithin", function () { + it("should clamp a number within the given limits", function () { + expect(lamb.clamp(0, -5, 5)).toBe(0); + expect(lamb.clamp(-5, -5, 5)).toBe(-5); + expect(lamb.clamp(5, -5, 5)).toBe(5); + expect(lamb.clamp(-6, -5, 5)).toBe(-5); + expect(lamb.clamp(6, -5, 5)).toBe(5); + expect(lamb.clamp(5, 5, 5)).toBe(5); + expect(Object.is(lamb.clamp(-0, 0, 0), -0)).toBe(true); + expect(Object.is(lamb.clamp(0, -0, 0), 0)).toBe(true); + expect(Object.is(lamb.clamp(0, 0, -0), 0)).toBe(true); + + expect(lamb.clampWithin(-5, 5)(0)).toBe(0); + expect(lamb.clampWithin(-5, 5)(-5)).toBe(-5); + expect(lamb.clampWithin(-5, 5)(5)).toBe(5); + expect(lamb.clampWithin(-5, 5)(-6)).toBe(-5); + expect(lamb.clampWithin(-5, 5)(6)).toBe(5); + expect(lamb.clampWithin(5, 5)(5)).toBe(5); + expect(Object.is(lamb.clampWithin(0, 0)(-0), -0)).toBe(true); + expect(Object.is(lamb.clampWithin(-0, 0)(0), 0)).toBe(true); + expect(Object.is(lamb.clampWithin(0, -0)(0), 0)).toBe(true); + }); + + it("should return `NaN` if `min` is greater than `max`", function () { + expect(lamb.clamp(5, 10, 7)).toEqual(NaN); + expect(lamb.clampWithin(10, 7)(5)).toEqual(NaN); + }); + + it("should convert all expected arguments to `Number`, received or not", function () { + var d1 = new Date(2015, 1, 1); + var d2 = new Date(2016, 1, 1); + var d3 = new Date(2017, 1, 1); + + expect(lamb.clamp(d1, d2, d3)).toBe(+d2); + expect(lamb.clamp([97], [98], [99])).toBe(98); + expect(lamb.clamp("5", "3", "4")).toBe(4); + expect(lamb.clamp("5", "3", "4")).toBe(4); + expect(lamb.clamp(true, false, true)).toBe(1); + expect(lamb.clamp(null, -1, 1)).toBe(0); + expect(lamb.clamp(-1, null, 2)).toBe(0); + expect(lamb.clamp(5, -1, null)).toBe(0); + + expect(lamb.clampWithin(d2, d3)(d1)).toBe(+d2); + expect(lamb.clampWithin([98], [99])([97])).toBe(98); + expect(lamb.clampWithin(false, true)(true)).toBe(1); + expect(lamb.clampWithin(-1, 1)(null)).toBe(0); + expect(lamb.clampWithin(null, 2)(-1)).toBe(0); + expect(lamb.clampWithin(-1, null)(5)).toBe(0); + + wannabeNaNs.forEach(function (value) { + expect(lamb.clamp(value, 99, 100)).toEqual(NaN); + expect(lamb.clamp(99, value, 100)).toBe(99); + expect(lamb.clamp(99, value, 98)).toBe(98); + expect(lamb.clamp(99, 98, value)).toBe(99); + expect(lamb.clamp(99, 100, value)).toBe(100); + + expect(lamb.clampWithin(99, 100)(value)).toEqual(NaN); + expect(lamb.clampWithin(value, 100)(99)).toBe(99); + expect(lamb.clampWithin(value, 98)(99)).toBe(98); + expect(lamb.clampWithin(98, value)(99)).toBe(99); + expect(lamb.clampWithin(100, value)(99)).toBe(100); + }); + + expect(lamb.clamp(5, 10)).toBe(10); + expect(lamb.clamp(-5, void 0, 10)).toBe(-5); + expect(lamb.clamp()).toEqual(NaN); + + expect(lamb.clampWithin(10)(5)).toBe(10); + expect(lamb.clampWithin(void 0, 10)(-5)).toBe(-5); + expect(lamb.clampWithin()()).toEqual(NaN); + }); +}); diff --git a/src/core/__tests__/compose.spec.js b/src/core/__tests__/compose.spec.js new file mode 100644 index 0000000..a3fc849 --- /dev/null +++ b/src/core/__tests__/compose.spec.js @@ -0,0 +1,43 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("compose", function () { + var double = function (n) { return n * 2; }; + var cube = function (n) { return Math.pow(n, 3); }; + var changeSign = function (n) { return -n; }; + + var cubeAndDouble = lamb.compose(double, cube); + var doubleAndCube = lamb.compose(cube, double); + + it("should return a function that is the composition of the given functions; the first one consuming the return value of the function that follows", function () { + expect(cubeAndDouble(5)).toBe(250); + expect(doubleAndCube(5)).toBe(1000); + }); + + it("should be possible to reuse composed functions", function () { + var changeSignCubeAndDouble = lamb.compose(cubeAndDouble, changeSign); + + expect(changeSignCubeAndDouble(2)).toBe(-16); + }); + + it("should behave like `identity` if no functions are passed", function () { + var obj = {}; + + expect(lamb.compose()(obj)).toBe(obj); + expect(lamb.compose()()).toBeUndefined(); + expect(lamb.compose()(2, 3, 4)).toBe(2); + }); + + it("should ignore extra arguments", function () { + expect(lamb.compose(double, cube, changeSign)(5)).toBe(250); + }); + + it("should build a function throwing an exception if any parameter is not a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.compose(lamb.identity, value)).toThrow(); + expect(lamb.compose(value, lamb.identity)).toThrow(); + }); + + expect(lamb.compose(lamb.identity)).toThrow(); + }); +}); diff --git a/src/core/__tests__/forEach.spec.js b/src/core/__tests__/forEach.spec.js new file mode 100644 index 0000000..87a69ca --- /dev/null +++ b/src/core/__tests__/forEach.spec.js @@ -0,0 +1,58 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("forEach", function () { + var arr = [1, 2, 3, 4, 5]; + var fn = function (el, idx, list) { + expect(list[idx]).toBe(el); + }; + + it("should execute the given function for every element of the provided array", function () { + expect(lamb.forEach(arr, fn)).toBeUndefined(); + }); + + it("should work with array-like objects", function () { + expect(lamb.forEach("foo bar", fn)).toBeUndefined(); + }); + + it("should not skip deleted or unassigned elements, unlike the native method", function () { + var sparseArr = Array(3); + + sparseArr[1] = 3; + + var dummyFn = jest.fn(); + + expect(lamb.forEach(sparseArr, dummyFn)).toBeUndefined(); + expect(dummyFn).toHaveBeenCalledTimes(3); + expect(dummyFn.mock.calls[0]).toEqual([void 0, 0, sparseArr]); + expect(dummyFn.mock.calls[1]).toEqual([3, 1, sparseArr]); + expect(dummyFn.mock.calls[2]).toEqual([void 0, 2, sparseArr]); + }); + + it("should throw an exception if the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.forEach(arr, value); }).toThrow(); + }); + + expect(function () { lamb.forEach(arr); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.forEach).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.forEach(null, fn); }).toThrow(); + expect(function () { lamb.forEach(void 0, fn); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + var dummyFn = jest.fn(); + + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.forEach(value, dummyFn)).toBeUndefined(); + }); + + expect(dummyFn).toHaveBeenCalledTimes(0); + }); +}); diff --git a/src/core/__tests__/generic.spec.js b/src/core/__tests__/generic.spec.js new file mode 100644 index 0000000..3a45a80 --- /dev/null +++ b/src/core/__tests__/generic.spec.js @@ -0,0 +1,32 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("generic", function () { + it("should create a generic function out of an object method", function () { + var arrayProto = Array.prototype; + + jest.spyOn(arrayProto, "map"); + + var fakeContext = {}; + var double = function (n) { + expect(this).toBe(fakeContext); + + return n * 2; + }; + var numbers = [1, 2, 3, 4, 5]; + var map = lamb.generic(arrayProto.map); + + expect(map(numbers, double, fakeContext)).toEqual([2, 4, 6, 8, 10]); + expect(arrayProto.map).toHaveBeenCalledTimes(1); + expect(arrayProto.map.mock.calls[0][0]).toBe(double); + expect(arrayProto.map.mock.calls[0][1]).toBe(fakeContext); + }); + + it("should build a function throwing an exception if called without arguments or if `method` isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.generic(value)).toThrow(); + }); + + expect(lamb.generic()).toThrow(); + }); +}); diff --git a/src/core/__tests__/identity.spec.js b/src/core/__tests__/identity.spec.js new file mode 100644 index 0000000..949da72 --- /dev/null +++ b/src/core/__tests__/identity.spec.js @@ -0,0 +1,14 @@ +import * as lamb from "../.."; + +describe("identity", function () { + it("should return the value received as argument", function () { + expect(lamb.identity(0)).toBe(0); + expect(lamb.identity("foo")).toBe("foo"); + expect(lamb.identity(null)).toBe(null); + expect(lamb.identity()).toBeUndefined(); + + var someRefType = { foo: 5 }; + + expect(lamb.identity(someRefType)).toBe(someRefType); + }); +}); diff --git a/src/core/__tests__/isNil.spec.js b/src/core/__tests__/isNil.spec.js new file mode 100644 index 0000000..c98d466 --- /dev/null +++ b/src/core/__tests__/isNil.spec.js @@ -0,0 +1,19 @@ +import * as lamb from "../.."; +import { nonNils } from "../../__tests__/commons"; + +describe("isNil", function () { + it("should verify if the given value is null or undefined", function () { + expect(lamb.isNil(null)).toBe(true); + expect(lamb.isNil(void 0)).toBe(true); + }); + + it("should return true if called without arguments", function () { + expect(lamb.isNil()).toBe(true); + }); + + it("should return false for every other value", function () { + nonNils.forEach(function (value) { + expect(lamb.isNil(value)).toBe(false); + }); + }); +}); diff --git a/src/core/__tests__/isNull.spec.js b/src/core/__tests__/isNull.spec.js new file mode 100644 index 0000000..d617944 --- /dev/null +++ b/src/core/__tests__/isNull.spec.js @@ -0,0 +1,18 @@ +import * as lamb from "../.."; +import { nonNulls } from "../../__tests__/commons"; + +describe("isNull", function () { + it("should verify if the given value is null", function () { + expect(lamb.isNull(null)).toBe(true); + }); + + it("should return false if called without arguments", function () { + expect(lamb.isNull()).toBe(false); + }); + + it("should return false for every other value", function () { + nonNulls.forEach(function (value) { + expect(lamb.isNull(value)).toBe(false); + }); + }); +}); diff --git a/src/core/__tests__/isUndefined.spec.js b/src/core/__tests__/isUndefined.spec.js new file mode 100644 index 0000000..083e726 --- /dev/null +++ b/src/core/__tests__/isUndefined.spec.js @@ -0,0 +1,18 @@ +import * as lamb from "../.."; +import { nonUndefineds } from "../../__tests__/commons"; + +describe("isUndefined", function () { + it("should verify if the given value is undefined", function () { + expect(lamb.isUndefined(void 0)).toBe(true); + }); + + it("should return true if called without arguments", function () { + expect(lamb.isUndefined()).toBe(true); + }); + + it("should return false for every other value", function () { + nonUndefineds.forEach(function (value) { + expect(lamb.isUndefined(value)).toBe(false); + }); + }); +}); diff --git a/src/core/__tests__/map.spec.js b/src/core/__tests__/map.spec.js new file mode 100644 index 0000000..85df57c --- /dev/null +++ b/src/core/__tests__/map.spec.js @@ -0,0 +1,66 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("map / mapWith", function () { + var double = function (n, idx, list) { + expect(list[idx]).toBe(n); + + return n * 2; + }; + var makeDoubles = lamb.mapWith(double); + var numbers = [1, 2, 3, 4, 5]; + + afterEach(function () { + expect(numbers).toEqual([1, 2, 3, 4, 5]); + }); + + it("should apply the provided function to the elements of the given array", function () { + expect(lamb.map(numbers, double)).toEqual([2, 4, 6, 8, 10]); + expect(makeDoubles(numbers)).toEqual([2, 4, 6, 8, 10]); + }); + + it("should work with array-like objects", function () { + expect(lamb.map("12345", double)).toEqual([2, 4, 6, 8, 10]); + expect(makeDoubles("12345")).toEqual([2, 4, 6, 8, 10]); + }); + + it("should not skip deleted or unassigned elements, unlike the native method", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + var sparseArr = [, "foo", ,]; + var safeUpperCase = lamb.compose(lamb.invoker("toUpperCase"), String); + var result = ["UNDEFINED", "FOO", "UNDEFINED"]; + + expect(lamb.map(sparseArr, safeUpperCase)).toStrictArrayEqual(result); + expect(lamb.map(sparseArr, lamb.identity)).toStrictArrayEqual([void 0, "foo", void 0]); + expect(lamb.mapWith(safeUpperCase)(sparseArr)).toStrictArrayEqual(result); + expect(lamb.mapWith(lamb.identity)(sparseArr)).toStrictArrayEqual([void 0, "foo", void 0]); + }); + + it("should throw an exception if the iteratee isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.mapWith(value)(numbers); }).toThrow(); + }); + + expect(function () { lamb.mapWith()(numbers); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.map).toThrow(); + expect(makeDoubles).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.map(null, double); }).toThrow(); + expect(function () { lamb.map(void 0, double); }).toThrow(); + expect(function () { makeDoubles(null); }).toThrow(); + expect(function () { makeDoubles(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.map(value, double)).toEqual([]); + expect(makeDoubles(value)).toEqual([]); + }); + }); +}); diff --git a/src/core/__tests__/partial.spec.js b/src/core/__tests__/partial.spec.js new file mode 100644 index 0000000..200bef8 --- /dev/null +++ b/src/core/__tests__/partial.spec.js @@ -0,0 +1,112 @@ +import * as lamb from "../.."; +import { nonArrayLikes, nonFunctions } from "../../__tests__/commons"; + +describe("partial / partialRight", function () { + var foo = function (a, b, c, d, e) { + return a + b + c + d + e; + }; + var __ = lamb.__; + + it("should return a partially applied function using the given arguments", function () { + var f1 = lamb.partial(foo, ["a", "b", "c"]); + var f2 = lamb.partialRight(foo, ["a", "b", "c"]); + + expect(f1("d", "e")).toBe("abcde"); + expect(f2("d", "e")).toBe("deabc"); + }); + + it("should accept placeholders as arguments", function () { + var f1 = lamb.partial(foo, [__, __, "c", __, "e"]); + var f2 = lamb.partialRight(foo, [__, __, "c", __, "e"]); + var f3 = lamb.partial(foo, [__, "c", __, "e"]); + var f4 = lamb.partialRight(foo, [__, "c", __, "e"]); + + expect(f1("a", "b", "d")).toBe("abcde"); + expect(f2("a", "b", "d")).toBe("abcde"); + expect(f3("a", "b", "d")).toBe("acbed"); + expect(f4("a", "b", "d")).toBe("abcde"); + }); + + it("should be possible to create a partial application from another partial application", function () { + var f1 = lamb.partial(foo, [__, "b", __, "d"]); + var f2 = lamb.partialRight(foo, [__, "b", __, "d"]); + + expect(lamb.partial(f1, ["a", __, "e"])("c")).toBe("abcde"); + expect(lamb.partial(f2, ["a", __, "e"])("c")).toBe("acbed"); + }); + + it("should give an `undefined` value to unfilled placeholders", function () { + var f1 = lamb.partial(lamb.list, [__, 2, __, 3, __, 5, __]); + var f2 = lamb.partialRight(lamb.list, [__, 2, __, 3, __, 5, __]); + + expect(f1(99)).toEqual([99, 2, void 0, 3, void 0, 5, void 0]); + expect(f1(98, 99)).toEqual([98, 2, 99, 3, void 0, 5, void 0]); + expect(f2(99)).toEqual([void 0, 2, void 0, 3, void 0, 5, 99]); + expect(f2(98, 99)).toEqual([void 0, 2, void 0, 3, 98, 5, 99]); + }); + + it("should be safe to call the partial application multiple times with different values for unfilled placeholders", function () { + var list = [{ id: "foo" }, { id: "bar" }, { id: "baz" }]; + var getID1 = lamb.partial(lamb.getIn, [__, "id"]); + var getID2 = lamb.unary(lamb.partialRight(lamb.getIn, [__, "id"])); + + expect(list.map(getID1)).toEqual(["foo", "bar", "baz"]); + expect(list.map(getID2)).toEqual(["foo", "bar", "baz"]); + }); + + it("should pass all the received arguments to the partially applied function, even if they exceed the original function's arity", function () { + function binaryFoo (a, b) { + return a + b + arguments[2] + arguments[3] + arguments[4]; + } + + expect(lamb.partial(binaryFoo, ["a", __, __, "d"])("b", "c", "e")).toBe("abcde"); + expect(lamb.partialRight(binaryFoo, ["a", __, __, "d"])("b", "c", "e")).toBe("baced"); + }); + + it("should preserve the function's context", function () { + var fn = function (a, b) { + this.values.push(a - b); + }; + + var obj = { + values: [1, 2, 3], + foo: lamb.partial(fn, [4, __]), + bar: lamb.partialRight(fn, [__, 4]) + }; + + obj.foo(5); + expect(obj.values).toEqual([1, 2, 3, -1]); + + obj.bar(5); + expect(obj.values).toEqual([1, 2, 3, -1, 1]); + }); + + it("should consider non-arrays received as the `args` parameter as empty arrays", function () { + var divideSpy = jest.fn(lamb.divide); + + nonArrayLikes.forEach(function (value, idx) { + expect(lamb.partial(divideSpy, value)(5, 4)).toBe(1.25); + expect(lamb.partialRight(divideSpy, value)(5, 4)).toBe(1.25); + expect(divideSpy.mock.calls[idx * 2]).toEqual([5, 4]); + expect(divideSpy.mock.calls[idx * 2 + 1]).toEqual([5, 4]); + }); + + expect(lamb.partial(divideSpy)(5, 4)).toBe(1.25); + expect(lamb.partialRight(divideSpy)(5, 4)).toBe(1.25); + expect(divideSpy.mock.calls[nonArrayLikes.length * 2]).toEqual([5, 4]); + expect(divideSpy.mock.calls[nonArrayLikes.length * 2 + 1]).toEqual([5, 4]); + expect(divideSpy).toHaveBeenCalledTimes(nonArrayLikes.length * 2 + 2); + + divideSpy.mockRestore(); + }); + + it("should build a function throwing an exception if called without arguments or if `fn` isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.partial(value)).toThrow(); + expect(lamb.partialRight(value)).toThrow(); + }); + + expect(lamb.partial()).toThrow(); + expect(lamb.partialRight()).toThrow(); + }); +}); diff --git a/src/core/__tests__/reduce.spec.js b/src/core/__tests__/reduce.spec.js new file mode 100644 index 0000000..a237def --- /dev/null +++ b/src/core/__tests__/reduce.spec.js @@ -0,0 +1,96 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("reduce / reduceWith", function () { + var arr = [1, 2, 3, 4, 5]; + var s = "12345"; + + afterEach(function () { + expect(arr).toEqual([1, 2, 3, 4, 5]); + }); + + it("should fold or reduce the provided array with the given accumulator function starting from the first element", function () { + var subtract = jest.fn(function (prev, current, idx, list) { + expect(list).toBe(arr); + expect(list[idx]).toBe(current); + + return prev - current; + }); + + var prevValues = [ + 1, -1, -4, -8, 0, -1, -3, -6, -10, 10, 9, 7, 4, 0, + 1, -1, -4, -8, 0, -1, -3, -6, -10, 10, 9, 7, 4, 0 + ]; + + expect(lamb.reduce(arr, subtract)).toBe(-13); + expect(lamb.reduce(arr, subtract, 0)).toBe(-15); + expect(lamb.reduce(arr, subtract, 10)).toBe(-5); + expect(lamb.reduceWith(subtract)(arr)).toBe(-13); + expect(lamb.reduceWith(subtract, 0)(arr)).toBe(-15); + expect(lamb.reduceWith(subtract, 10)(arr)).toBe(-5); + + expect(subtract).toHaveBeenCalledTimes(prevValues.length); + + prevValues.forEach(function (prevValue, idx) { + expect(subtract.mock.calls[idx][0]).toEqual(prevValue); + }); + }); + + it("should work with array-like objects", function () { + var fn = lamb.tapArgs(lamb.subtract, [lamb.identity, Number]); + + expect(lamb.reduce(s, fn)).toBe(-13); + expect(lamb.reduce(s, fn, 0)).toBe(-15); + expect(lamb.reduce(s, fn, 10)).toBe(-5); + expect(lamb.reduceWith(fn)(s)).toBe(-13); + expect(lamb.reduceWith(fn, 0)(s)).toBe(-15); + expect(lamb.reduceWith(fn, 10)(s)).toBe(-5); + }); + + it("should not skip deleted or unassigned elements, unlike the native method", function () { + var sum = jest.fn(function (a, b) { return a + b; }); + var sparseArr = Array(3); + + sparseArr[1] = 3; + + expect(lamb.reduce(sparseArr, sum, 0)).toEqual(NaN); + expect(lamb.reduceWith(sum, 0)(sparseArr)).toEqual(NaN); + expect(sum).toHaveBeenCalledTimes(6); + }); + + it("should build a function throwing an exception if the accumulator isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.reduce(arr, value, 0); }).toThrow(); + expect(function () { lamb.reduceWith(value, 0)(arr); }).toThrow(); + }); + + expect(function () { lamb.reduce(arr); }).toThrow(); + expect(function () { lamb.reduceWith()(arr); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.reduce).toThrow(); + expect(lamb.reduceWith(lamb.sum, 0)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { + expect(function () { lamb.reduce(null, lamb.subtract, 0); }).toThrow(); + expect(function () { lamb.reduce(void 0, lamb.subtract, 0); }).toThrow(); + expect(function () { lamb.reduceWith(lamb.subtract, 0)(null); }).toThrow(); + expect(function () { lamb.reduceWith(lamb.subtract, 0)(void 0); }).toThrow(); + }); + + it("should throw an exception when supplied with an empty array-like without an initial value", function () { + expect(function () { lamb.reduce([], lamb.subtract); }).toThrow(); + expect(function () { lamb.reduce("", lamb.subtract); }).toThrow(); + expect(function () { lamb.reduceWith(lamb.subtract)([]); }).toThrow(); + expect(function () { lamb.reduceWith(lamb.subtract)(""); }).toThrow(); + }); + + it("should treat every other value as an empty array and return the initial value", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.reduce(value, lamb.subtract, 99)).toEqual(99); + expect(lamb.reduceWith(lamb.subtract, 99)(value)).toEqual(99); + }); + }); +}); diff --git a/src/core/__tests__/slice.spec.js b/src/core/__tests__/slice.spec.js new file mode 100644 index 0000000..120a301 --- /dev/null +++ b/src/core/__tests__/slice.spec.js @@ -0,0 +1,263 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays, zeroesAsIntegers } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("slice / sliceAt", function () { + var arr = [1, 2, 3, 4, 5, 6]; + var arrCopy = arr.slice(); + var s = "hello world"; + var users = [{ name: "Jane" }, { name: "John" }, { name: "Mario" }, { name: "Paolo" }]; + var usersCopy = [{ name: "Jane" }, { name: "John" }, { name: "Mario" }, { name: "Paolo" }]; + + afterEach(function () { + expect(arr).toEqual(arrCopy); + expect(users).toEqual(usersCopy); + }); + + it("should return a copy of the desired portion of an array", function () { + expect(lamb.slice(arr, 0, 3)).toEqual([1, 2, 3]); + expect(lamb.sliceAt(0, 3)(arr)).toEqual([1, 2, 3]); + + var usersSliceA = lamb.slice(users, 1, 3); + var usersSliceB = lamb.sliceAt(1, 3)(users); + + expect(usersSliceA).toEqual([{ name: "John" }, { name: "Mario" }]); + expect(usersSliceA[0]).toBe(users[1]); + expect(usersSliceA[1]).toBe(users[2]); + expect(usersSliceB).toEqual([{ name: "John" }, { name: "Mario" }]); + expect(usersSliceB[0]).toBe(users[1]); + expect(usersSliceB[1]).toBe(users[2]); + }); + + it("should work on array-like objects", function () { + expect(lamb.slice(s, 1, 3)).toEqual(["e", "l"]); + expect(lamb.sliceAt(1, 3)(s)).toEqual(["e", "l"]); + }); + + it("should return an array copy of the array-like if the desired slice encompasses the whole array-like", function () { + var r1 = lamb.slice(arr, 0, arr.length); + var r2 = lamb.sliceAt(0, arr.length)(arr); + + expect(r1).toEqual(arrCopy); + expect(r2).toEqual(arrCopy); + expect(r1).not.toBe(arr); + expect(r2).not.toBe(arr); + expect(lamb.slice(s, 0, s.length)).toEqual(s.split("")); + expect(lamb.sliceAt(0, s.length)(s)).toEqual(s.split("")); + }); + + it("should accept negative indexes in the `start` parameter", function () { + expect(lamb.slice(arr, -3, 5)).toEqual([4, 5]); + expect(lamb.slice(s, -5, 9)).toEqual(["w", "o", "r"]); + + expect(lamb.sliceAt(-3, 5)(arr)).toEqual([4, 5]); + expect(lamb.sliceAt(-5, 9)(s)).toEqual(["w", "o", "r"]); + }); + + it("should accept negative indexes in the `end` parameter", function () { + expect(lamb.slice(arr, 2, -2)).toEqual([3, 4]); + expect(lamb.slice(s, 2, -2)).toEqual(["l", "l", "o", " ", "w", "o", "r"]); + + expect(lamb.sliceAt(2, -2)(arr)).toEqual([3, 4]); + expect(lamb.sliceAt(2, -2)(s)).toEqual(["l", "l", "o", " ", "w", "o", "r"]); + }); + + it("should slice from the beginning of the array-like if the `start` parameter is less than or equal to the additive inverse of the array-like length", function () { + expect(lamb.slice(arr, -20, 3)).toEqual([1, 2, 3]); + expect(lamb.slice(arr, -arr.length, 3)).toEqual([1, 2, 3]); + expect(lamb.slice(s, -20, 4)).toEqual(["h", "e", "l", "l"]); + expect(lamb.slice(s, -s.length, 4)).toEqual(["h", "e", "l", "l"]); + + expect(lamb.sliceAt(-20, 3)(arr)).toEqual([1, 2, 3]); + expect(lamb.sliceAt(-arr.length, 3)(arr)).toEqual([1, 2, 3]); + expect(lamb.sliceAt(-20, 4)(s)).toEqual(["h", "e", "l", "l"]); + expect(lamb.sliceAt(-s.length, 4)(s)).toEqual(["h", "e", "l", "l"]); + }); + + it("should slice to the end of the array-like if the `end` parameter is greater than its length", function () { + expect(lamb.slice(arr, 3, 30)).toEqual([4, 5, 6]); + expect(lamb.slice(s, 8, 40)).toEqual(["r", "l", "d"]); + + expect(lamb.sliceAt(3, 30)(arr)).toEqual([4, 5, 6]); + expect(lamb.sliceAt(8, 40)(s)).toEqual(["r", "l", "d"]); + }); + + it("should return an empty array if `start` is greater than or equal to the length of the array-like", function () { + expect(lamb.slice(arr, 6, 6)).toEqual([]); + expect(lamb.slice(arr, 6, -1)).toEqual([]); + expect(lamb.slice(arr, 7, -1)).toEqual([]); + expect(lamb.slice(s, 11, 11)).toEqual([]); + expect(lamb.slice(s, 11, -1)).toEqual([]); + expect(lamb.slice(s, 12, -1)).toEqual([]); + + expect(lamb.sliceAt(6, 6)(arr)).toEqual([]); + expect(lamb.sliceAt(6, -1)(arr)).toEqual([]); + expect(lamb.sliceAt(7, -1)(arr)).toEqual([]); + expect(lamb.sliceAt(11, 11)(s)).toEqual([]); + expect(lamb.sliceAt(11, -1)(s)).toEqual([]); + expect(lamb.sliceAt(12, -1)(s)).toEqual([]); + }); + + it("should return an empty array if `start` is greater than or equal to `end` (as natural indexes)", function () { + expect(lamb.slice(arr, 4, 4)).toEqual([]); + expect(lamb.slice(arr, 5, 4)).toEqual([]); + expect(lamb.slice(arr, -5, -40)).toEqual([]); + expect(lamb.slice(s, 4, 4)).toEqual([]); + expect(lamb.slice(s, 5, 4)).toEqual([]); + expect(lamb.slice(s, -5, -40)).toEqual([]); + + expect(lamb.sliceAt(4, 4)(arr)).toEqual([]); + expect(lamb.sliceAt(5, 4)(arr)).toEqual([]); + expect(lamb.sliceAt(-5, -40)(arr)).toEqual([]); + expect(lamb.sliceAt(4, 4)(s)).toEqual([]); + expect(lamb.sliceAt(5, 4)(s)).toEqual([]); + expect(lamb.sliceAt(-5, -40)(s)).toEqual([]); + }); + + it("should convert to integer any value received as `start` or `end` following ECMA specifications", function () { + // see https://www.ecma-international.org/ecma-262/7.0/#sec-tointeger + + expect(lamb.slice(arr, new Date(1), new Date(4))).toEqual([2, 3, 4]); + expect(lamb.slice(arr, 1.8, 4.9)).toEqual([2, 3, 4]); + expect(lamb.slice(arr, null, 3)).toEqual([1, 2, 3]); + expect(lamb.slice(arr, 1, null)).toEqual([]); + expect(lamb.slice(arr, "1.8", "4.9")).toEqual([2, 3, 4]); + expect(lamb.slice(arr, false, true)).toEqual([1]); + expect(lamb.slice(arr, [1.8], [4.9])).toEqual([2, 3, 4]); + expect(lamb.slice(arr, ["1.8"], ["4.9"])).toEqual([2, 3, 4]); + + expect(lamb.slice(s, new Date(1), new Date(3))).toEqual(["e", "l"]); + expect(lamb.slice(s, 1.8, 3.9)).toEqual(["e", "l"]); + expect(lamb.slice(s, null, 3)).toEqual(["h", "e", "l"]); + expect(lamb.slice(s, 1, null)).toEqual([]); + expect(lamb.slice(s, "1.8", "4.9")).toEqual(["e", "l", "l"]); + expect(lamb.slice(s, false, true)).toEqual(["h"]); + expect(lamb.slice(s, [1.8], [4.9])).toEqual(["e", "l", "l"]); + expect(lamb.slice(s, ["1.8"], ["4.9"])).toEqual(["e", "l", "l"]); + + expect(lamb.sliceAt(new Date(1), new Date(4))(arr)).toEqual([2, 3, 4]); + expect(lamb.sliceAt(1.8, 4.9)(arr)).toEqual([2, 3, 4]); + expect(lamb.sliceAt(null, 3)(arr)).toEqual([1, 2, 3]); + expect(lamb.sliceAt(1, null)(arr)).toEqual([]); + expect(lamb.sliceAt("1.8", "4.9")(arr)).toEqual([2, 3, 4]); + expect(lamb.sliceAt(false, true)(arr)).toEqual([1]); + expect(lamb.sliceAt([1.8], [4.9])(arr)).toEqual([2, 3, 4]); + expect(lamb.sliceAt(["1.8"], ["4.9"])(arr)).toEqual([2, 3, 4]); + + expect(lamb.sliceAt(new Date(1), new Date(3))(s)).toEqual(["e", "l"]); + expect(lamb.sliceAt(1.8, 3.9)(s)).toEqual(["e", "l"]); + expect(lamb.sliceAt(null, 3)(s)).toEqual(["h", "e", "l"]); + expect(lamb.sliceAt(1, null)(s)).toEqual([]); + expect(lamb.sliceAt("1.8", "4.9")(s)).toEqual(["e", "l", "l"]); + expect(lamb.sliceAt(false, true)(s)).toEqual(["h"]); + expect(lamb.sliceAt([1.8], [4.9])(s)).toEqual(["e", "l", "l"]); + expect(lamb.sliceAt(["1.8"], ["4.9"])(s)).toEqual(["e", "l", "l"]); + + expect(lamb.slice(arr, -4.8, -2.9)).toEqual([3, 4]); + expect(lamb.slice(arr, "-4.8", "-2.9")).toEqual([3, 4]); + expect(lamb.slice(arr, [-4.8], [-2.9])).toEqual([3, 4]); + + expect(lamb.slice(s, -4.8, -2.9)).toEqual(["o", "r"]); + expect(lamb.slice(s, "-4.8", "-2.9")).toEqual(["o", "r"]); + expect(lamb.slice(s, [-4.8], [-2.9])).toEqual(["o", "r"]); + + expect(lamb.sliceAt(-4.8, -2.9)(arr)).toEqual([3, 4]); + expect(lamb.sliceAt("-4.8", "-2.9")(arr)).toEqual([3, 4]); + expect(lamb.sliceAt([-4.8], [-2.9])(arr)).toEqual([3, 4]); + + expect(lamb.sliceAt(-4.8, -2.9)(s)).toEqual(["o", "r"]); + expect(lamb.sliceAt("-4.8", "-2.9")(s)).toEqual(["o", "r"]); + expect(lamb.sliceAt([-4.8], [-2.9])(s)).toEqual(["o", "r"]); + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.slice(arr, value, arr.length)).toEqual(arrCopy); + expect(lamb.slice(s, value, s.length)).toEqual(s.split("")); + expect(lamb.slice(arr, 0, value)).toEqual([]); + expect(lamb.slice(s, 0, value)).toEqual([]); + + expect(lamb.sliceAt(value, arr.length)(arr)).toEqual(arrCopy); + expect(lamb.sliceAt(value, s.length)(s)).toEqual(s.split("")); + expect(lamb.sliceAt(0, value)(arr)).toEqual([]); + expect(lamb.sliceAt(0, value)(s)).toEqual([]); + }); + }); + + it("should interpret as zeroes missing values in the `start` or `end` parameter", function () { + expect(lamb.slice(arr, 0)).toEqual([]); + expect(lamb.slice(s, 0)).toEqual([]); + expect(lamb.sliceAt(0)(arr)).toEqual([]); + expect(lamb.sliceAt(0)(s)).toEqual([]); + + expect(lamb.slice(arr)).toEqual([]); + expect(lamb.sliceAt()(arr)).toEqual([]); + expect(lamb.slice(s)).toEqual([]); + expect(lamb.sliceAt()(s)).toEqual([]); + }); + + it("should return an array with `undefined` values in place of unassigned or deleted indexes if a sparse array is received", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + var aSparse = [, 1, 2, , 4, , ,]; // length === 7, same as aDense + var aDense = [void 0, 1, 2, void 0, 4, void 0, void 0]; + var r1 = lamb.slice(aSparse, 0, aSparse.length); + var r2 = lamb.sliceAt(0, aSparse.length)(aSparse); + + expect(r1).toStrictArrayEqual(aDense); + expect(r1).not.toStrictArrayEqual(aSparse); + expect(r2).toStrictArrayEqual(aDense); + expect(r2).not.toStrictArrayEqual(aSparse); + }); + + it("should try, as native `slice` does, to retrieve elements from objects with a `length` property, but it should always build dense arrays", function () { + var objs = [ + { length: 3, 0: 1, 1: 2, 2: 3 }, + { length: 3, 0: 1, 2: 3 }, + { length: "2", 0: 1, 1: 2, 2: 3 }, + { length: "-2", 0: 1, 1: 2, 2: 3 } + ]; + + var results = [ [2, 3], [void 0, 3], [2], [] ]; + + objs.forEach(function (obj, idx) { + expect(lamb.slice(obj, 1, 3)).toStrictArrayEqual(results[idx]); + expect(lamb.sliceAt(1, 3)(obj)).toStrictArrayEqual(results[idx]); + }); + + var oSparse = { length: 2 }; + var rSparse = Array(2); + var rDense = [void 0, void 0]; + + expect(lamb.slice(oSparse, 0, oSparse.length)).toStrictArrayEqual(rDense); + expect(lamb.slice(oSparse, 0, oSparse.length)).not.toStrictArrayEqual(rSparse); + expect(lamb.sliceAt(0, oSparse.length)(oSparse)).toStrictArrayEqual(rDense); + expect(lamb.sliceAt(0, oSparse.length)(oSparse)).not.toStrictArrayEqual(rSparse); + }); + + it("should work with array-like lengths up to 2^32 - 1", function () { + var maxLen = Math.pow(2, 32) - 1; + var maxIndex = maxLen - 1; + var obj = { length: maxLen + 100 }; + + obj[maxIndex] = 99; + obj[maxIndex + 1] = 88; + + expect(lamb.slice(obj, -1, obj.length)).toEqual([99]); + expect(lamb.sliceAt(-1, obj.length)(obj)).toEqual([99]); + }); + + it("should throw an exception if it receives `null` or `undefined` instead of an array-like or if it's called without parameters", function () { + expect(function () { lamb.slice(null, 1, 2); }).toThrow(); + expect(function () { lamb.slice(void 0, 1, 2); }).toThrow(); + expect(function () { lamb.sliceAt(1, 2)(null); }).toThrow(); + expect(function () { lamb.sliceAt(1, 2)(void 0); }).toThrow(); + + expect(lamb.slice).toThrow(); + expect(lamb.sliceAt()).toThrow(); + }); + + it("should treat every other value as an empty array", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.slice(value, 0, 2)).toEqual([]); + expect(lamb.sliceAt(0, 2)(value)).toEqual([]); + }); + }); +}); diff --git a/src/core/__tests__/type.spec.js b/src/core/__tests__/type.spec.js new file mode 100644 index 0000000..8164932 --- /dev/null +++ b/src/core/__tests__/type.spec.js @@ -0,0 +1,26 @@ +import * as lamb from "../.."; + +describe("type", function () { + it("should extract the \"type tag\" from the given value", function () { + var getArgsType = function () { return lamb.type(arguments); }; + + expect(getArgsType()).toBe("Arguments"); + expect(lamb.type(new getArgsType())).toBe("Object"); // eslint-disable-line new-cap + expect(lamb.type(void 0)).toBe("Undefined"); + expect(lamb.type(null)).toBe("Null"); + expect(lamb.type(NaN)).toBe("Number"); + expect(lamb.type(0)).toBe("Number"); + expect(lamb.type(new Number())).toBe("Number"); + expect(lamb.type("")).toBe("String"); + expect(lamb.type(new String("foo"))).toBe("String"); + expect(lamb.type(/a/)).toBe("RegExp"); + expect(lamb.type(new RegExp())).toBe("RegExp"); + expect(lamb.type(new Date())).toBe("Date"); + expect(lamb.type(function () {})).toBe("Function"); + expect(lamb.type(new Function())).toBe("Function"); + expect(lamb.type([1, 2, 3])).toBe("Array"); + expect(lamb.type(new Array())).toBe("Array"); + expect(lamb.type(Object.create(null))).toBe("Object"); + expect(lamb.type({})).toBe("Object"); + }); +}); diff --git a/src/core/always.js b/src/core/always.js new file mode 100644 index 0000000..ce4df22 --- /dev/null +++ b/src/core/always.js @@ -0,0 +1,31 @@ +/** + * Builds a function that returns a constant value. + * It's actually the simplest form of the K combinator or Kestrel. + * @example + * var truth = _.always(true); + * + * truth() // => true + * truth(false) // => true + * truth(1, 2) // => true + * + * // the value being returned is actually the + * // very same value passed to the function + * var foo = {bar: "baz"}; + * var alwaysFoo = _.always(foo); + * + * alwaysFoo() === foo // => true + * + * @memberof module:lamb + * @category Function + * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus} + * @since 0.1.0 + * @param {*} value + * @returns {Function} + */ +function always (value) { + return function () { + return value; + }; +} + +export default always; diff --git a/src/core/areSVZ.js b/src/core/areSVZ.js new file mode 100644 index 0000000..0b873ca --- /dev/null +++ b/src/core/areSVZ.js @@ -0,0 +1,31 @@ +/** + * Verifies that the two supplied values are the same value using the "SameValueZero" comparison.
+ * With this comparison NaN is equal to itself, but 0 and -0 are + * considered the same value.
+ * See also {@link module:lamb.isSVZ|isSVZ} for a curried version building a predicate and + * {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} to perform a "SameValue" comparison. + * @example + * var testObject = {}; + * + * _.areSVZ({}, testObject) // => false + * _.areSVZ(testObject, testObject) // => true + * _.areSVZ("foo", "foo") // => true + * _.areSVZ(0, -0) // => true + * _.areSVZ(0 / 0, NaN) // => true + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.isSVZ|isSVZ} + * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.50.0 + * @param {*} a + * @param {*} b + * @returns {Boolean} + */ +function areSVZ (a, b) { + return a !== a ? b !== b : a === b; // eslint-disable-line no-self-compare +} + +export default areSVZ; diff --git a/src/core/binary.js b/src/core/binary.js new file mode 100644 index 0000000..27562e5 --- /dev/null +++ b/src/core/binary.js @@ -0,0 +1,23 @@ +/** + * Builds a function that passes only two arguments to the given function.
+ * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, + * exposed for convenience. + * @example + * _.list(1, 2, 3, 4, 5) // => [1, 2, 3, 4, 5] + * _.binary(_.list)(1, 2, 3, 4, 5) // => [1, 2] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.aritize|aritize} + * @see {@link module:lamb.unary|unary} + * @since 0.10.0 + * @param {Function} fn + * @returns {Function} + */ +function binary (fn) { + return function (a, b) { + return fn.call(this, a, b); + }; +} + +export default binary; diff --git a/src/core/clamp.js b/src/core/clamp.js new file mode 100644 index 0000000..b0c1c5c --- /dev/null +++ b/src/core/clamp.js @@ -0,0 +1,36 @@ +/** + * "Clamps" a number within the given limits, both included.
+ * The function will convert to number all its parameters before starting any + * evaluation, and will return NaN if min is greater + * than max. + * @example + * _.clamp(-5, 0, 10) // => 0 + * _.clamp(5, 0, 10) // => 5 + * _.clamp(15, 0, 10) // => 10 + * _.clamp(0, 0, 10) // => 0 + * _.clamp(10, 0, 10) // => 10 + * _.is(_.clamp(-0, 0, 10), -0) // => true + * _.clamp(10, 20, 15) // => NaN + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.clampWithin|clampWithin} + * @since 0.13.0 + * @param {Number} n + * @param {Number} min + * @param {Number} max + * @returns {Number} + */ +function clamp (n, min, max) { + n = +n; + min = +min; + max = +max; + + if (min > max) { + return NaN; + } else { + return n < min ? min : n > max ? max : n; + } +} + +export default clamp; diff --git a/src/core/clampWithin.js b/src/core/clampWithin.js new file mode 100644 index 0000000..f0a23e5 --- /dev/null +++ b/src/core/clampWithin.js @@ -0,0 +1,27 @@ +import _makePartial3 from "../privates/_makePartial3"; +import clamp from "./clamp"; + +/** + * A curried version of {@link module:lamb.clamp|clamp}, expecting a min + * and a max value, that builds a function waiting for the number to clamp. + * @example + * _.clampWithin(0, 10)(-5) // => 0 + * _.clampWithin(0, 10)(5) // => 5 + * _.clampWithin(0, 10)(15) // => 10 + * _.clampWithin(0, 10)(0) // => 0 + * _.clampWithin(0, 10)(10) // => 10 + * _.is(_.clampWithin(0, 10)(-0), -0) // => true + * _.clampWithin(20, 15)(10) // => NaN + * + * @memberof module:lamb + * @category Math + * @function + * @see {@link module:lamb.clamp|clamp} + * @since 0.47.0 + * @param {Number} min + * @param {Number} max + * @returns {Function} + */ +var clampWithin = _makePartial3(clamp); + +export default clampWithin; diff --git a/src/core/compose.js b/src/core/compose.js new file mode 100644 index 0000000..33eb96c --- /dev/null +++ b/src/core/compose.js @@ -0,0 +1,35 @@ +import identity from "./identity"; + +/** + * Returns a function that is the composition of the functions given as parameters. + * The first function consumes the result of the function that follows. + * @example + * var sayHi = function (name) { return "Hi, " + name; }; + * var capitalize = function (s) { + * return s[0].toUpperCase() + s.substr(1).toLowerCase(); + * }; + * var fixNameAndSayHi = _.compose(sayHi, capitalize); + * + * sayHi("bOb") // => "Hi, bOb" + * fixNameAndSayHi("bOb") // "Hi, Bob" + * + * var users = [{name: "fred"}, {name: "bOb"}]; + * var sayHiToUser = _.compose(fixNameAndSayHi, _.getKey("name")); + * + * _.map(users, sayHiToUser) // ["Hi, Fred", "Hi, Bob"] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.pipe|pipe} + * @since 0.1.0 + * @param {Function} a + * @param {Function} b + * @returns {Function} + */ +function compose (a, b) { + return arguments.length ? function () { + return a.call(this, b.apply(this, arguments)); + } : identity; +} + +export default compose; diff --git a/src/core/forEach.js b/src/core/forEach.js new file mode 100644 index 0000000..7304470 --- /dev/null +++ b/src/core/forEach.js @@ -0,0 +1,28 @@ +import _toArrayLength from "../privates/_toArrayLength"; + +/** + * Executes the provided iteratee for each element of the given array-like object.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example Adding a CSS class to all elements of a NodeList in a browser environment: + * var addClass = _.curry(function (className, element) { + * element.classList.add(className); + * }); + * var paragraphs = document.querySelectorAll("#some-container p"); + * + * _.forEach(paragraphs, addClass("main")); + * // each "p" element in the container will have the "main" class now + * + * @memberof module:lamb + * @category Array + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Undefined} + */ +function forEach (arrayLike, iteratee) { + for (var i = 0, len = _toArrayLength(arrayLike.length); i < len; i++) { + iteratee(arrayLike[i], i, arrayLike); + } +} + +export default forEach; diff --git a/src/core/generic.js b/src/core/generic.js new file mode 100644 index 0000000..02efa81 --- /dev/null +++ b/src/core/generic.js @@ -0,0 +1,22 @@ +/** + * Creates generic functions out of methods. + * @author A very little change on a great idea by [Irakli Gozalishvili]{@link https://github.com/Gozala/}. + * Thanks for this *beautiful* one-liner (never liked your "unbind" naming choice, though). + * @memberof module:lamb + * @category Function + * @function + * @example + * var join = _.generic(Array.prototype.join); + * + * join([1, 2, 3, 4, 5], "-") // => "1-2-3-4-5" + * + * // the function will work with any array-like object + * join("hello", "-") // => "h-e-l-l-o" + * + * @since 0.1.0 + * @param {Function} method + * @returns {Function} + */ +var generic = Function.bind.bind(Function.call); + +export default generic; diff --git a/src/core/identity.js b/src/core/identity.js new file mode 100644 index 0000000..9e57edf --- /dev/null +++ b/src/core/identity.js @@ -0,0 +1,19 @@ +/** + * The I combinator. Any value passed to the function is simply returned as it is. + * @example + * var foo = {bar: "baz"}; + * + * _.identity(foo) === foo // true + * + * @memberof module:lamb + * @category Function + * @see [SKI combinator calculus]{@link https://en.wikipedia.org/wiki/SKI_combinator_calculus} + * @since 0.1.0 + * @param {*} value + * @returns {*} The value passed as parameter. + */ +function identity (value) { + return value; +} + +export default identity; diff --git a/src/core/isNil.js b/src/core/isNil.js new file mode 100644 index 0000000..4581264 --- /dev/null +++ b/src/core/isNil.js @@ -0,0 +1,25 @@ +import isNull from "./isNull"; +import isUndefined from "./isUndefined"; + +/** + * Verifies if a value is null or undefined. + * @example + * _.isNil(NaN) // => false + * _.isNil({}) // => false + * _.isNil(null) // => true + * _.isNil(void 0) // => true + * _.isNil() // => true + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isNull|isNull} + * @see {@link module:lamb.isUndefined|isUndefined} + * @since 0.1.0 + * @param {*} value + * @returns {Boolean} + */ +function isNil (value) { + return isNull(value) || isUndefined(value); +} + +export default isNil; diff --git a/src/core/isNull.js b/src/core/isNull.js new file mode 100644 index 0000000..ee69c9e --- /dev/null +++ b/src/core/isNull.js @@ -0,0 +1,19 @@ +/** + * Verifies if a value is null. + * @example + * _.isNull(null) // => true + * _.isNull(void 0) // => false + * _.isNull(false) // => false + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isNil|isNil} if you want to check for undefined too. + * @since 0.1.0 + * @param {*} value + * @returns {Boolean} + */ +function isNull (value) { + return value === null; +} + +export default isNull; diff --git a/src/core/isSVZ.js b/src/core/isSVZ.js new file mode 100644 index 0000000..68806eb --- /dev/null +++ b/src/core/isSVZ.js @@ -0,0 +1,42 @@ +import _curry2 from "../privates/_curry2"; +import areSVZ from "./areSVZ"; + +/** + * A curried version of {@link module:lamb.areSVZ|areSVZ}.
+ * Accepts a value and builds a predicate that checks whether the value + * and the one received by the predicate are the same using the "SameValueZero" + * comparison.
+ * See also {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} + * to perform a "SameValue" comparison. + * @example + * var john = {name: "John", surname: "Doe"}; + * var isJohn = _.isSVZ(john); + * var isZero = _.isSVZ(0); + * var isReallyNaN = _.isSVZ(NaN); + * + * isJohn(john) // => true + * isJohn({name: "John", surname: "Doe"}) // => false + * + * isZero(0) // => true + * isZero(-0) // => true + * + * isNaN(NaN) // => true + * isNaN("foo") // => true + * + * isReallyNaN(NaN) // => true + * isReallyNaN("foo") // => false + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.areSVZ|areSVZ} + * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.1.0 + * @param {*} value + * @returns {Function} + */ +var isSVZ = _curry2(areSVZ); + +export default isSVZ; diff --git a/src/core/isUndefined.js b/src/core/isUndefined.js new file mode 100644 index 0000000..545f83d --- /dev/null +++ b/src/core/isUndefined.js @@ -0,0 +1,19 @@ +/** + * Verifies if a value is undefined. + * @example + * _.isUndefined(null) // => false + * _.isUndefined(void 0) // => true + * _.isUndefined(false) // => false + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isNil|isNil} if you want to check for null too. + * @since 0.1.0 + * @param {*} value + * @returns {Boolean} + */ +function isUndefined (value) { + return value === void 0; +} + +export default isUndefined; diff --git a/src/core/map.js b/src/core/map.js new file mode 100644 index 0000000..d8021ec --- /dev/null +++ b/src/core/map.js @@ -0,0 +1,32 @@ +import _toArrayLength from "../privates/_toArrayLength"; + +/** + * Builds a new array by applying the iteratee function to each element of the + * received array-like object.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example + * _.map(["Joe", "Mario", "Jane"], _.invoker("toUpperCase")) // => ["JOE", "MARIO", "JANE"] + * + * _.map([4, 9, 16], Math.sqrt); // => [2, 3, 4] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.mapWith|mapWith} + * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} iteratee + * @returns {Array} + */ +function map (arrayLike, iteratee) { + var len = _toArrayLength(arrayLike.length); + var result = Array(len); + + for (var i = 0; i < len; i++) { + result[i] = iteratee(arrayLike[i], i, arrayLike); + } + + return result; +} + +export default map; diff --git a/src/core/mapWith.js b/src/core/mapWith.js new file mode 100644 index 0000000..ef4edd4 --- /dev/null +++ b/src/core/mapWith.js @@ -0,0 +1,24 @@ +import _curry2 from "../privates/_curry2"; +import map from "./map"; + +/** + * A curried version of {@link module:lamb.map|map} that uses the provided iteratee to + * build a function expecting the array-like object to act upon. + * @example + * var square = function (n) { return n * n; }; + * var getSquares = _.mapWith(square); + * + * getSquares([1, 2, 3, 4, 5]) // => [1, 4, 9, 16, 25] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.map|map} + * @see {@link module:lamb.flatMap|flatMap}, {@link module:lamb.flatMapWith|flatMapWith} + * @since 0.1.0 + * @param {ListIteratorCallback} iteratee + * @returns {function} + */ +var mapWith = _curry2(map, true); + +export default mapWith; diff --git a/src/core/partial.js b/src/core/partial.js new file mode 100644 index 0000000..edd6825 --- /dev/null +++ b/src/core/partial.js @@ -0,0 +1,55 @@ +import __ from "./__"; + +/** + * Builds a partially applied function.
+ * The {@link module:lamb.__|__} object can be used as a placeholder for arguments.
+ * @example + * var __ = _.__; + * var users = [ + * {id: 1, name: "John", active: true, confirmedMail: true}, + * {id: 2, name: "Jane", active: true, confirmedMail: false}, + * {id: 3, name: "Mario", active: false, confirmedMail: false} + * ]; + * var isKeyTrue = _.partial(_.hasKeyValue, [__, true]); + * var isActive = isKeyTrue("active"); + * var hasConfirmedMail = isKeyTrue("confirmedMail"); + * + * _.map(users, isActive) // => [true, true, false] + * _.map(users, hasConfirmedMail) // => [true, false, false] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.__|__} The placeholder object. + * @since 0.1.0 + * @param {Function} fn + * @param {Array} args + * @returns {Function} + */ +function partial (fn, args) { + return function () { + if (!Array.isArray(args)) { + return fn.apply(this, arguments); + } + + var lastIdx = 0; + var newArgs = []; + var argsLen = args.length; + + for (var i = 0, boundArg; i < argsLen; i++) { + boundArg = args[i]; + newArgs[i] = boundArg === __ ? arguments[lastIdx++] : boundArg; + } + + for (var len = arguments.length; lastIdx < len; lastIdx++) { + newArgs[i++] = arguments[lastIdx]; + } + + return fn.apply(this, newArgs); + }; +} + +export default partial; diff --git a/src/core/partialRight.js b/src/core/partialRight.js new file mode 100644 index 0000000..7cb79c8 --- /dev/null +++ b/src/core/partialRight.js @@ -0,0 +1,65 @@ +import __ from "./__"; + +/** + * Like {@link module:lamb.partial|partial} will build a partially applied function and + * it will accept placeholders.
+ * The difference is that the bound arguments will be appended to the ones received by + * the resulting function. + * @example + * Explaining the difference with partial: + * var f1 = _.partial(_.list, ["a", "b", "c"]); + * var f2 = _.partialRight(_.list, ["a", "b", "c"]); + * + * f1("d", "e") // => ["a", "b", "c", "d", "e"] + * f2("d", "e") // => ["d", "e", "a", "b", "c"] + * + * @example + * Explaining placeholder substitutions: + * var __ = _.__; + * var f1 = _.partial(_.list, ["a", __, __, "d"]); + * var f2 = _.partialRight(_.list, ["a", __, __, "d"]); + * + * f1("b", "c", "e") // => ["a", "b", "c", "d", "e"] + * f2("b", "c", "e") // => ["b", "a", "c", "e", "d"] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.partial|partial} + * @see {@link module:lamb.asPartial|asPartial} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.__|__} The placeholder object. + * @param {Function} fn + * @param {Array} args + * @since 0.52.0 + * @returns {Function} + */ +function partialRight (fn, args) { + return function () { + if (!Array.isArray(args)) { + return fn.apply(this, arguments); + } + + var lastIdx = arguments.length - 1; + var argsLen = args.length; + var boundArgs = Array(argsLen); + var newArgs = []; + + for (var i = argsLen - 1, boundArg; i > -1; i--) { + boundArg = args[i]; + boundArgs[i] = boundArg === __ ? arguments[lastIdx--] : boundArg; + } + + for (i = 0; i <= lastIdx; i++) { + newArgs[i] = arguments[i]; + } + + for (var j = 0; j < argsLen; j++) { + newArgs[i++] = boundArgs[j]; + } + + return fn.apply(this, newArgs); + }; +} + +export default partialRight; diff --git a/src/core/reduce.js b/src/core/reduce.js new file mode 100644 index 0000000..49db2df --- /dev/null +++ b/src/core/reduce.js @@ -0,0 +1,23 @@ +import _makeReducer from "../privates/_makeReducer"; + +/** + * Reduces (or folds) the values of an array-like object, starting from the first, to a new + * value using the provided accumulator function.
+ * Note that unlike the native array method this function doesn't skip unassigned or deleted indexes. + * @example + * _.reduce([1, 2, 3, 4], _.sum) // => 10 + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.reduceRight|reduceRight} + * @see {@link module:lamb.reduceWith|reduceWith}, {@link module:lamb.reduceRightWith|reduceRightWith} + * @since 0.1.0 + * @param {ArrayLike} arrayLike + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] + * @returns {*} + */ +var reduce = _makeReducer(1); + +export default reduce; diff --git a/src/core/reduceWith.js b/src/core/reduceWith.js new file mode 100644 index 0000000..abca89e --- /dev/null +++ b/src/core/reduceWith.js @@ -0,0 +1,27 @@ +import _makePartial3 from "../privates/_makePartial3"; +import reduce from "./reduce"; + +/** + * A partial application of {@link module:lamb.reduce|reduce} that uses the + * provided accumulator and the optional initialValue to + * build a function expecting the array-like object to act upon. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.reduceWith(_.sum)(arr) // => 15 + * _.reduceWith(_.subtract)(arr) // => -13 + * _.reduceWith(_.subtract, 0)(arr) // => -15 + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.reduceRightWith|reduceRightWith} + * @see {@link module:lamb.reduce|reduce}, {@link module:lamb.reduce|reduceRight} + * @since 0.27.0 + * @param {AccumulatorCallback} accumulator + * @param {*} [initialValue] + * @returns {Function} + */ +var reduceWith = _makePartial3(reduce, true); + +export default reduceWith; diff --git a/src/core/slice.js b/src/core/slice.js new file mode 100644 index 0000000..b2528be --- /dev/null +++ b/src/core/slice.js @@ -0,0 +1,54 @@ +import _toArrayLength from "../privates/_toArrayLength"; +import _toInteger from "../privates/_toInteger"; + +/** + * Builds an array by extracting a portion of an array-like object.
+ * Note that unlike the native array method this function ensures that dense + * arrays are returned.
+ * Also, unlike the native method, the start and end + * parameters aren't optional and will be simply converted to integer.
+ * See {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a + * slice to the end of the array-like. + * @example + * var arr = [1, 2, 3, 4, 5]; + * + * _.slice(arr, 0, 2) // => [1, 2] + * _.slice(arr, 2, -1) // => [3, 4] + * _.slice(arr, -3, 5) // => [3, 4, 5] + * + * @memberof module:lamb + * @category Array + * @see {@link module:lamb.sliceAt|sliceAt} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @since 0.1.0 + * @param {ArrayLike} arrayLike - Any array like object. + * @param {Number} start - Index at which to begin extraction. + * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. + * @returns {Array} + */ +function slice (arrayLike, start, end) { + var len = _toArrayLength(arrayLike.length); + var begin = _toInteger(start); + var upTo = _toInteger(end); + + if (begin < 0) { + begin = begin < -len ? 0 : begin + len; + } + + if (upTo < 0) { + upTo = upTo < -len ? 0 : upTo + len; + } else if (upTo > len) { + upTo = len; + } + + var resultLen = upTo - begin; + var result = resultLen > 0 ? Array(resultLen) : []; + + for (var i = 0; i < resultLen; i++) { + result[i] = arrayLike[begin + i]; + } + + return result; +} + +export default slice; diff --git a/src/core/sliceAt.js b/src/core/sliceAt.js new file mode 100644 index 0000000..95d9c8a --- /dev/null +++ b/src/core/sliceAt.js @@ -0,0 +1,29 @@ +import _makePartial3 from "../privates/_makePartial3"; +import slice from "./slice"; + +/** + * Given the start and end bounds, builds a partial application + * of {@link module:lamb.slice|slice} expecting the array-like object to slice.
+ * See also {@link module:lamb.dropFrom|dropFrom} and {@link module:lamb.drop|drop} if you want a + * slice to the end of the array-like. + * @example + * var arr = [1, 2, 3, 4, 5]; + * var s = "hello"; + * var dropFirstAndLast = _.sliceAt(1, -1); + * + * dropFirstAndLast(arr) // => [2, 3, 4] + * dropFirstAndLast(s) // => ["e", "l", "l"] + * + * @memberof module:lamb + * @category Array + * @function + * @see {@link module:lamb.slice|slice} + * @see {@link module:lamb.dropFrom|dropFrom}, {@link module:lamb.drop|drop} + * @since 0.48.0 + * @param {Number} start - Index at which to begin extraction. + * @param {Number} end - Index at which to end extraction. Extracts up to but not including end. + * @returns {Function} + */ +var sliceAt = _makePartial3(slice); + +export default sliceAt; diff --git a/src/core/type.js b/src/core/type.js new file mode 100644 index 0000000..3525643 --- /dev/null +++ b/src/core/type.js @@ -0,0 +1,28 @@ +var objectProtoToString = Object.prototype.toString; + +/** + * Retrieves the "type tag" from the given value. + * @example + * var x = 5; + * var y = new Number(5); + * + * typeof x // => "number" + * typeof y // => "object" + * _.type(x) // => "Number" + * _.type(y) // => "Number" + * + * _.type(Object.prototype.toString) // => "Function" + * _.type(/a/) // => "RegExp" + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isType|isType} + * @since 0.9.0 + * @param {*} value + * @returns {String} + */ +function type (value) { + return objectProtoToString.call(value).slice(8, -1); +} + +export default type; diff --git a/src/function.js b/src/function.js deleted file mode 100644 index aabb7f8..0000000 --- a/src/function.js +++ /dev/null @@ -1,616 +0,0 @@ -/** - * Applies the given function to a list of arguments. - * @example - * _.application(_.sum, [3, 4]) // => 7 - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.apply|apply}, {@link module:lamb.applyTo|applyTo} - * @since 0.47.0 - * @param {Function} fn - * @param {ArrayLike} args - * @returns {*} - */ -function application (fn, args) { - return fn.apply(this, Object(args)); -} - -/** - * A left-curried version of {@link module:lamb.application|application}. Expects the function - * to apply and builds a function waiting for the arguments array. - * @example - * var arrayMax = _.apply(Math.max); - * - * arrayMax([4, 5, 2, 6, 1]) // => 6 - * - * @memberof module:lamb - * @category Function - * @function - * @see {@link module:lamb.application|application}, {@link module:lamb.applyTo|applyTo} - * @since 0.1.0 - * @param {Function} fn - * @returns {Function} - */ -var apply = _curry2(application); - -/** - * A right-curried version of {@link module:lamb.application|application}. Expects an array-like - * object to use as arguments and builds a function waiting for the target of the application. - * @example - * var data = [3, 4]; - * var applyToData = _.applyTo(data); - * - * applyToData(_.sum) // => 7 - * applyToData(_.multiply) // => 12 - * - * @memberof module:lamb - * @category Function - * @function - * @see {@link module:lamb.application|application}, {@link module:lamb.apply|apply} - * @since 0.47.0 - * @param {ArrayLike} args - * @returns {Function} - */ -var applyTo = _curry2(application, true); - -/** - * Builds a new function that passes only the specified amount of arguments to the original one.
- * As {@link module:lamb.slice|slice} is used to extract the arguments, you can also - * pass a negative arity. - * @example - * Math.max(10, 11, 45, 99) // => 99 - * _.aritize(Math.max, 2)(10, 11, 45, 99) // => 11 - * - * @example Using a negative arity: - * _.aritize(Math.max, -1)(10, 11, 45, 99) // => 45 - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.binary|binary}, {@link module:lamb.unary|unary} for common use cases shortcuts - * @since 0.1.0 - * @param {Function} fn - * @param {Number} arity - * @returns {Function} - */ -function aritize (fn, arity) { - return function () { - var n = _toInteger(arity); - var args = list.apply(null, arguments).slice(0, n); - - for (var i = args.length; i < n; i++) { - args[i] = void 0; - } - - return fn.apply(this, args); - }; -} - -/** - * Decorates the received function so that it can be called with - * placeholders to build a partial application of it.
- * The difference with {@link module:lamb.partial|partial} is that, as long as - * you call the generated function with placeholders, another partial application - * of the original function will be built.
- * The final application will happen when one of the generated functions is - * invoked without placeholders, using the parameters collected so far.
- * This function comes in handy when you need to build different specialized - * functions starting from a basic one, but it's also useful when dealing with - * optional parameters as you can decide to apply the function even if its arity - * hasn't been entirely consumed. - * @example Explaining the function's behaviour: - * var f = _.asPartial(function (a, b, c) { - * return a + b + c; - * }); - * - * f(4, 3, 2) // => 9 - * f(4, _, 2)(3) // => 9 - * f(_, 3, _)(4, _)(2) // => 9 - * - * @example Exploiting optional parameters: - * var f = _.asPartial(function (a, b, c) { - * return a + b + (c || 0); - * }); - * - * var addFive = f(5, _); - * addFive(2) // => 7 - * - * var addNine = addFive(4, _); - * addNine(11) // => 20 - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.@@lamb/placeholder|@@lamb/placeholder} - * @since 0.36.0 - * @param {Function} fn - * @returns {Function} - */ -function asPartial (fn) { - return _asPartial(fn, []); -} - -/** - * Builds a function that passes only two arguments to the given function.
- * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, - * exposed for convenience. - * @example - * _.list(1, 2, 3, 4, 5) // => [1, 2, 3, 4, 5] - * _.binary(_.list)(1, 2, 3, 4, 5) // => [1, 2] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.aritize|aritize} - * @see {@link module:lamb.unary|unary} - * @since 0.10.0 - * @param {Function} fn - * @returns {Function} - */ -function binary (fn) { - return function (a, b) { - return fn.call(this, a, b); - }; -} - -/** - * Accepts a series of functions and builds a new function. The functions in the series - * will then be applied, in order, with the values received by the function built with - * collect.
- * The collected results will be returned in an array. - * @example - * var user = { - * id: "jdoe", - * name: "John", - * surname: "Doe", - * scores: [2, 4, 7] - * }; - * var getIDAndLastScore = _.collect([_.getKey("id"), _.getPath("scores.-1")]); - * - * getIDAndLastScore(user) // => ["jdoe", 7] - * - * @example - * var minAndMax = _.collect([Math.min, Math.max]); - * - * minAndMax(3, 1, -2, 5, 4, -1) // => [-2, 5] - * - * @memberof module:lamb - * @category Function - * @since 0.35.0 - * @param {Function[]} functions - * @returns {Function} - */ -function collect (functions) { - if (!Array.isArray(functions)) { - throw _makeTypeErrorFor(functions, "array"); - } - - return function () { - return map(functions, applyTo(arguments)); - }; -} - -/** - * Transforms the evaluation of the given function in the evaluation of a sequence of functions - * expecting only one argument. Each function of the sequence is a partial application of the - * original one, which will be applied when the specified (or derived) arity is consumed.
- * Currying will start from the leftmost argument: use {@link module:lamb.curryRight|curryRight} - * for right currying. - * @example - * var makeWithKeys = _.curry(_.make); - * var makePerson = makeWithKeys(["name", "surname"]); - * - * makePerson(["John", "Doe"]) // => {name: "John", surname: "Doe"}; - * makePerson(["Mario", "Rossi"]) // => {name: "Mario", surname: "Rossi"}; - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.1.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} - */ -function curry (fn, arity) { - return _curry(fn, arity, false); -} - -/** - * Builds an auto-curried function. The resulting function can be called multiple times with - * any number of arguments, and the original function will be applied only when the specified - * (or derived) arity is consumed.
- * Currying will start from the leftmost argument: use {@link module:lamb.curryableRight|curryableRight} - * for right currying. - * @example - * var collectFourElements = _.curryable(_.list, 4); - * - * collectFourElements(2)(3)(4)(5) // => [2, 3, 4, 5] - * collectFourElements(2)(3, 4)(5) // => [2, 3, 4, 5] - * collectFourElements(2, 3, 4, 5) // => [2, 3, 4, 5] - * collectFourElements(2, 3)(4, 5) // => [2, 3, 4, 5] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.6.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} - */ -function curryable (fn, arity) { - return _curry(fn, arity, false, true); -} - -/** - * Same as {@link module:lamb.curryable|curryable}, but currying starts from the rightmost argument. - * @example - * var collectFourElements = _.curryableRight(_.list, 4); - * - * collectFourElements(2)(3)(4)(5) // => [5, 4, 3, 2] - * collectFourElements(2)(3, 4)(5) // => [5, 4, 3, 2] - * collectFourElements(2, 3, 4, 5) // => [5, 4, 3, 2] - * collectFourElements(2, 3)(4, 5) // => [5, 4, 3, 2] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curryable|curryable} - * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.9.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} - */ -function curryableRight (fn, arity) { - return _curry(fn, arity, true, true); -} - -/** - * Same as {@link module:lamb.curry|curry}, but currying starts from the rightmost argument. - * @example - * var makeWithValues = _.curryRight(_.make); - * var makeJohnDoe = makeWithValues(["John", "Doe"]); - * - * makeJohnDoe(["name", "surname"]) // => {name: "John", surname: "Doe"}; - * makeJohnDoe(["firstName", "lastName"]) // => {firstName: "John", lastName: "Doe"}; - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.curry|curry} - * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} - * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} - * @see {@link module:lamb.asPartial|asPartial} - * @since 0.9.0 - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @returns {Function} - */ -function curryRight (fn, arity) { - return _curry(fn, arity, true); -} - -/** - * Returns a function that will execute the given function only if it stops being called for the - * specified timespan.
- * See also {@link module:lamb.throttle|throttle} for a different behaviour where the first call - * happens immediately. - * @example A common use case of debounce in a browser environment: - * var updateLayout = function () { - * // some heavy DOM operations here - * }; - * - * window.addEventListener("resize", _.debounce(updateLayout, 200), false); - * - * // The resize event is fired repeteadly until the user stops resizing the - * // window, while the `updateLayout` function is called only once: 200 ms - * // after he stopped. - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.throttle|throttle} - * @since 0.1.0 - * @param {Function} fn - * @param {Number} timespan - Expressed in milliseconds - * @returns {Function} - */ -function debounce (fn, timespan) { - var timeoutID; - - return function () { - var args = arguments; - var debounced = function () { - timeoutID = null; - fn.apply(this, args); - }.bind(this); - - clearTimeout(timeoutID); - timeoutID = setTimeout(debounced, timespan); - }; -} - -/** - * Returns a function that applies the original function with the arguments in reverse order. - * @example - * _.list(1, 2, 3) // => [1, 2, 3] - * _.flip(_.list)(1, 2, 3) // => [3, 2, 1] - * - * @memberof module:lamb - * @category Function - * @since 0.1.0 - * @param {Function} fn - * @returns {Function} - */ -function flip (fn) { - return function () { - var args = list.apply(null, arguments).reverse(); - - return fn.apply(this, args); - }; -} - -/** - * Builds a function that returns the argument received at the given index.
- * As with {@link module:lamb.getAt|getAt} negative indexes are allowed.
- * The resulting function will return undefined if no arguments are - * passed or if the index is out of bounds. - * @example - * var getFirstArg = _.getArgAt(0); - * var getLastArg = _.getArgAt(-1); - * - * getFirstArg(1, 2, 3) // => 1 - * getLastArg(1, 2, 3) // => 3 - * - * _.getArgAt()(1, 2, 3) // => undefined - * _.getArgAt(6)(1, 2, 3) // => undefined - * _.getArgAt(1)() // => undefined - * - * @memberof module:lamb - * @category Function - * @since 0.17.0 - * @param {Number} idx - * @returns {Function} - */ -function getArgAt (idx) { - return function () { - return arguments[_toNaturalIndex(idx, arguments.length)]; - }; -} - -/** - * Builds a function that will invoke the given method name on any received object and - * return the result. If no method with such name is found the function will return - * undefined.
- * Along with the method name it's possible to supply some arguments that will be bound to the - * method call. Further arguments can also be passed when the function is actually called, and - * they will be concatenated to the bound ones.
- * Returning undefined is a behaviour meant to quickly create a case for - * {@link module:lamb.adapter|adapter} without the need to check for the existence of the - * desired method.
- * See also {@link module:lamb.generic|generic} to create functions out of object methods. - * @example Basic polymorphism with invoker: - * var polySlice = _.invoker("slice"); - * - * polySlice([1, 2, 3, 4, 5], 1, 3) // => [2, 3] - * polySlice("Hello world", 1, 3) // => "el" - * - * @example With bound arguments: - * var substrFrom2 = _.invoker("substr", 2); - * substrFrom2("Hello world") // => "llo world" - * substrFrom2("Hello world", 5) // => "llo w" - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.invokerOn|invokerOn} - * @since 0.1.0 - * @param {String} methodName - * @param {...*} [boundArg] - * @returns {Function} - */ -function invoker (methodName) { - return partial(_invoker, [_argsTail.apply(null, arguments), methodName]); -} - -/** - * Accepts an object and builds a function expecting a method name, and optionally arguments, - * to call on such object. - * Like {@link module:lamb.invoker|invoker}, if no method with the given name is found the - * function will return undefined. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var arr = [1, 2, 3, 4, 5]; - * var invokerOnArr = _.invokerOn(arr); - * - * invokerOnArr("filter", isEven) // => [2, 4] - * invokerOnArr("slice", 1, 3) // => [2, 3] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.invoker|invoker} - * @since 0.15.0 - * @param {Object} target - * @returns {Function} - */ -function invokerOn (target) { - return partial(_invoker, [[], _, target]); -} - -/** - * Builds a function that allows to map over the received arguments before applying them - * to the original one. - * @example - * var sumArray = _.reduceWith(_.sum); - * var sumArgs = _.compose(sumArray, _.list); - * - * sumArgs(1, 2, 3, 4, 5) // => 15 - * - * var square = _.partial(Math.pow, [_, 2]); - * var sumSquares = _.mapArgs(sumArgs, square); - * - * sumSquares(1, 2, 3, 4, 5) // => 55 - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.tapArgs|tapArgs} - * @since 0.3.0 - * @param {Function} fn - * @param {ListIteratorCallback} mapper - * @returns {Function} - */ -function mapArgs (fn, mapper) { - return pipe([list, mapWith(mapper), apply(fn)]); -} - -/** - * Creates a pipeline of functions, where each function consumes the result of the previous one. - * @example - * var square = _.partial(Math.pow, [_, 2]); - * var getMaxAndSquare = _.pipe([Math.max, square]); - * - * getMaxAndSquare(3, 5) // => 25 - * - * @memberof module:lamb - * @category Function - * @function - * @see {@link module:lamb.compose|compose} - * @since 0.1.0 - * @param {Function[]} functions - * @returns {Function} - */ -function pipe (functions) { - if (!Array.isArray(functions)) { - throw _makeTypeErrorFor(functions, "array"); - } - - var len = functions.length; - - return len ? function () { - var result = functions[0].apply(this, arguments); - - for (var i = 1; i < len; i++) { - result = functions[i].call(this, result); - } - - return result; - } : identity; -} - -/** - * Builds a function that allows to "tap" into the arguments of the original one. - * This allows to extract simple values from complex ones, transform arguments or simply intercept them. - * If a "tapper" isn't found the argument is passed as it is. - * @example - * var someObject = {count: 5}; - * var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0]; - * var getDataAmount = _.tapArgs(_.sum, [_.getKey("count"), _.getKey("length")]); - * - * getDataAmount(someObject, someArrayData); // => 15 - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.mapArgs|mapArgs} - * @since 0.3.0 - * @param {Function} fn - * @param {Function[]} tappers - * @returns {Function} - */ -function tapArgs (fn, tappers) { - return function () { - var len = arguments.length; - var tappersLen = tappers.length; - var args = []; - - for (var i = 0; i < len; i++) { - args.push(i < tappersLen ? tappers[i](arguments[i]) : arguments[i]); - } - - return fn.apply(this, args); - }; -} - -/** - * Returns a function that will invoke the passed function at most once in the given timespan.
- * The first call in this case happens as soon as the function is invoked; see also - * {@link module:lamb.debounce|debounce} for a different behaviour where the first call is delayed. - * @example - * var log = _.throttle(console.log.bind(console), 5000); - * - * log("Hi"); // console logs "Hi" - * log("Hi again"); // nothing happens - * // after five seconds - * log("Hello world"); // console logs "Hello world" - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.debounce|debounce} - * @since 0.1.0 - * @param {Function} fn - * @param {Number} timespan - Expressed in milliseconds. - * @returns {Function} - */ -function throttle (fn, timespan) { - var result; - var lastCall = 0; - - return function () { - var now = Date.now(); - - if (now - lastCall >= timespan) { - lastCall = now; - result = fn.apply(this, arguments); - } - - return result; - }; -} - -/** - * Builds a function that passes only one argument to the given function.
- * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, - * exposed for convenience. - * @example - * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; - * - * _.map(weights, _.unary(parseInt)) // => [2, 10, 1, 7] - * - * @memberof module:lamb - * @category Function - * @see {@link module:lamb.aritize|aritize} - * @see {@link module:lamb.binary|binary} - * @since 0.10.0 - * @param {Function} fn - * @returns {Function} - */ -function unary (fn) { - return function (a) { - return fn.call(this, a); - }; -} - -lamb.application = application; -lamb.apply = apply; -lamb.applyTo = applyTo; -lamb.aritize = aritize; -lamb.asPartial = asPartial; -lamb.binary = binary; -lamb.collect = collect; -lamb.curry = curry; -lamb.curryRight = curryRight; -lamb.curryable = curryable; -lamb.curryableRight = curryableRight; -lamb.debounce = debounce; -lamb.flip = flip; -lamb.getArgAt = getArgAt; -lamb.invoker = invoker; -lamb.invokerOn = invokerOn; -lamb.mapArgs = mapArgs; -lamb.pipe = pipe; -lamb.tapArgs = tapArgs; -lamb.throttle = throttle; -lamb.unary = unary; diff --git a/src/function/__tests__/application.spec.js b/src/function/__tests__/application.spec.js new file mode 100644 index 0000000..9e0a66f --- /dev/null +++ b/src/function/__tests__/application.spec.js @@ -0,0 +1,71 @@ +import * as lamb from "../.."; +import { nonArrayLikes, nonFunctions } from "../../__tests__/commons"; + +describe("application / apply / applyTo", function () { + function Foo (value) { + this.value = value; + } + + Foo.prototype = { + value: 0, + bar: function (a, b) { + return (this.value + a) / b; + } + }; + + it("should apply the desired function to the given arguments", function () { + expect(lamb.application(Math.max, [-1, 3, 2, 15, 7])).toBe(15); + expect(lamb.apply(Math.max)([-1, 3, 2, 15, 7])).toBe(15); + expect(lamb.applyTo([-1, 3, 2, 15, 7])(Math.max)).toBe(15); + }); + + it("should accept an array-like object as arguments for the function", function () { + expect(lamb.application(Math.max, "3412")).toBe(4); + expect(lamb.apply(Math.max)("3412")).toBe(4); + expect(lamb.applyTo("3412")(Math.max)).toBe(4); + }); + + it("should not alter the function's context", function () { + var obj = { + value: 4, + application: lamb.application, + applyBar: lamb.apply(Foo.prototype.bar), + baz: lamb.applyTo([1, 2]) + }; + + expect(obj.application(Foo.prototype.bar, [1, 2])).toBe(2.5); + expect(obj.applyBar([1, 2])).toBe(2.5); + expect(obj.baz(Foo.prototype.bar)).toBe(2.5); + }); + + it("should treat non-array-like values for the `args` parameter as empty arrays", function () { + var dummyFn = jest.fn(); + + for (var i = 0, ofs = 0; i < nonArrayLikes.length; i++, ofs += 3) { + lamb.application(dummyFn, nonArrayLikes[i]); + lamb.apply(dummyFn)(nonArrayLikes[i]); + lamb.applyTo(nonArrayLikes[i])(dummyFn); + + expect(dummyFn.mock.calls[ofs].length).toBe(0); + expect(dummyFn.mock.calls[ofs + 1].length).toBe(0); + expect(dummyFn.mock.calls[ofs + 2].length).toBe(0); + } + + lamb.application(dummyFn); + lamb.apply(dummyFn)(); + lamb.applyTo()(dummyFn); + + expect(dummyFn.mock.calls[ofs].length).toBe(0); + expect(dummyFn.mock.calls[ofs + 1].length).toBe(0); + expect(dummyFn.mock.calls[ofs + 2].length).toBe(0); + expect(dummyFn).toHaveBeenCalledTimes(ofs + 3); + }); + + it("should throw an exception if `fn` isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.application(value, []); }).toThrow(); + expect(lamb.apply(value)).toThrow(); + expect(function () { lamb.applyTo([])(value); }).toThrow(); + }); + }); +}); diff --git a/src/function/__tests__/aritize.spec.js b/src/function/__tests__/aritize.spec.js new file mode 100644 index 0000000..21572a2 --- /dev/null +++ b/src/function/__tests__/aritize.spec.js @@ -0,0 +1,77 @@ +import * as lamb from "../.."; +import { nonFunctions, zeroesAsIntegers } from "../../__tests__/commons"; + +describe("aritize", function () { + var maxArgument = jest.fn(Math.max); + + afterEach(function () { + maxArgument.mockClear(); + }); + + it("should change the arity of the given function to the specified value", function () { + var maxOfFirst3 = lamb.aritize(maxArgument, 3); + + expect(maxOfFirst3(0, 1, 2, 3, 4, 5)).toBe(2); + expect(maxArgument.mock.calls[0]).toEqual([0, 1, 2]); + }); + + it("should allow negative arities", function () { + expect(lamb.aritize(maxArgument, -1)(0, 1, 2, 3)).toBe(2); + expect(maxArgument.mock.calls[0]).toEqual([0, 1, 2]); + }); + + it("should call the function without arguments if the arity is zero or if it's out of bounds", function () { + expect(lamb.aritize(maxArgument, -10)(0, 1, 2, 3)).toBe(-Infinity); + expect(lamb.aritize(maxArgument, 0)(0, 1, 2, 3)).toBe(-Infinity); + expect(maxArgument).toHaveBeenCalledTimes(2); + expect(maxArgument.mock.calls[0].length).toBe(0); + expect(maxArgument.mock.calls[1].length).toBe(0); + }); + + it("should add `undefined` arguments if the desired arity is greater than the amount of received parameters", function () { + expect(lamb.aritize(maxArgument, 6)(0, 1, 2, 3)).toEqual(NaN); + expect(maxArgument.mock.calls[0]).toEqual([0, 1, 2, 3, void 0, void 0]); + }); + + it("should convert the arity to an integer following ECMA specifications", function () { + // see https://www.ecma-international.org/ecma-262/7.0/#sec-tointeger + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.aritize(maxArgument, value)(0, 1, 2, 3, 4, 5)).toBe(-Infinity); + expect(maxArgument.mock.calls[0].length).toBe(0); + }); + + expect(lamb.aritize(maxArgument)(0, 1, 2, 3, 4, 5)).toBe(-Infinity); + + var lastCallIdx = maxArgument.mock.calls.length - 1; + + expect(maxArgument.mock.calls[lastCallIdx].length).toBe(0); + + maxArgument.mockClear(); + + [[5], 5.9, "5.9", "-1", ["-1.9"]].forEach(function (value, idx) { + expect(lamb.aritize(maxArgument, value)(0, 1, 2, 3, 4, 5)).toBe(4); + expect(maxArgument.mock.calls[idx]).toEqual([0, 1, 2, 3, 4]); + }); + }); + + it("should not modify the function's context", function () { + var fn = function () { + this.values = this.values.concat(lamb.slice(arguments, 0, arguments.length)); + }; + + var obj = { values: [1, 2, 3], addValues: lamb.aritize(fn, 2) }; + + obj.addValues(4, 5, 6, 7); + + expect(obj.values).toEqual([1, 2, 3, 4, 5]); + }); + + it("should build a function throwing an exception if the `fn` parameter isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.aritize(value, 0)).toThrow(); + }); + + expect(lamb.aritize()).toThrow(); + }); +}); diff --git a/src/function/__tests__/asPartial.spec.js b/src/function/__tests__/asPartial.spec.js new file mode 100644 index 0000000..1b50330 --- /dev/null +++ b/src/function/__tests__/asPartial.spec.js @@ -0,0 +1,87 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("asPartial", function () { + var fooSubtract = jest.fn(function (a, b, c, d) { + return a - b - c - d; + }); + var __ = lamb.__; + + afterEach(function () { + fooSubtract.mockClear(); + }); + + it("should build a function that returns a partial application of the original one as long as it's called with placeholders", function () { + var fn = lamb.asPartial(fooSubtract); + + expect(fn(__, 4, __, __)(__, 3, __)(5, __)(2)).toBe(-4); + expect(fn(__)(__, __)(__)(5, __)(__, 3)(4, __)(2)).toBe(-4); + expect(fn(__, 4, __, __)(__, 3, __)(5, __, __, __, 2, __, __)(99, 6)).toBe(-101); + expect(fn(3, 2, 1, 0)).toBe(0); + expect(fn(5, __, 3)(__)(__, __)(4, __)(2)).toBe(-4); + expect(fn(__, 2, __, 0)(__, __)(3, __)(__)(1)).toBe(0); + expect(fn(5, __, __, __)(4, 3, 2)).toBe(-4); + }); + + it("should be safe to call the partial application multiple times with different values for unfilled placeholders", function () { + var fn = lamb.asPartial(fooSubtract)(__, 5, __, __); + var minusTen1 = fn(__, 4, __)(__, 1); + var minusTen2 = fn(__, 3, 2); + var minusTen3 = fn(__, -5, 10); + + var numbers = [-1000, 6, 502, 856, 790, 547, 157, 750, 111, -419]; + var results = [-1010, -4, 492, 846, 780, 537, 147, 740, 101, -429]; + + expect(numbers.map(minusTen1)).toEqual(results); + expect(numbers.map(minusTen2)).toEqual(results); + expect(numbers.map(minusTen3)).toEqual(results); + }); + + it("should build a function that applies the original function when it's called without placeholders even if its arity isn't consumed", function () { + var fn = lamb.asPartial(fooSubtract); + + expect(fn(5, __, 3)(4)).toEqual(NaN); + expect(fooSubtract).toHaveBeenCalledTimes(1); + expect(fooSubtract.mock.calls[0]).toEqual([5, 4, 3]); + }); + + it("should pass all the received arguments, even if they exceed the original function's arity", function () { + var fn = lamb.asPartial(fooSubtract); + + expect(fn(__, 4, __, 2)(5, 3, 6, 7, 8)).toBe(-4); + expect(fooSubtract).toHaveBeenCalledTimes(1); + expect(fooSubtract.mock.calls[0]).toEqual([5, 4, 3, 2, 6, 7, 8]); + }); + + it("should give an `undefined` value to unfilled placeholders", function () { + var fn = lamb.asPartial(lamb.list)(__, 2, __, 3, __, 5, __); + + expect(fn(1)).toEqual([1, 2, void 0, 3, void 0, 5, void 0]); + }); + + it("should preserve the function's context", function () { + var fn = lamb.asPartial(function (a, b) { + this.values.push(a - b); + }); + + var obj = { + values: [1, 2, 3], + foo: fn(4, __), + bar: fn(__, 4) + }; + + obj.foo(5); + expect(obj.values).toEqual([1, 2, 3, -1]); + + obj.bar(5); + expect(obj.values).toEqual([1, 2, 3, -1, 1]); + }); + + it("should build a function throwing an exception if called without arguments or if `fn` isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.asPartial(value)).toThrow(); + }); + + expect(lamb.asPartial()).toThrow(); + }); +}); diff --git a/src/function/__tests__/collect.spec.js b/src/function/__tests__/collect.spec.js new file mode 100644 index 0000000..d04d780 --- /dev/null +++ b/src/function/__tests__/collect.spec.js @@ -0,0 +1,39 @@ +import * as lamb from "../.."; +import { nonArrayLikes, nonFunctions } from "../../__tests__/commons"; + +describe("collect", function () { + it("should collect the values returned by the given series of functions applied with the provided parameters", function () { + var min = jest.fn(Math.min); + var max = jest.fn(Math.max); + var minAndMax = lamb.collect([min, max]); + + expect(minAndMax(3, 1, -2, 5, 4, -1)).toEqual([-2, 5]); + expect(min).toHaveBeenCalledTimes(1); + expect(max).toHaveBeenCalledTimes(1); + expect(min.mock.calls[0]).toEqual([3, 1, -2, 5, 4, -1]); + expect(max.mock.calls[0]).toEqual([3, 1, -2, 5, 4, -1]); + }); + + it("should return an empty array if the array of functions is empty", function () { + expect(lamb.collect([])(1, 2, 3)).toEqual([]); + }); + + it("should call the received functions even if there are no provided parameters", function () { + expect(lamb.collect([lamb.identity, lamb.always(99)])()).toEqual([void 0, 99]); + }); + + it("should throw an exception if the received parameter isn't an array", function () { + nonArrayLikes.forEach(function (value) { + expect(function () { lamb.collect(value); }).toThrow(); + }); + + expect(lamb.collect).toThrow(); + }); + + it("should build a function returning an exception if it receives a value that isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.collect([lamb.always(99), value])(1, 2, 3); }).toThrow(); + expect(function () { lamb.collect([value, lamb.always(99)])(1, 2, 3); }).toThrow(); + }); + }); +}); diff --git a/src/function/__tests__/currying.spec.js b/src/function/__tests__/currying.spec.js new file mode 100644 index 0000000..6cbcc3b --- /dev/null +++ b/src/function/__tests__/currying.spec.js @@ -0,0 +1,222 @@ +import * as lamb from "../.."; + +describe("currying", function () { + var fooSubtract = jest.fn(function (a, b, c) { + return a - b - c; + }); + + afterEach(function () { + fooSubtract.mockClear(); + }); + + describe("curry / curryRight", function () { + it("should allow currying by always returning a function with an arity of one", function () { + var sub1 = lamb.curry(fooSubtract, 3); + var sub2 = lamb.curryRight(fooSubtract, 3); + + expect(sub1(1)(2)(3)).toBe(-4); + expect(sub2(1)(2)(3)).toBe(0); + }); + + it("should try to desume the arity from the function's length", function () { + var sub1 = lamb.curry(fooSubtract); + var sub2 = lamb.curryRight(fooSubtract); + + expect(sub1(1)(2)(3)).toBe(-4); + expect(sub2(1)(2)(3)).toBe(0); + }); + + it("should return the received function if the desumed or given arity isn't greater than one", function () { + expect(lamb.curry(lamb.list)).toBe(lamb.list); + expect(lamb.curryRight(lamb.list)).toBe(lamb.list); + expect(lamb.curry(lamb.head)).toBe(lamb.head); + expect(lamb.curryRight(lamb.head)).toBe(lamb.head); + }); + + it("should give priority to the `arity` parameter over the function's length", function () { + var sub1 = lamb.curry(fooSubtract, 2); + var sub2 = lamb.curryRight(fooSubtract, 2); + + expect(sub1(1)(2)).toEqual(NaN); + expect(sub2(1)(2)).toEqual(NaN); + + expect(fooSubtract).toHaveBeenCalledTimes(2); + expect(fooSubtract.mock.calls[0]).toEqual([1, 2]); + expect(fooSubtract.mock.calls[1]).toEqual([2, 1]); + }); + + it("should ignore extra parameters", function () { + var sub1 = lamb.curry(fooSubtract, 3); + var sub2 = lamb.curryRight(fooSubtract, 3); + + expect(sub1(1, 99)(2, 88, 77)(3, 66)).toBe(-4); + expect(sub2(1, 99)(2, 88, 77)(3, 66)).toBe(0); + + expect(fooSubtract).toHaveBeenCalledTimes(2); + expect(fooSubtract.mock.calls[0]).toEqual([1, 2, 3]); + expect(fooSubtract.mock.calls[1]).toEqual([3, 2, 1]); + }); + + it("should let empty calls consume the arity", function () { + var collectLeft = lamb.curry(lamb.list, 4); + var collectRight = lamb.curryRight(lamb.list, 4); + + expect(collectLeft("a", "z")()("c")("d")).toEqual(["a", void 0, "c", "d"]); + expect(collectRight("a", "z")()("c")("d")).toEqual(["d", "c", void 0, "a"]); + }); + + it("should return a reusable function", function () { + var fooSubFromFive = lamb.curry(fooSubtract)(5); + var fooSubMinusFive = lamb.curryRight(fooSubtract)(5); + var subFromOne = fooSubFromFive(4); + var subFromTwo = fooSubFromFive(3); + var minusNine = fooSubMinusFive(4); + var minusSix = fooSubMinusFive(1); + + expect(fooSubFromFive(4)(3)).toBe(-2); + expect(fooSubMinusFive(4)(3)).toBe(-6); + expect(subFromOne(1)).toBe(0); + expect(subFromOne(3)).toBe(-2); + expect(subFromTwo(3)).toBe(-1); + expect(subFromTwo(2)).toBe(0); + expect(minusNine(10)).toBe(1); + expect(minusNine(9)).toBe(0); + expect(minusSix(10)).toBe(4); + expect(minusSix(4)).toBe(-2); + }); + + it("should preserve the function's context", function () { + var fn = function (a, b) { + this.values.push(a - b); + }; + + var obj = { + values: [1, 2, 3], + foo: lamb.curry(fn)(4), + bar: lamb.curryRight(fn)(4) + }; + + obj.foo(5); + expect(obj.values).toEqual([1, 2, 3, -1]); + + obj.bar(5); + expect(obj.values).toEqual([1, 2, 3, -1, 1]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.curry).toThrow(); + expect(lamb.curryRight).toThrow(); + }); + }); + + describe("curryable / curryableRight", function () { + it("should build an \"auto-curried\" function that allows us to consume its arity in any moment", function () { + var sub1 = lamb.curryable(fooSubtract, 3); + var sub2 = lamb.curryableRight(fooSubtract, 3); + + expect(sub1(1, 2, 3)).toBe(-4); + expect(sub1(1, 2)(3)).toBe(-4); + expect(sub1(1)(2, 3)).toBe(-4); + expect(sub1(1)(2)(3)).toBe(-4); + expect(sub2(1, 2, 3)).toBe(0); + expect(sub2(1, 2)(3)).toBe(0); + expect(sub2(1)(2, 3)).toBe(0); + expect(sub2(1)(2)(3)).toBe(0); + }); + + it("should try to desume the arity from the function's length", function () { + var sub1 = lamb.curryable(fooSubtract); + var sub2 = lamb.curryableRight(fooSubtract); + + expect(sub1(1)(2, 3)).toBe(-4); + expect(sub2(1)(2, 3)).toBe(0); + }); + + it("should return the received function if the desumed or given arity isn't greater than one", function () { + expect(lamb.curryable(lamb.list)).toBe(lamb.list); + expect(lamb.curryableRight(lamb.list)).toBe(lamb.list); + expect(lamb.curryable(lamb.head)).toBe(lamb.head); + expect(lamb.curryableRight(lamb.head)).toBe(lamb.head); + }); + + it("should give priority to the `arity` parameter over the function's length", function () { + var sub1 = lamb.curryable(fooSubtract, 2); + var sub2 = lamb.curryableRight(fooSubtract, 2); + + expect(sub1(1)(2)).toEqual(NaN); + expect(sub2(1)(2)).toEqual(NaN); + + expect(fooSubtract).toHaveBeenCalledTimes(2); + expect(fooSubtract.mock.calls[0]).toEqual([1, 2]); + expect(fooSubtract.mock.calls[1]).toEqual([2, 1]); + }); + + it("should pass extra arguments received to the original function", function () { + var foo = jest.fn(); + var args = [1, 2, 3, 4, 5]; + + lamb.curryable(foo, 3)(1, 2)(3, 4, 5); + lamb.curryableRight(foo, 4)(5, 4)(3)(2, 1); + lamb.curryable(fooSubtract, 3)(1, 2)(3, 4, 5); + lamb.curryableRight(fooSubtract, 3)(5, 4)(3, 2, 1); + + expect(foo).toHaveBeenCalledTimes(2); + expect(foo.mock.calls[0]).toEqual(args); + expect(foo.mock.calls[1]).toEqual(args); + expect(fooSubtract).toHaveBeenCalledTimes(2); + expect(fooSubtract.mock.calls[0]).toEqual(args); + expect(fooSubtract.mock.calls[1]).toEqual(args); + }); + + it("should let empty calls consume the arity", function () { + var collectLeft = lamb.curryable(lamb.list, 4); + var collectRight = lamb.curryableRight(lamb.list, 4); + + expect(collectLeft("a")()("c", "d")).toEqual(["a", void 0, "c", "d"]); + expect(collectRight("a")()("c", "d")).toEqual(["d", "c", void 0, "a"]); + }); + + it("should return a reusable function", function () { + var fooSubFromFive = lamb.curryable(fooSubtract)(5); + var fooSubMinusFive = lamb.curryableRight(fooSubtract)(5); + var subFromOne = fooSubFromFive(4); + var subFromTwo = fooSubFromFive(3); + var minusNine = fooSubMinusFive(4); + var minusSix = fooSubMinusFive(1); + + expect(fooSubFromFive(4, 3)).toBe(-2); + expect(fooSubMinusFive(4, 3)).toBe(-6); + expect(subFromOne(1)).toBe(0); + expect(subFromOne(3)).toBe(-2); + expect(subFromTwo(3)).toBe(-1); + expect(subFromTwo(2)).toBe(0); + expect(minusNine(10)).toBe(1); + expect(minusNine(9)).toBe(0); + expect(minusSix(10)).toBe(4); + expect(minusSix(4)).toBe(-2); + }); + + it("should preserve the function's context", function () { + var fn = function (a, b) { + this.values.push(a - b); + }; + + var obj = { + values: [1, 2, 3], + foo: lamb.curryable(fn), + bar: lamb.curryableRight(fn) + }; + + obj.foo(4, 5); + expect(obj.values).toEqual([1, 2, 3, -1]); + + obj.bar(4, 5); + expect(obj.values).toEqual([1, 2, 3, -1, 1]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.curryable).toThrow(); + expect(lamb.curryableRight).toThrow(); + }); + }); +}); diff --git a/src/function/__tests__/debounce.spec.js b/src/function/__tests__/debounce.spec.js new file mode 100644 index 0000000..264a13a --- /dev/null +++ b/src/function/__tests__/debounce.spec.js @@ -0,0 +1,26 @@ +import * as lamb from "../.."; + +jest.useFakeTimers(); + +describe("debounce", function () { + var value = 0; + var testFn = jest.fn(function (n) { + value += n; + }); + + it("should return a function that will execute the given function only if it stops being called for the specified timespan", function () { + var debounced = lamb.debounce(testFn, 100); + + debounced(1); + debounced(2); + debounced(3); + + expect(testFn).toHaveBeenCalledTimes(0); + expect(value).toBe(0); + + jest.advanceTimersByTime(101); + + expect(testFn).toHaveBeenCalledTimes(1); + expect(value).toBe(3); + }); +}); diff --git a/src/function/__tests__/flip.spec.js b/src/function/__tests__/flip.spec.js new file mode 100644 index 0000000..1ca4680 --- /dev/null +++ b/src/function/__tests__/flip.spec.js @@ -0,0 +1,10 @@ +import * as lamb from "../.."; + +describe("flip", function () { + it("should return a function that applies its arguments to the original function in reverse order", function () { + var appendTo = function (a, b) { return a + b; }; + var prependTo = lamb.flip(appendTo); + + expect(prependTo("foo", "bar")).toBe("barfoo"); + }); +}); diff --git a/src/function/__tests__/getArgAt.spec.js b/src/function/__tests__/getArgAt.spec.js new file mode 100644 index 0000000..20e46a1 --- /dev/null +++ b/src/function/__tests__/getArgAt.spec.js @@ -0,0 +1,32 @@ +import * as lamb from "../.."; +import { zeroesAsIntegers } from "../../__tests__/commons"; + +describe("getArgAt", function () { + it("should build a function that returns the argument received at the given index", function () { + expect(lamb.getArgAt(1)("a", "b", "c")).toBe("b"); + }); + + it("should allow negative indexes", function () { + expect(lamb.getArgAt(-1)("a", "b", "c")).toBe("c"); + expect(lamb.getArgAt(-2)("a", "b", "c")).toBe("b"); + }); + + it("should build a function returning `undefined` if no arguments are passed or if the index is out of bounds", function () { + expect(lamb.getArgAt(6)("a", "b", "c")).toBeUndefined(); + expect(lamb.getArgAt(-4)("a", "b", "c")).toBeUndefined(); + expect(lamb.getArgAt(2)()).toBeUndefined(); + }); + + it("should convert the `idx` parameter to integer", function () { + zeroesAsIntegers.forEach(function (value) { + expect(lamb.getArgAt(value)("a", "b", "c")).toBe("a"); + }); + + [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { + expect(lamb.getArgAt(value)("a", "b", "c")).toBe("b"); + }); + + expect(lamb.getArgAt(new Date())("a", "b", "c")).toBeUndefined(); + expect(lamb.getArgAt()("a", "b", "c")).toBe("a"); + }); +}); diff --git a/src/function/__tests__/invoker.spec.js b/src/function/__tests__/invoker.spec.js new file mode 100644 index 0000000..652cb8b --- /dev/null +++ b/src/function/__tests__/invoker.spec.js @@ -0,0 +1,71 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("invoker", function () { + var slice = lamb.invoker("slice"); + var tail = lamb.invoker("slice", 1); + var arr = [1, 2, 3, 4, 5]; + var sliceSpy = jest.spyOn(arr, "slice"); + + afterEach(function () { + sliceSpy.mockClear(); + }); + + it("should build a function that will invoke the desired method on the given object", function () { + var s = "foo bar"; + + expect(slice(arr, 1, 3)).toEqual([2, 3]); + expect(arr.slice).toHaveBeenCalledTimes(1); + expect(arr.slice.mock.calls[0]).toEqual([1, 3]); + expect(slice(s, 1, 3)).toBe("oo"); + }); + + it("should allow bound arguments", function () { + expect(tail(arr)).toEqual([2, 3, 4, 5]); + expect(tail(arr, -1)).toEqual([2, 3, 4]); + expect(arr.slice).toHaveBeenCalledTimes(2); + expect(arr.slice.mock.calls[0]).toEqual([1]); + expect(arr.slice.mock.calls[1]).toEqual([1, -1]); + }); + + it("should build a function returning `undefined` if the given method doesn't exist on the received object", function () { + expect(slice({})).toBeUndefined(); + expect(slice(new Date())).toBeUndefined(); + }); + + it("should accept an empty string as a method name", function () { + var obj = { "": function () { return 99; } }; + + expect(lamb.invoker("")(obj)).toBe(99); + }); + + it("should convert to string every value received as a method name", function () { + var obj = {}; + + nonStringsAsStrings.forEach(function (method, idx) { + obj[method] = lamb.always(method); + + expect(lamb.invoker(nonStrings[idx])(obj)).toBe(method); + }); + + expect(lamb.invoker()(obj)).toBe("undefined"); + }); + + it("should build a function throwing an exception if the received object is `null`, `undefined` or is missing", function () { + expect(function () { slice(null); }).toThrow(); + expect(function () { slice(void 0); }).toThrow(); + expect(slice).toThrow(); + }); + + it("should build a function that converts to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + if (value !== "") { + expect(slice(value)).toBeUndefined(); + } + }); + }); +}); diff --git a/src/function/__tests__/invokerOn.spec.js b/src/function/__tests__/invokerOn.spec.js new file mode 100644 index 0000000..573806a --- /dev/null +++ b/src/function/__tests__/invokerOn.spec.js @@ -0,0 +1,70 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("invokerOn", function () { + var arr = [1, 2, 3, 4, 5]; + var callOnArr = lamb.invokerOn(arr); + + var sliceSpy = jest.spyOn(arr, "slice"); + var joinSpy = jest.spyOn(arr, "join"); + + afterEach(function () { + sliceSpy.mockClear(); + joinSpy.mockClear(); + }); + + it("should accept an object and build a function expecting a method name to be called on such object with the given parameters", function () { + var s = "foo bar"; + var callOnS = lamb.invokerOn(s); + + expect(callOnArr("slice", 1, 3)).toEqual([2, 3]); + expect(arr.slice).toHaveBeenCalledTimes(1); + expect(arr.slice.mock.calls[0]).toEqual([1, 3]); + + expect(callOnArr("join", "")).toBe("12345"); + expect(arr.join).toHaveBeenCalledTimes(1); + expect(arr.join.mock.calls[0]).toEqual([""]); + + expect(callOnS("slice", 1, 3)).toBe("oo"); + expect(callOnS("toUpperCase")).toBe("FOO BAR"); + }); + + it("should build a function returning `undefined` if the given method doesn't exist on the received object", function () { + expect(callOnArr("foo")).toBeUndefined(); + }); + + it("should accept an empty string as a method name", function () { + var obj = { "": function () { return 99; } }; + + expect(lamb.invokerOn(obj)("")).toBe(99); + }); + + it("should convert to string every value received as a method name", function () { + var obj = {}; + var callOnObj = lamb.invokerOn(obj); + + nonStringsAsStrings.forEach(function (method, idx) { + obj[method] = lamb.always(method); + + expect(callOnObj(nonStrings[idx])).toBe(method); + }); + + expect(callOnObj()).toBe("undefined"); + }); + + it("should build a function throwing an exception if the received object is `null`, `undefined` or is missing", function () { + expect(lamb.invokerOn(null)).toThrow(); + expect(lamb.invokerOn(void 0)).toThrow(); + expect(lamb.invokerOn()).toThrow(); + }); + + it("should build a function that converts to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.invokerOn(value)("someMethod")).toBeUndefined(); + }); + }); +}); diff --git a/src/function/__tests__/mapArgs.spec.js b/src/function/__tests__/mapArgs.spec.js new file mode 100644 index 0000000..04f5e2a --- /dev/null +++ b/src/function/__tests__/mapArgs.spec.js @@ -0,0 +1,35 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("mapArgs", function () { + var double = jest.fn(function (n) { return n * 2; }); + + afterEach(function () { + double.mockClear(); + }); + + it("should build a function that allows to map over the received arguments before applying them to the original one", function () { + expect(lamb.mapArgs(lamb.sum, double)(5, 3)).toBe(16); + expect(double).toHaveBeenCalledTimes(2); + expect(double.mock.calls[0]).toEqual([5, 0, [5, 3]]); + expect(double.mock.calls[1]).toEqual([3, 1, [5, 3]]); + }); + + it("should build a function throwing an exception if the mapper isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.mapArgs(lamb.sum, value)(1, 2); }).toThrow(); + }); + + expect(function () { lamb.mapArgs(lamb.sum)(1, 2); }).toThrow(); + }); + + it("should build a function throwing an exception if `fn` isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.mapArgs(value, double)).toThrow(); + }); + }); + + it("should build a function throwing an exception if called without arguments", function () { + expect(lamb.mapArgs()).toThrow(); + }); +}); diff --git a/src/function/__tests__/pipe.spec.js b/src/function/__tests__/pipe.spec.js new file mode 100644 index 0000000..0e0cfb8 --- /dev/null +++ b/src/function/__tests__/pipe.spec.js @@ -0,0 +1,52 @@ +import * as lamb from "../.."; +import { nonArrayLikes, nonFunctions } from "../../__tests__/commons"; + +describe("pipe", function () { + var double = function (n) { return n * 2; }; + var cube = function (n) { return Math.pow(n, 3); }; + var changeSign = function (n) { return -n; }; + + it("should return a function that is the pipeline of the given ones", function () { + var pipeline = lamb.pipe([cube, changeSign, double]); + + expect(pipeline(2)).toBe(-16); + }); + + it("should be possible to reuse piped functions", function () { + var cubeAndDouble = lamb.pipe([cube, double]); + var fn1 = lamb.pipe([cubeAndDouble, cube, double]); + var fn2 = lamb.pipe([cubeAndDouble, cubeAndDouble]); + + expect(fn1(5)).toBe(31250000); + expect(fn2(5)).toBe(31250000); + }); + + it("should behave like the received function if only one function is supplied", function () { + var fn = function (a, b, c) { return a - b - c; }; + + expect(lamb.pipe([fn])(5, 4, 3)).toBe(-2); + }); + + it("should behave like `identity` if no functions are passed", function () { + var obj = {}; + + expect(lamb.pipe([])(obj)).toBe(obj); + expect(lamb.pipe([])()).toBeUndefined(); + expect(lamb.pipe([])(2, 3, 4)).toBe(2); + }); + + it("should throw an exception if the received parameter isn't an array", function () { + nonArrayLikes.forEach(function (value) { + expect(function () { lamb.pipe(value); }).toThrow(); + }); + + expect(lamb.pipe).toThrow(); + }); + + it("should build a function throwing an exception if any parameter is not a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.pipe([lamb.identity, value])).toThrow(); + expect(lamb.pipe([value, lamb.identity])).toThrow(); + }); + }); +}); diff --git a/src/function/__tests__/tapArgs.spec.js b/src/function/__tests__/tapArgs.spec.js new file mode 100644 index 0000000..62f11ea --- /dev/null +++ b/src/function/__tests__/tapArgs.spec.js @@ -0,0 +1,62 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("tapArgs", function () { + var someObject = { count: 5 }; + var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0]; + var sum = jest.fn(lamb.sum); + + afterEach(function () { + sum.mockClear(); + }); + + it("should build a function that allows to tap into the arguments of the original one", function () { + var getDataAmount = lamb.tapArgs(sum, [lamb.getKey("count"), lamb.getKey("length")]); + + expect(getDataAmount(someObject, someArrayData)).toBe(15); + expect(sum).toHaveBeenCalledTimes(1); + expect(sum.mock.calls[0]).toEqual([someObject.count, someArrayData.length]); + }); + + it("should use arguments as they are when tappers are missing", function () { + expect(lamb.tapArgs(sum, [lamb.getKey("count")])(someObject, -10)).toBe(-5); + expect(sum).toHaveBeenCalledTimes(1); + expect(sum.mock.calls[0]).toEqual([someObject.count, -10]); + }); + + it("should build a function throwing an exception if a tapper isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.tapArgs(lamb.sum, [value, lamb.always(99)])(1, 2); }).toThrow(); + expect(function () { lamb.tapArgs(lamb.sum, [lamb.always(99), value])(1, 2); }).toThrow(); + }); + }); + + it("should build a function that doesn't throw an exception if a tapper isn't a function but is not called", function () { + var inc = function (n) { return ++n; }; + + nonFunctions.forEach(function (value) { + expect(lamb.tapArgs(lamb.sum, [value, lamb.always(99)])()).toEqual(NaN); + expect(lamb.tapArgs(inc, [inc, value])(25)).toBe(27); + }); + }); + + it("should build a function throwing an exception if a `nil` value is passed as the tappers array", function () { + expect(function () { lamb.tapArgs(sum, null)(2, 3); }).toThrow(); + expect(function () { lamb.tapArgs(sum, void 0)(2, 3); }).toThrow(); + }); + + it("should consider other values as empty arrays", function () { + wannabeEmptyArrays.forEach(function (value, idx) { + expect(lamb.tapArgs(sum, value)(2, 3)).toBe(5); + expect(sum.mock.calls[idx]).toEqual([2, 3]); + }); + }); + + it("should build a function throwing an exception if `fn` isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.tapArgs(value, [lamb.always(99)])).toThrow(); + }); + + expect(lamb.tapArgs()).toThrow(); + }); +}); diff --git a/src/function/__tests__/throttle.spec.js b/src/function/__tests__/throttle.spec.js new file mode 100644 index 0000000..aeeabaa --- /dev/null +++ b/src/function/__tests__/throttle.spec.js @@ -0,0 +1,41 @@ +import * as lamb from "../.."; + +describe("throttle", function () { + var value = 0; + var foo = jest.fn(function () { + ++value; + }); + var now = Date.now(); + + jest.spyOn(Date, "now") + .mockReturnValueOnce(now) + .mockReturnValueOnce(now + 10) + .mockReturnValueOnce(now + 50) + .mockReturnValueOnce(now + 101) + .mockReturnValueOnce(now + 151) + .mockReturnValueOnce(now + 201); + + it("should return a function that will invoke the passed function at most once in the given timespan", function () { + var throttledFoo = lamb.throttle(foo, 100); + + throttledFoo(); + throttledFoo(); + throttledFoo(); + + expect(foo).toHaveBeenCalledTimes(1); + expect(value).toBe(1); + + foo.mockClear(); + + throttledFoo(); + throttledFoo(); + + expect(foo).toHaveBeenCalledTimes(1); + expect(value).toBe(2); + + throttledFoo(); + + expect(foo).toHaveBeenCalledTimes(2); + expect(value).toBe(3); + }); +}); diff --git a/src/function/__tests__/unary.spec.js b/src/function/__tests__/unary.spec.js new file mode 100644 index 0000000..49f0dbe --- /dev/null +++ b/src/function/__tests__/unary.spec.js @@ -0,0 +1,40 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("unary", function () { + var listSpy = jest.fn(lamb.list); + var unaryList = lamb.unary(listSpy); + + afterEach(function () { + listSpy.mockClear(); + }); + + it("should build a function that passes only one argument to the given one", function () { + expect(unaryList.length).toBe(1); + expect(unaryList(1, 2, 3)).toEqual([1]); + expect(listSpy.mock.calls[0]).toEqual([1]); + }); + + it("should add an `undefined` argument if the built function doesn't receive parameters", function () { + expect(unaryList()).toEqual([void 0]); + }); + + it("should not modify the function's context", function () { + var fn = function () { + this.values.push(arguments[0]); + }; + var obj = { values: [1, 2, 3], addValue: lamb.unary(fn) }; + + obj.addValue(4); + + expect(obj.values).toEqual([1, 2, 3, 4]); + }); + + it("should build a function throwing an exception if the `fn` parameter isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.unary(value)).toThrow(); + }); + + expect(lamb.unary()).toThrow(); + }); +}); diff --git a/src/function/application.js b/src/function/application.js new file mode 100644 index 0000000..11cd177 --- /dev/null +++ b/src/function/application.js @@ -0,0 +1,18 @@ +/** + * Applies the given function to a list of arguments. + * @example + * _.application(_.sum, [3, 4]) // => 7 + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.apply|apply}, {@link module:lamb.applyTo|applyTo} + * @since 0.47.0 + * @param {Function} fn + * @param {ArrayLike} args + * @returns {*} + */ +function application (fn, args) { + return fn.apply(this, Object(args)); +} + +export default application; diff --git a/src/function/apply.js b/src/function/apply.js new file mode 100644 index 0000000..6a2c712 --- /dev/null +++ b/src/function/apply.js @@ -0,0 +1,22 @@ +import _curry2 from "../privates/_curry2"; +import application from "./application"; + +/** + * A left-curried version of {@link module:lamb.application|application}. Expects the function + * to apply and builds a function waiting for the arguments array. + * @example + * var arrayMax = _.apply(Math.max); + * + * arrayMax([4, 5, 2, 6, 1]) // => 6 + * + * @memberof module:lamb + * @category Function + * @function + * @see {@link module:lamb.application|application}, {@link module:lamb.applyTo|applyTo} + * @since 0.1.0 + * @param {Function} fn + * @returns {Function} + */ +var apply = _curry2(application); + +export default apply; diff --git a/src/function/applyTo.js b/src/function/applyTo.js new file mode 100644 index 0000000..23e0927 --- /dev/null +++ b/src/function/applyTo.js @@ -0,0 +1,24 @@ +import _curry2 from "../privates/_curry2"; +import application from "./application"; + +/** + * A right-curried version of {@link module:lamb.application|application}. Expects an array-like + * object to use as arguments and builds a function waiting for the target of the application. + * @example + * var data = [3, 4]; + * var applyToData = _.applyTo(data); + * + * applyToData(_.sum) // => 7 + * applyToData(_.multiply) // => 12 + * + * @memberof module:lamb + * @category Function + * @function + * @see {@link module:lamb.application|application}, {@link module:lamb.apply|apply} + * @since 0.47.0 + * @param {ArrayLike} args + * @returns {Function} + */ +var applyTo = _curry2(application, true); + +export default applyTo; diff --git a/src/function/aritize.js b/src/function/aritize.js new file mode 100644 index 0000000..d7dcdba --- /dev/null +++ b/src/function/aritize.js @@ -0,0 +1,36 @@ +import _toInteger from "../privates/_toInteger"; +import list from "../array/list"; + +/** + * Builds a new function that passes only the specified amount of arguments to the original one.
+ * As {@link module:lamb.slice|slice} is used to extract the arguments, you can also + * pass a negative arity. + * @example + * Math.max(10, 11, 45, 99) // => 99 + * _.aritize(Math.max, 2)(10, 11, 45, 99) // => 11 + * + * @example Using a negative arity: + * _.aritize(Math.max, -1)(10, 11, 45, 99) // => 45 + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.binary|binary}, {@link module:lamb.unary|unary} for common use cases shortcuts + * @since 0.1.0 + * @param {Function} fn + * @param {Number} arity + * @returns {Function} + */ +function aritize (fn, arity) { + return function () { + var n = _toInteger(arity); + var args = list.apply(null, arguments).slice(0, n); + + for (var i = args.length; i < n; i++) { + args[i] = void 0; + } + + return fn.apply(this, args); + }; +} + +export default aritize; diff --git a/src/function/asPartial.js b/src/function/asPartial.js new file mode 100644 index 0000000..fd90d0e --- /dev/null +++ b/src/function/asPartial.js @@ -0,0 +1,51 @@ +import _asPartial from "../privates/_asPartial"; + +/** + * Decorates the received function so that it can be called with + * placeholders to build a partial application of it.
+ * The difference with {@link module:lamb.partial|partial} is that, as long as + * you call the generated function with placeholders, another partial application + * of the original function will be built.
+ * The final application will happen when one of the generated functions is + * invoked without placeholders, using the parameters collected so far.
+ * This function comes in handy when you need to build different specialized + * functions starting from a basic one, but it's also useful when dealing with + * optional parameters as you can decide to apply the function even if its arity + * hasn't been entirely consumed. + * @example Explaining the function's behaviour: + * var __ = _.__; + * var f = _.asPartial(function (a, b, c) { + * return a + b + c; + * }); + * + * f(4, 3, 2) // => 9 + * f(4, __, 2)(3) // => 9 + * f(__, 3, __)(4, __)(2) // => 9 + * + * @example Exploiting optional parameters: + * var __ = _.__; + * var f = _.asPartial(function (a, b, c) { + * return a + b + (c || 0); + * }); + * + * var addFive = f(5, __); + * addFive(2) // => 7 + * + * var addNine = addFive(4, __); + * addNine(11) // => 20 + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.__|__} The placeholder object. + * @since 0.36.0 + * @param {Function} fn + * @returns {Function} + */ +function asPartial (fn) { + return _asPartial(fn, []); +} + +export default asPartial; diff --git a/src/function/collect.js b/src/function/collect.js new file mode 100644 index 0000000..2769867 --- /dev/null +++ b/src/function/collect.js @@ -0,0 +1,42 @@ +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; +import map from "../core/map"; +import applyTo from "./applyTo"; + +/** + * Accepts a series of functions and builds a new function. The functions in the series + * will then be applied, in order, with the values received by the function built with + * collect.
+ * The collected results will be returned in an array. + * @example + * var user = { + * id: "jdoe", + * name: "John", + * surname: "Doe", + * scores: [2, 4, 7] + * }; + * var getIDAndLastScore = _.collect([_.getKey("id"), _.getPath("scores.-1")]); + * + * getIDAndLastScore(user) // => ["jdoe", 7] + * + * @example + * var minAndMax = _.collect([Math.min, Math.max]); + * + * minAndMax(3, 1, -2, 5, 4, -1) // => [-2, 5] + * + * @memberof module:lamb + * @category Function + * @since 0.35.0 + * @param {Function[]} functions + * @returns {Function} + */ +function collect (functions) { + if (!Array.isArray(functions)) { + throw _makeTypeErrorFor(functions, "array"); + } + + return function () { + return map(functions, applyTo(arguments)); + }; +} + +export default collect; diff --git a/src/function/curry.js b/src/function/curry.js new file mode 100644 index 0000000..61bb28b --- /dev/null +++ b/src/function/curry.js @@ -0,0 +1,31 @@ +import _curry from "../privates/_curry"; + +/** + * Transforms the evaluation of the given function in the evaluation of a sequence of functions + * expecting only one argument. Each function of the sequence is a partial application of the + * original one, which will be applied when the specified (or derived) arity is consumed.
+ * Currying will start from the leftmost argument: use {@link module:lamb.curryRight|curryRight} + * for right currying. + * @example + * var makeWithKeys = _.curry(_.make); + * var makePerson = makeWithKeys(["name", "surname"]); + * + * makePerson(["John", "Doe"]) // => {name: "John", surname: "Doe"}; + * makePerson(["Mario", "Rossi"]) // => {name: "Mario", surname: "Rossi"}; + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.1.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] + * @returns {Function} + */ +function curry (fn, arity) { + return _curry(fn, arity, false); +} + +export default curry; diff --git a/src/function/curryRight.js b/src/function/curryRight.js new file mode 100644 index 0000000..b8b9b1c --- /dev/null +++ b/src/function/curryRight.js @@ -0,0 +1,27 @@ +import _curry from "../privates/_curry"; + +/** + * Same as {@link module:lamb.curry|curry}, but currying starts from the rightmost argument. + * @example + * var makeWithValues = _.curryRight(_.make); + * var makeJohnDoe = makeWithValues(["John", "Doe"]); + * + * makeJohnDoe(["name", "surname"]) // => {name: "John", surname: "Doe"}; + * makeJohnDoe(["firstName", "lastName"]) // => {firstName: "John", lastName: "Doe"}; + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.curry|curry} + * @see {@link module:lamb.curryable|curryable}, {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.9.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] + * @returns {Function} + */ +function curryRight (fn, arity) { + return _curry(fn, arity, true); +} + +export default curryRight; diff --git a/src/function/curryable.js b/src/function/curryable.js new file mode 100644 index 0000000..739be2a --- /dev/null +++ b/src/function/curryable.js @@ -0,0 +1,32 @@ +import _curry from "../privates/_curry"; + +/** + * Builds an auto-curried function. The resulting function can be called multiple times with + * any number of arguments, and the original function will be applied only when the specified + * (or derived) arity is consumed.
+ * Currying will start from the leftmost argument: use {@link module:lamb.curryableRight|curryableRight} + * for right currying. + * @example + * var collectFourElements = _.curryable(_.list, 4); + * + * collectFourElements(2)(3)(4)(5) // => [2, 3, 4, 5] + * collectFourElements(2)(3, 4)(5) // => [2, 3, 4, 5] + * collectFourElements(2, 3, 4, 5) // => [2, 3, 4, 5] + * collectFourElements(2, 3)(4, 5) // => [2, 3, 4, 5] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.curryableRight|curryableRight} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.6.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] + * @returns {Function} + */ +function curryable (fn, arity) { + return _curry(fn, arity, false, true); +} + +export default curryable; diff --git a/src/function/curryableRight.js b/src/function/curryableRight.js new file mode 100644 index 0000000..5f9f395 --- /dev/null +++ b/src/function/curryableRight.js @@ -0,0 +1,28 @@ +import _curry from "../privates/_curry"; + +/** + * Same as {@link module:lamb.curryable|curryable}, but currying starts from the rightmost argument. + * @example + * var collectFourElements = _.curryableRight(_.list, 4); + * + * collectFourElements(2)(3)(4)(5) // => [5, 4, 3, 2] + * collectFourElements(2)(3, 4)(5) // => [5, 4, 3, 2] + * collectFourElements(2, 3, 4, 5) // => [5, 4, 3, 2] + * collectFourElements(2, 3)(4, 5) // => [5, 4, 3, 2] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.curryable|curryable} + * @see {@link module:lamb.curry|curry}, {@link module:lamb.curryRight|curryRight} + * @see {@link module:lamb.partial|partial}, {@link module:lamb.partialRight|partialRight} + * @see {@link module:lamb.asPartial|asPartial} + * @since 0.9.0 + * @param {Function} fn + * @param {Number} [arity=fn.length] + * @returns {Function} + */ +function curryableRight (fn, arity) { + return _curry(fn, arity, true, true); +} + +export default curryableRight; diff --git a/src/function/debounce.js b/src/function/debounce.js new file mode 100644 index 0000000..3115fee --- /dev/null +++ b/src/function/debounce.js @@ -0,0 +1,40 @@ +/** + * Returns a function that will execute the given function only if it stops being called for the + * specified timespan.
+ * See also {@link module:lamb.throttle|throttle} for a different behaviour where the first call + * happens immediately. + * @example A common use case of debounce in a browser environment: + * var updateLayout = function () { + * // some heavy DOM operations here + * }; + * + * window.addEventListener("resize", _.debounce(updateLayout, 200), false); + * + * // The resize event is fired repeteadly until the user stops resizing the + * // window, while the `updateLayout` function is called only once: 200 ms + * // after he stopped. + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.throttle|throttle} + * @since 0.1.0 + * @param {Function} fn + * @param {Number} timespan - Expressed in milliseconds + * @returns {Function} + */ +function debounce (fn, timespan) { + var timeoutID; + + return function () { + var args = arguments; + var debounced = function () { + timeoutID = null; + fn.apply(this, args); + }.bind(this); + + clearTimeout(timeoutID); + timeoutID = setTimeout(debounced, timespan); + }; +} + +export default debounce; diff --git a/src/function/flip.js b/src/function/flip.js new file mode 100644 index 0000000..b79224f --- /dev/null +++ b/src/function/flip.js @@ -0,0 +1,23 @@ +import list from "../array/list"; + +/** + * Returns a function that applies the original function with the arguments in reverse order. + * @example + * _.list(1, 2, 3) // => [1, 2, 3] + * _.flip(_.list)(1, 2, 3) // => [3, 2, 1] + * + * @memberof module:lamb + * @category Function + * @since 0.1.0 + * @param {Function} fn + * @returns {Function} + */ +function flip (fn) { + return function () { + var args = list.apply(null, arguments).reverse(); + + return fn.apply(this, args); + }; +} + +export default flip; diff --git a/src/function/getArgAt.js b/src/function/getArgAt.js new file mode 100644 index 0000000..56a33e8 --- /dev/null +++ b/src/function/getArgAt.js @@ -0,0 +1,31 @@ +import _toNaturalIndex from "../privates/_toNaturalIndex"; + +/** + * Builds a function that returns the argument received at the given index.
+ * As with {@link module:lamb.getAt|getAt} negative indexes are allowed.
+ * The resulting function will return undefined if no arguments are + * passed or if the index is out of bounds. + * @example + * var getFirstArg = _.getArgAt(0); + * var getLastArg = _.getArgAt(-1); + * + * getFirstArg(1, 2, 3) // => 1 + * getLastArg(1, 2, 3) // => 3 + * + * _.getArgAt()(1, 2, 3) // => undefined + * _.getArgAt(6)(1, 2, 3) // => undefined + * _.getArgAt(1)() // => undefined + * + * @memberof module:lamb + * @category Function + * @since 0.17.0 + * @param {Number} idx + * @returns {Function} + */ +function getArgAt (idx) { + return function () { + return arguments[_toNaturalIndex(idx, arguments.length)]; + }; +} + +export default getArgAt; diff --git a/src/function/invoker.js b/src/function/invoker.js new file mode 100644 index 0000000..e3e8fb8 --- /dev/null +++ b/src/function/invoker.js @@ -0,0 +1,39 @@ +import partial from "../core/partial"; +import _argsTail from "../privates/_argsTail"; +import _invoker from "../privates/_invoker"; + +/** + * Builds a function that will invoke the given method name on any received object and + * return the result. If no method with such name is found the function will return + * undefined.
+ * Along with the method name it's possible to supply some arguments that will be bound to the + * method call. Further arguments can also be passed when the function is actually called, and + * they will be concatenated to the bound ones.
+ * Returning undefined is a behaviour meant to quickly create a case for + * {@link module:lamb.adapter|adapter} without the need to check for the existence of the + * desired method.
+ * See also {@link module:lamb.generic|generic} to create functions out of object methods. + * @example Basic polymorphism with invoker: + * var polySlice = _.invoker("slice"); + * + * polySlice([1, 2, 3, 4, 5], 1, 3) // => [2, 3] + * polySlice("Hello world", 1, 3) // => "el" + * + * @example With bound arguments: + * var substrFrom2 = _.invoker("substr", 2); + * substrFrom2("Hello world") // => "llo world" + * substrFrom2("Hello world", 5) // => "llo w" + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.invokerOn|invokerOn} + * @since 0.1.0 + * @param {String} methodName + * @param {...*} [boundArg] + * @returns {Function} + */ +function invoker (methodName) { + return partial(_invoker, [_argsTail.apply(null, arguments), methodName]); +} + +export default invoker; diff --git a/src/function/invokerOn.js b/src/function/invokerOn.js new file mode 100644 index 0000000..aeb0809 --- /dev/null +++ b/src/function/invokerOn.js @@ -0,0 +1,29 @@ +import __ from "../core/__"; +import partial from "../core/partial"; +import _invoker from "../privates/_invoker"; + +/** + * Accepts an object and builds a function expecting a method name, and optionally arguments, + * to call on such object. + * Like {@link module:lamb.invoker|invoker}, if no method with the given name is found the + * function will return undefined. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var arr = [1, 2, 3, 4, 5]; + * var invokerOnArr = _.invokerOn(arr); + * + * invokerOnArr("filter", isEven) // => [2, 4] + * invokerOnArr("slice", 1, 3) // => [2, 3] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.invoker|invoker} + * @since 0.15.0 + * @param {Object} target + * @returns {Function} + */ +function invokerOn (target) { + return partial(_invoker, [[], __, target]); +} + +export default invokerOn; diff --git a/src/function/mapArgs.js b/src/function/mapArgs.js new file mode 100644 index 0000000..5dfb659 --- /dev/null +++ b/src/function/mapArgs.js @@ -0,0 +1,33 @@ +import mapWith from "../core/mapWith"; +import list from "../array/list"; +import apply from "./apply"; +import pipe from "./pipe"; + +/** + * Builds a function that allows to map over the received arguments before applying them + * to the original one. + * @example + * var __ = _.__; + * var sumArray = _.reduceWith(_.sum); + * var sumArgs = _.compose(sumArray, _.list); + * + * sumArgs(1, 2, 3, 4, 5) // => 15 + * + * var square = _.partial(Math.pow, [__, 2]); + * var sumSquares = _.mapArgs(sumArgs, square); + * + * sumSquares(1, 2, 3, 4, 5) // => 55 + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.tapArgs|tapArgs} + * @since 0.3.0 + * @param {Function} fn + * @param {ListIteratorCallback} mapper + * @returns {Function} + */ +function mapArgs (fn, mapper) { + return pipe([list, mapWith(mapper), apply(fn)]); +} + +export default mapArgs; diff --git a/src/function/pipe.js b/src/function/pipe.js new file mode 100644 index 0000000..4afec45 --- /dev/null +++ b/src/function/pipe.js @@ -0,0 +1,39 @@ +import identity from "../core/identity"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; + +/** + * Creates a pipeline of functions, where each function consumes the result of the previous one. + * @example + * var __ = _.__; + * var square = _.partial(Math.pow, [__, 2]); + * var getMaxAndSquare = _.pipe([Math.max, square]); + * + * getMaxAndSquare(3, 5) // => 25 + * + * @memberof module:lamb + * @category Function + * @function + * @see {@link module:lamb.compose|compose} + * @since 0.1.0 + * @param {Function[]} functions + * @returns {Function} + */ +function pipe (functions) { + if (!Array.isArray(functions)) { + throw _makeTypeErrorFor(functions, "array"); + } + + var len = functions.length; + + return len ? function () { + var result = functions[0].apply(this, arguments); + + for (var i = 1; i < len; i++) { + result = functions[i].call(this, result); + } + + return result; + } : identity; +} + +export default pipe; diff --git a/src/function/tapArgs.js b/src/function/tapArgs.js new file mode 100644 index 0000000..ff9275f --- /dev/null +++ b/src/function/tapArgs.js @@ -0,0 +1,34 @@ +/** + * Builds a function that allows to "tap" into the arguments of the original one. + * This allows to extract simple values from complex ones, transform arguments or simply intercept them. + * If a "tapper" isn't found the argument is passed as it is. + * @example + * var someObject = {count: 5}; + * var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0]; + * var getDataAmount = _.tapArgs(_.sum, [_.getKey("count"), _.getKey("length")]); + * + * getDataAmount(someObject, someArrayData); // => 15 + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.mapArgs|mapArgs} + * @since 0.3.0 + * @param {Function} fn + * @param {Function[]} tappers + * @returns {Function} + */ +function tapArgs (fn, tappers) { + return function () { + var len = arguments.length; + var tappersLen = tappers.length; + var args = []; + + for (var i = 0; i < len; i++) { + args.push(i < tappersLen ? tappers[i](arguments[i]) : arguments[i]); + } + + return fn.apply(this, args); + }; +} + +export default tapArgs; diff --git a/src/function/throttle.js b/src/function/throttle.js new file mode 100644 index 0000000..dfacb6a --- /dev/null +++ b/src/function/throttle.js @@ -0,0 +1,37 @@ +/** + * Returns a function that will invoke the passed function at most once in the given timespan.
+ * The first call in this case happens as soon as the function is invoked; see also + * {@link module:lamb.debounce|debounce} for a different behaviour where the first call is delayed. + * @example + * var log = _.throttle(console.log.bind(console), 5000); + * + * log("Hi"); // console logs "Hi" + * log("Hi again"); // nothing happens + * // after five seconds + * log("Hello world"); // console logs "Hello world" + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.debounce|debounce} + * @since 0.1.0 + * @param {Function} fn + * @param {Number} timespan - Expressed in milliseconds. + * @returns {Function} + */ +function throttle (fn, timespan) { + var result; + var lastCall = 0; + + return function () { + var now = Date.now(); + + if (now - lastCall >= timespan) { + lastCall = now; + result = fn.apply(this, arguments); + } + + return result; + }; +} + +export default throttle; diff --git a/src/function/unary.js b/src/function/unary.js new file mode 100644 index 0000000..3cb92ca --- /dev/null +++ b/src/function/unary.js @@ -0,0 +1,24 @@ +/** + * Builds a function that passes only one argument to the given function.
+ * It's simply a shortcut for a common use case of {@link module:lamb.aritize|aritize}, + * exposed for convenience. + * @example + * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; + * + * _.map(weights, _.unary(parseInt)) // => [2, 10, 1, 7] + * + * @memberof module:lamb + * @category Function + * @see {@link module:lamb.aritize|aritize} + * @see {@link module:lamb.binary|binary} + * @since 0.10.0 + * @param {Function} fn + * @returns {Function} + */ +function unary (fn) { + return function (a) { + return fn.call(this, a); + }; +} + +export default unary; diff --git a/src/grouping.js b/src/grouping.js deleted file mode 100644 index ea766cf..0000000 --- a/src/grouping.js +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Transforms an array-like object in a lookup table with the keys generated by the provided - * iteratee, having as values the count of matches for the key. - * @example - * var persons = [ - * {"name": "Jane", "age": 12}, - * {"name": "John", "age": 40}, - * {"name": "Mario", "age": 17}, - * {"name": "Paolo", "age": 15} - * ]; - * var getAgeStatus = function (person) { return person.age >= 18 ? "adult" : "minor"; }; - * - * _.count(persons, getAgeStatus) // => {"adult": 1, "minor": 3} - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.21.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Object} - */ -var count = _groupWith(function (a) { - return a ? ++a : 1; -}); - -/** - * A curried version of {@link module:lamb.count|count} that uses the provided iteratee to - * build a function expecting the array-like object to act upon. - * @example - * var persons = [ - * {"name": "Jane", "city": "New York"}, - * {"name": "John", "city": "New York"}, - * {"name": "Mario", "city": "Rome"}, - * {"name": "Paolo"} - * ]; - * var getCityOrUnknown = _.adapter([_.getKey("city"), _.always("Unknown")]); - * var countByCity = _.countBy(getCityOrUnknown); - * - * countByCity(persons) // => {"New York": 2, "Rome": 1, "Unknown": 1} - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.count|count} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.21.0 - * @param {ListIteratorCallback} iteratee - * @returns {Function} - */ -var countBy = _curry2(count, true); - -/** - * Transforms an array-like object into a lookup table using the provided iteratee as a grouping - * criterion to generate keys and values. - * @example - * var persons = [ - * {"name": "Jane", "city": "New York"}, - * {"name": "John", "city": "New York"}, - * {"name": "Mario", "city": "Rome"}, - * {"name": "Paolo"} - * ]; - * var getCity = _.getKey("city"); - * var personsByCity = _.group(persons, getCity); - * - * // "personsByCity" holds: - * // { - * // "New York": [ - * // {"name": "Jane", "city": "New York"}, - * // {"name": "John", "city": "New York"} - * // ], - * // "Rome": [ - * // {"name": "Mario", "city": "Rome"} - * // ], - * // "undefined": [ - * // {"name": "Paolo"} - * // ] - * // } - * - * @example Adding a custom value for missing keys: - * - * var getCityOrUnknown = _.adapter([getCity, _.always("Unknown")]); - * - * var personsByCity = _.group(persons, getCityOrUnknown); - * - * // "personsByCity" holds: - * // { - * // "New York": [ - * // {"name": "Jane", "city": "New York"}, - * // {"name": "John", "city": "New York"} - * // ], - * // "Rome": [ - * // {"name": "Mario", "city": "Rome"} - * // ], - * // "Unknown": [ - * // {"name": "Paolo"} - * // ] - * // } - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.groupBy|groupBy} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.7.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Object} - */ -var group = _groupWith(function (a, b) { - if (!a) { - return [b]; - } - - a[a.length] = b; - - return a; -}); - -/** - * A curried version of {@link module:lamb.group|group} that uses the provided iteratee - * to build a function expecting the array-like object to act upon. - * @example - * var persons = [ - * {"name": "Jane", "age": 12}, - * {"name": "John", "age": 40}, - * {"name": "Mario", "age": 18}, - * {"name": "Paolo", "age": 15} - * ]; - * - * var getAgeStatus = function (person) { return person.age > 20 ? "over 20" : "under 20"; }; - * var groupByAgeStatus = _.groupBy(getAgeStatus); - * - * var personsByAgeStatus = groupByAgeStatus(persons); - * - * // "personsByAgeStatus" holds: - * // { - * // "under 20": [ - * // {"name": "Jane", "age": 12}, - * // {"name": "Mario", "age": 18}, - * // {"name": "Paolo", "age": 15} - * // ], - * // "over 20": [ - * // {"name": "John", "age": 40} - * // ] - * // } - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.group|group} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.index|index}, {@link module:lamb.indexBy|indexBy} - * @since 0.7.0 - * @param {ListIteratorCallback} iteratee - * @returns {Function} - */ -var groupBy = _curry2(group, true); - -/** - * Similar to {@link module:lamb.group|group}, but the generated lookup table will have - * only one element of the original array-like object for each value.
- * Should be used only when you're sure that your iteratee won't produce - * duplicate keys, otherwise only the last evaluated element will be in the result. - * @example - * var users = [ - * {id: 1, name: "John"}, - * {id: 2, name: "Jane"}, - * {id: 3, name: "Mario"}, - * {id: 4, name: "John"} - * ]; - * - * var indexedUsers = _.index(users, _.getKey("id")); - * - * // "indexedUsers" holds: - * // { - * // "1": {id: 1, name: "John"}, - * // "2": {id: 2, name: "Jane"}, - * // "3": {id: 3, name: "Mario"}, - * // "4": {id: 4, name: "John"} - * // } - * - * @example Result of an iteratee producing a duplicate key: - * var users = [ - * {id: 1, name: "John"}, - * {id: 2, name: "Jane"}, - * {id: 3, name: "Mario"}, - * {id: 4, name: "John"} - * ]; - * - * var indexedUsers = _.index(users, _.getKey("name")); - * - * // "indexedUsers" holds: - * // { - * // "John": {"id": 4, "name": "John"}, - * // "Jane": {"id": 2, "name": "Jane"}, - * // "Mario": {"id": 3, "name": "Mario"} - * // } - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.indexBy|indexBy} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @since 0.21.0 - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} iteratee - * @returns {Object} - */ -var index = _groupWith(function (a, b) { - return b; -}); - -/** - * A curried version of {@link module:lamb.index|index} that uses the provided iteratee - * to build a function expecting the array-like object to act upon. - * @example - * var users = [ - * {id: 1, name: "John"}, - * {id: 2, name: "Jane"}, - * {id: 3, name: "Mario"} - * ]; - * var indexByID = _.indexBy(_.getKey("id")); - * - * var indexedUsers = indexByID(users); - * - * // "indexedUsers" holds: - * // { - * // "1": {id: 1, name: "John"}, - * // "2": {id: 2, name: "Jane"}, - * // "3": {id: 3, name: "Mario"} - * // } - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.index|index} - * @see {@link module:lamb.count|count}, {@link module:lamb.countBy|countBy} - * @see {@link module:lamb.group|group}, {@link module:lamb.groupBy|groupBy} - * @since 0.21.0 - * @param {ListIteratorCallback} iteratee - * @returns {Function} - */ -var indexBy = _curry2(index, true); - -lamb.count = count; -lamb.countBy = countBy; -lamb.group = group; -lamb.groupBy = groupBy; -lamb.index = index; -lamb.indexBy = indexBy; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..1813f9b --- /dev/null +++ b/src/index.js @@ -0,0 +1,221 @@ +export { default as __ } from "./core/__"; +export { default as always } from "./core/always"; +export { default as areSVZ } from "./core/areSVZ"; +export { default as binary } from "./core/binary"; +export { default as clamp } from "./core/clamp"; +export { default as clampWithin } from "./core/clampWithin"; +export { default as compose } from "./core/compose"; +export { default as forEach } from "./core/forEach"; +export { default as generic } from "./core/generic"; +export { default as identity } from "./core/identity"; +export { default as isNil } from "./core/isNil"; +export { default as isNull } from "./core/isNull"; +export { default as isSVZ } from "./core/isSVZ"; +export { default as isUndefined } from "./core/isUndefined"; +export { default as map } from "./core/map"; +export { default as mapWith } from "./core/mapWith"; +export { default as partial } from "./core/partial"; +export { default as partialRight } from "./core/partialRight"; +export { default as reduce } from "./core/reduce"; +export { default as reduceWith } from "./core/reduceWith"; +export { default as slice } from "./core/slice"; +export { default as sliceAt } from "./core/sliceAt"; +export { default as type } from "./core/type"; + +// ARRAY + +export { default as append } from "./array/append"; +export { default as appendTo } from "./array/appendTo"; +export { default as contains } from "./array/contains"; +export { default as count } from "./array/count"; +export { default as countBy } from "./array/countBy"; +export { default as difference } from "./array/difference"; +export { default as drop } from "./array/drop"; +export { default as dropFrom } from "./array/dropFrom"; +export { default as dropWhile } from "./array/dropWhile"; +export { default as every } from "./array/every"; +export { default as everyIn } from "./array/everyIn"; +export { default as filter } from "./array/filter"; +export { default as filterWith } from "./array/filterWith"; +export { default as find } from "./array/find"; +export { default as findIndex } from "./array/findIndex"; +export { default as findWhere } from "./array/findWhere"; +export { default as findIndexWhere } from "./array/findIndexWhere"; +export { default as flatMap } from "./array/flatMap"; +export { default as flatMapWith } from "./array/flatMapWith"; +export { default as flatten } from "./array/flatten"; +export { default as getAt } from "./array/getAt"; +export { default as getIndex } from "./array/getIndex"; +export { default as group } from "./array/group"; +export { default as groupBy } from "./array/groupBy"; +export { default as head } from "./array/head"; +export { default as index } from "./array/index"; +export { default as indexBy } from "./array/indexBy"; +export { default as init } from "./array/init"; +export { default as insert } from "./array/insert"; +export { default as insertAt } from "./array/insertAt"; +export { default as intersection } from "./array/intersection"; +export { default as isIn } from "./array/isIn"; +export { default as last } from "./array/last"; +export { default as list } from "./array/list"; +export { default as partition } from "./array/partition"; +export { default as partitionWith } from "./array/partitionWith"; +export { default as pluck } from "./array/pluck"; +export { default as pluckKey } from "./array/pluckKey"; +export { default as pull } from "./array/pull"; +export { default as pullFrom } from "./array/pullFrom"; +export { default as reduceRight } from "./array/reduceRight"; +export { default as reduceRightWith } from "./array/reduceRightWith"; +export { default as reverse } from "./array/reverse"; +export { default as rotate } from "./array/rotate"; +export { default as rotateBy } from "./array/rotateBy"; +export { default as setAt } from "./array/setAt"; +export { default as setIndex } from "./array/setIndex"; +export { default as shallowFlatten } from "./array/shallowFlatten"; +export { default as some } from "./array/some"; +export { default as someIn } from "./array/someIn"; +export { default as sort } from "./array/sort"; +export { default as sortedInsert } from "./array/sortedInsert"; +export { default as sorter } from "./array/sorter"; +export { default as sorterDesc } from "./array/sorterDesc"; +export { default as sortWith } from "./array/sortWith"; +export { default as tail } from "./array/tail"; +export { default as take } from "./array/take"; +export { default as takeFrom } from "./array/takeFrom"; +export { default as takeWhile } from "./array/takeWhile"; +export { default as transpose } from "./array/transpose"; +export { default as union } from "./array/union"; +export { default as unionBy } from "./array/unionBy"; +export { default as uniques } from "./array/uniques"; +export { default as uniquesBy } from "./array/uniquesBy"; +export { default as updateAt } from "./array/updateAt"; +export { default as updateIndex } from "./array/updateIndex"; +export { default as zip } from "./array/zip"; +export { default as zipWithIndex } from "./array/zipWithIndex"; + +// FUNCTION + +export { default as application } from "./function/application"; +export { default as apply } from "./function/apply"; +export { default as applyTo } from "./function/applyTo"; +export { default as asPartial } from "./function/asPartial"; +export { default as aritize } from "./function/aritize"; +export { default as collect } from "./function/collect"; +export { default as curry } from "./function/curry"; +export { default as curryable } from "./function/curryable"; +export { default as curryableRight } from "./function/curryableRight"; +export { default as curryRight } from "./function/curryRight"; +export { default as debounce } from "./function/debounce"; +export { default as flip } from "./function/flip"; +export { default as getArgAt } from "./function/getArgAt"; +export { default as invoker } from "./function/invoker"; +export { default as invokerOn } from "./function/invokerOn"; +export { default as mapArgs } from "./function/mapArgs"; +export { default as pipe } from "./function/pipe"; +export { default as tapArgs } from "./function/tapArgs"; +export { default as throttle } from "./function/throttle"; +export { default as unary } from "./function/unary"; + +// LOGIC + +export { default as adapter } from "./logic/adapter"; +export { default as allOf } from "./logic/allOf"; +export { default as anyOf } from "./logic/anyOf"; +export { default as areSame } from "./logic/areSame"; +export { default as case } from "./logic/case"; +export { default as condition } from "./logic/condition"; +export { default as gt } from "./logic/gt"; +export { default as gte } from "./logic/gte"; +export { default as is } from "./logic/is"; +export { default as isGT } from "./logic/isGT"; +export { default as isGTE } from "./logic/isGTE"; +export { default as isLT } from "./logic/isLT"; +export { default as isLTE } from "./logic/isLTE"; +export { default as lt } from "./logic/lt"; +export { default as lte } from "./logic/lte"; +export { default as not } from "./logic/not"; +export { default as unless } from "./logic/unless"; +export { default as when } from "./logic/when"; + +// MATH + +export { default as add } from "./math/add"; +export { default as deduct } from "./math/deduct"; +export { default as divide } from "./math/divide"; +export { default as divideBy } from "./math/divideBy"; +export { default as generate } from "./math/generate"; +export { default as isFinite } from "./math/isFinite"; +export { default as isInteger } from "./math/isInteger"; +export { default as isSafeInteger } from "./math/isSafeInteger"; +export { default as modulo } from "./math/modulo"; +export { default as multiply } from "./math/multiply"; +export { default as multiplyBy } from "./math/multiplyBy"; +export { default as randomInt } from "./math/randomInt"; +export { default as range } from "./math/range"; +export { default as remainder } from "./math/remainder"; +export { default as subtract } from "./math/subtract"; +export { default as sum } from "./math/sum"; + +// OBJECT + +export { default as checker } from "./object/checker"; +export { default as enumerables } from "./object/enumerables"; +export { default as fromPairs } from "./object/fromPairs"; +export { default as getIn } from "./object/getIn"; +export { default as getKey } from "./object/getKey"; +export { default as getPath } from "./object/getPath"; +export { default as getPathIn } from "./object/getPathIn"; +export { default as has } from "./object/has"; +export { default as hasKey } from "./object/hasKey"; +export { default as hasOwn } from "./object/hasOwn"; +export { default as hasOwnKey } from "./object/hasOwnKey"; +export { default as hasKeyValue } from "./object/hasKeyValue"; +export { default as hasPathValue } from "./object/hasPathValue"; +export { default as immutable } from "./object/immutable"; +export { default as keys } from "./object/keys"; +export { default as keySatisfies } from "./object/keySatisfies"; +export { default as make } from "./object/make"; +export { default as mapValues } from "./object/mapValues"; +export { default as mapValuesWith } from "./object/mapValuesWith"; +export { default as merge } from "./object/merge"; +export { default as mergeOwn } from "./object/mergeOwn"; +export { default as ownPairs } from "./object/ownPairs"; +export { default as ownValues } from "./object/ownValues"; +export { default as pairs } from "./object/pairs"; +export { default as pathExists } from "./object/pathExists"; +export { default as pathExistsIn } from "./object/pathExistsIn"; +export { default as pathSatisfies } from "./object/pathSatisfies"; +export { default as pick } from "./object/pick"; +export { default as pickIf } from "./object/pickIf"; +export { default as pickKeys } from "./object/pickKeys"; +export { default as rename } from "./object/rename"; +export { default as renameKeys } from "./object/renameKeys"; +export { default as renameWith } from "./object/renameWith"; +export { default as setIn } from "./object/setIn"; +export { default as setKey } from "./object/setKey"; +export { default as setPath } from "./object/setPath"; +export { default as setPathIn } from "./object/setPathIn"; +export { default as skip } from "./object/skip"; +export { default as skipIf } from "./object/skipIf"; +export { default as skipKeys } from "./object/skipKeys"; +export { default as tear } from "./object/tear"; +export { default as tearOwn } from "./object/tearOwn"; +export { default as updateIn } from "./object/updateIn"; +export { default as updateKey } from "./object/updateKey"; +export { default as updatePath } from "./object/updatePath"; +export { default as updatePathIn } from "./object/updatePathIn"; +export { default as validate } from "./object/validate"; +export { default as validateWith } from "./object/validateWith"; +export { default as values } from "./object/values"; + +// STRING + +export { default as padLeft } from "./string/padLeft"; +export { default as padRight } from "./string/padRight"; +export { default as repeat } from "./string/repeat"; +export { default as testWith } from "./string/testWith"; + +// TYPE + +export { default as isInstanceOf } from "./type/isInstanceOf"; +export { default as isType } from "./type/isType"; diff --git a/src/logic.js b/src/logic.js deleted file mode 100644 index 178f7bf..0000000 --- a/src/logic.js +++ /dev/null @@ -1,621 +0,0 @@ -/** - * Accepts a series of functions and builds a function that applies the received - * arguments to each one and returns the first non-undefined value.
- * Meant to work in synergy with {@link module:lamb.case|case} and - * {@link module:lamb.invoker|invoker}, can be useful as a strategy pattern for functions, - * to mimic conditional logic or pattern matching, and also to build polymorphic functions. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var filterString = _.compose(_.invoker("join", ""), _.filter); - * var filterAdapter = _.adapter([ - * _.invoker("filter"), - * _.case(_.isType("String"), filterString) - * ]); - * - * filterAdapter([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] - * filterAdapter("123456", isEven) // => "246" - * filterAdapter({}, isEven) // => undefined - * - * // by its nature is composable - * var filterWithDefault = _.adapter([filterAdapter, _.always("Not implemented")]); - * - * filterWithDefault([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] - * filterWithDefault("123456", isEven) // => "246" - * filterWithDefault({}, isEven) // => "Not implemented" - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.case|case} - * @see {@link module:lamb.invoker|invoker} - * @since 0.6.0 - * @param {Function[]} functions - * @returns {Function} - */ -function adapter (functions) { - if (!Array.isArray(functions)) { - throw _makeTypeErrorFor(functions, "array"); - } - - return function () { - var len = functions.length; - var result; - - for (var i = 0; i < len; i++) { - result = functions[i].apply(this, arguments); - - if (!isUndefined(result)) { - break; - } - } - - return result; - }; -} - -/** - * Accepts an array of predicates and builds a new one that returns true if they are all satisfied - * by the same arguments. The functions in the array will be applied one at a time until a - * false value is produced, which is returned immediately. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var isPositiveEven = _.allOf([isEven, _.isGT(0)]); - * - * isPositiveEven(-2) // => false - * isPositiveEven(11) // => false - * isPositiveEven(6) // => true - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.anyOf|anyOf} - * @since 0.1.0 - * @param {Function[]} predicates - * @returns {Function} - */ -var allOf = _checkPredicates(true); - -/** - * Accepts an array of predicates and builds a new one that returns true if at least one of them is - * satisfied by the received arguments. The functions in the array will be applied one at a time - * until a true value is produced, which is returned immediately. - * @example - * var users = [ - * {id: 1, name: "John", group: "guest"}, - * {id: 2, name: "Jane", group: "root"}, - * {id: 3, name: "Mario", group: "admin"} - * ]; - * var isInGroup = _.partial(_.hasKeyValue, ["group"]); - * var isSuperUser = _.anyOf([isInGroup("admin"), isInGroup("root")]); - * - * isSuperUser(users[0]) // => false - * isSuperUser(users[1]) // => true - * isSuperUser(users[2]) // => true - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.allOf|allOf} - * @since 0.1.0 - * @param {Function[]} predicates - * @returns {Function} - */ -var anyOf = _checkPredicates(false); - -/** - * Verifies that the two supplied values are the same value using the "SameValue" comparison.
- * Note that this doesn't behave as the strict equality operator, but rather as a shim of ES6's - * [Object.is]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is}. - * Differences are that 0 and -0 aren't the same value and, finally, - * NaN is equal to itself.
- * See also {@link module:lamb.is|is} for a curried version building a predicate and - * {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} to perform a "SameValueZero" - * comparison. - * @example - * var testObject = {}; - * - * _.areSame({}, testObject) // => false - * _.areSame(testObject, testObject) // => true - * _.areSame("foo", "foo") // => true - * _.areSame(0, -0) // => false - * _.areSame(0 / 0, NaN) // => true - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.is|is} - * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} - * @since 0.50.0 - * @param {*} a - * @param {*} b - * @returns {Boolean} - */ -function areSame (a, b) { - return a === 0 && b === 0 ? 1 / a === 1 / b : areSVZ(a, b); -} - -/** - * Verifies that the two supplied values are the same value using the "SameValueZero" comparison.
- * With this comparison NaN is equal to itself, but 0 and -0 are - * considered the same value.
- * See also {@link module:lamb.isSVZ|isSVZ} for a curried version building a predicate and - * {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} to perform a "SameValue" comparison. - * @example - * var testObject = {}; - * - * _.areSVZ({}, testObject) // => false - * _.areSVZ(testObject, testObject) // => true - * _.areSVZ("foo", "foo") // => true - * _.areSVZ(0, -0) // => true - * _.areSVZ(0 / 0, NaN) // => true - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.isSVZ|isSVZ} - * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} - * @since 0.50.0 - * @param {*} a - * @param {*} b - * @returns {Boolean} - */ -function areSVZ (a, b) { - return a !== a ? b !== b : a === b; // eslint-disable-line no-self-compare -} - -/** - * Builds a case for {@link module:lamb.adapter|adapter}.
- * The function will apply the received arguments to fn if the predicate is satisfied - * with the same arguments, otherwise will return undefined.
- * See also {@link module:lamb.condition|condition} to build a condition with two branching functions - * and {@link module:lamb.unless|unless} and {@link module:lamb.when|when} where one of the branches - * is the identity function. - * @example - * var halveIfNumber = _.case(_.isType("Number"), _.divideBy(2)); - * - * halveIfNumber(2) // => 1 - * halveIfNumber("2") // => undefined - * - * @alias module:lamb.case - * @category Logic - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.condition|condition} - * @see {@link module:lamb.unless|unless} - * @see {@link module:lamb.when|when} - * @since 0.51.0 - * @param {Function} predicate - * @param {Function} fn - * @returns {Function} - */ -function case_ (predicate, fn) { - return function () { - return predicate.apply(this, arguments) ? fn.apply(this, arguments) : void 0; - }; -} - -/** - * Builds a function that will apply the received arguments to trueFn, - * if the predicate is satisfied with the same arguments, or to falseFn otherwise.
- * Although you can use other conditions as trueFn or falseFn, - * it's probably better to use {@link module:lamb.adapter|adapter} to build more complex behaviours.
- * See also {@link module:lamb.unless|unless} and {@link module:lamb.when|when} as they are - * shortcuts to common use cases. - * @example - * var isEven = function (n) { return n % 2 === 0}; - * var halveEvenAndDoubleOdd = _.condition(isEven, _.divideBy(2), _.multiplyBy(2)); - * - * halveEvenAndDoubleOdd(5) // => 10 - * halveEvenAndDoubleOdd(6) // => 3 - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.unless|unless} - * @see {@link module:lamb.when|when} - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.case|case} - * @since 0.2.0 - * @param {Function} predicate - * @param {Function} trueFn - * @param {Function} falseFn - * @returns {Function} - */ -function condition (predicate, trueFn, falseFn) { - return function () { - return (predicate.apply(this, arguments) ? trueFn : falseFn).apply(this, arguments); - }; -} - -/** - * Verifies that the first given value is greater than the second.
- * Wraps the native > operator within a function. - * @example - * var pastDate = new Date(2010, 2, 12); - * var today = new Date(); - * - * _.gt(today, pastDate) // => true - * _.gt(pastDate, today) // => false - * _.gt(3, 4) // => false - * _.gt(3, 3) // => false - * _.gt(3, 2) // => true - * _.gt(0, -0) // => false - * _.gt(-0, 0) // => false - * _.gt("a", "A") // => true - * _.gt("b", "a") // => true - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.gte|gte} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} - */ -function gt (a, b) { - return a > b; -} - -/** - * Verifies that the first given value is greater than or equal to the second. - * Regarding equality, beware that this is simply a wrapper for the native - * >= operator, so -0 === 0. - * @example - * _.gte(3, 4) // => false - * _.gte(3, 3) // => true - * _.gte(3, 2) // => true - * _.gte(0, -0) // => true - * _.gte(-0, 0) // => true - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.gt|gt} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} - */ -function gte (a, b) { - return a >= b; -} - -/** - * A curried version of {@link module:lamb.areSame|areSame}.
- * Accepts a value and builds a predicate that checks whether the value - * and the one received by the predicate are the same using the "SameValue" - * comparison.
- * See also {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} - * to perform a "SameValueZero" comparison. - * @example - * var john = {name: "John", surname: "Doe"}; - * var isJohn = _.is(john); - * var isNegativeZero = _.is(-0); - * var isReallyNaN = _.is(NaN); - * - * isJohn(john) // => true - * isJohn({name: "John", surname: "Doe"}) // => false - * - * isNegativeZero(0) // => false - * isNegativeZero(-0) // => true - * - * isNaN(NaN) // => true - * isNaN("foo") // => true - * - * isReallyNaN(NaN) // => true - * isReallyNaN("foo") // => false - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.areSame|areSame} - * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} - * @since 0.1.0 - * @param {*} value - * @returns {Function} - */ -var is = _curry2(areSame); - -/** - * A right curried version of {@link module:lamb.gt|gt}.
- * Accepts a value and builds a predicate that checks whether the value - * is greater than the one received by the predicate. - * @example - * var isGreaterThan5 = _.isGT(5); - * - * isGreaterThan5(3) // => false - * isGreaterThan5(5) // => false - * isGreaterThan5(7) // => true - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value - * @returns {Function} - */ -var isGT = _curry2(gt, true); - -/** - * A right curried version of {@link module:lamb.gte|gte}.
- * Accepts a value and builds a predicate that checks whether the value - * is greater than or equal to the one received by the predicate. - * @example - * var isPositiveOrZero = _.isGTE(0); - * - * isPositiveOrZero(-3) // => false - * isPositiveOrZero(-0) // => true - * isPositiveOrZero(0) // => true - * isPositiveOrZero(5) // => true - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.isGT|isGT} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value - * @returns {Function} - */ -var isGTE = _curry2(gte, true); - -/** - * A right curried version of {@link module:lamb.lt|lt}.
- * Accepts a value and builds a predicate that checks whether the value - * is less than the one received by the predicate. - * @example - * var isLessThan5 = _.isLT(5); - * - * isLessThan5(7) // => false - * isLessThan5(5) // => false - * isLessThan5(3) // => true - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value - * @returns {Function} - */ -var isLT = _curry2(lt, true); - -/** - * A right curried version of {@link module:lamb.lte|lte}.
- * Accepts a value and builds a predicate that checks whether the value - * is less than or equal to the one received by the predicate. - * @example - * var isNegativeOrZero = _.isLTE(0); - * - * isNegativeOrZero(5) // => false - * isNegativeOrZero(-0) // => true - * isNegativeOrZero(0) // => true - * isNegativeOrZero(-3) // => true - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.isLT|isLT} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @since 0.1.0 - * @param {Number|String|Date|Boolean} value - * @returns {Function} - */ -var isLTE = _curry2(lte, true); - -/** - * A curried version of {@link module:lamb.areSVZ|areSVZ}.
- * Accepts a value and builds a predicate that checks whether the value - * and the one received by the predicate are the same using the "SameValueZero" - * comparison.
- * See also {@link module:lamb.areSame|areSame} and {@link module:lamb.is|is} - * to perform a "SameValue" comparison. - * @example - * var john = {name: "John", surname: "Doe"}; - * var isJohn = _.isSVZ(john); - * var isZero = _.isSVZ(0); - * var isReallyNaN = _.isSVZ(NaN); - * - * isJohn(john) // => true - * isJohn({name: "John", surname: "Doe"}) // => false - * - * isZero(0) // => true - * isZero(-0) // => true - * - * isNaN(NaN) // => true - * isNaN("foo") // => true - * - * isReallyNaN(NaN) // => true - * isReallyNaN("foo") // => false - * - * @memberof module:lamb - * @category Logic - * @function - * @see {@link module:lamb.areSVZ|areSVZ} - * @see {@link module:lamb.areSame|areSame}, {@link module:lamb.is|is} - * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} - * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} - * @since 0.1.0 - * @param {*} value - * @returns {Function} - */ -var isSVZ = _curry2(areSVZ); - -/** - * Verifies that the first given value is less than the second.
- * Wraps the native < operator within a function. - * @example - * var pastDate = new Date(2010, 2, 12); - * var today = new Date(); - * - * _.lt(today, pastDate) // => false - * _.lt(pastDate, today) // => true - * _.lt(3, 4) // => true - * _.lt(3, 3) // => false - * _.lt(3, 2) // => false - * _.lt(0, -0) // => false - * _.lt(-0, 0) // => false - * _.lt("a", "A") // => false - * _.lt("a", "b") // => true - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.lte|lte} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} - */ -function lt (a, b) { - return a < b; -} - -/** - * Verifies that the first given value is less than or equal to the second. - * Regarding equality, beware that this is simply a wrapper for the native - * <= operator, so -0 === 0. - * @example - * _.lte(3, 4) // => true - * _.lte(3, 3) // => true - * _.lte(3, 2) // => false - * _.lte(0, -0) // => true - * _.lte(-0, 0) // => true - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.lt|lt} - * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} - * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} - * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} - * @since 0.50.0 - * @param {Number|String|Date|Boolean} a - * @param {Number|String|Date|Boolean} b - * @returns {Boolean} - */ -function lte (a, b) { - return a <= b; -} - -/** - * Returns a predicate that negates the given one. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var isOdd = _.not(isEven); - * - * isOdd(5) // => true - * isOdd(4) // => false - * - * @memberof module:lamb - * @category Logic - * @since 0.1.0 - * @param {Function} predicate - * @returns {Function} - */ -function not (predicate) { - return function () { - return !predicate.apply(this, arguments); - }; -} - -/** - * Builds a unary function that will check its argument against the given predicate. - * If the predicate isn't satisfied, the provided fn function will be - * applied to the same value. The received argument is returned as it is otherwise.
- * See {@link module:lamb.when|when} for the opposite behaviour.
- * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, - * where its trueFn parameter is the [identity function]{@link module:lamb.identity}. - * @example - * var isEven = function (n) { return n % 2 === 0}; - * var halveUnlessIsEven = _.unless(isEven, _.divideBy(2)); - * - * halveUnlessIsEven(5) // => 2.5 - * halveUnlessIsEven(6) // => 6 - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.condition|condition} - * @see {@link module:lamb.when|when} - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.case|case} - * @since 0.42.0 - * @param {Function} predicate - * @param {Function} fn - * @returns {Function} - */ -function unless (predicate, fn) { - return function (value) { - return predicate.call(this, value) ? value : fn.call(this, value); - }; -} - -/** - * Builds a unary function that will check its argument against the given predicate. - * If the predicate is satisfied, the provided fn function will be - * applied to the same value. The received argument is returned as it is otherwise.
- * See {@link module:lamb.unless|unless} for the opposite behaviour.
- * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, - * where its falseFn parameter is the [identity function]{@link module:lamb.identity}. - * @example - * var isEven = function (n) { return n % 2 === 0; }; - * var halveIfEven = _.when(isEven, _.divideBy(2)); - * - * halveIfEven(5) // => 5 - * halveIfEven(6) // => 3 - * - * @memberof module:lamb - * @category Logic - * @see {@link module:lamb.condition|condition} - * @see {@link module:lamb.unless|unless} - * @see {@link module:lamb.adapter|adapter} - * @see {@link module:lamb.case|case} - * @since 0.42.0 - * @param {Function} predicate - * @param {Function} fn - * @returns {Function} - */ -function when (predicate, fn) { - return function (value) { - return predicate.call(this, value) ? fn.call(this, value) : value; - }; -} - -lamb.adapter = adapter; -lamb.allOf = allOf; -lamb.anyOf = anyOf; -lamb.areSame = areSame; -lamb.areSVZ = areSVZ; -lamb.case = case_; -lamb.condition = condition; -lamb.gt = gt; -lamb.gte = gte; -lamb.is = is; -lamb.isGT = isGT; -lamb.isGTE = isGTE; -lamb.isLT = isLT; -lamb.isLTE = isLTE; -lamb.isSVZ = isSVZ; -lamb.lt = lt; -lamb.lte = lte; -lamb.not = not; -lamb.unless = unless; -lamb.when = when; diff --git a/src/logic/__tests__/_commons.js b/src/logic/__tests__/_commons.js new file mode 100644 index 0000000..085c7ec --- /dev/null +++ b/src/logic/__tests__/_commons.js @@ -0,0 +1,51 @@ +import * as lamb from "../.."; + +var isEven = function (n) { return n % 2 === 0; }; +var isGreaterThanTwo = lamb.isGT(2); +var isLessThanTen = lamb.isLT(10); + +// to check "truthy" and "falsy" values returned by predicates +var hasEvens = function (array) { return ~lamb.findIndex(array, isEven); }; +var isVowel = function (char) { return ~"aeiouAEIOU".indexOf(char); }; + +function Foo (value) { + this.value = value; +} + +Foo.prototype = { + _safeValue: 99, + value: 0, + getSafeValue: function () { + return this._safeValue; + }, + getValue: function () { + return typeof this.value === "number" ? this.value : void 0; + }, + isEven: function () { + return this.value % 2 === 0; + }, + isPositive: function () { + return this.value > 0; + } +}; +Foo.prototype.getIfPositiveOrGetSafe = lamb.condition( + Foo.prototype.isPositive, + Foo.prototype.getValue, + Foo.prototype.getSafeValue +); +Foo.prototype.getIfPositiveOrUndefined = lamb.case(Foo.prototype.isPositive, Foo.prototype.getValue); + +Foo.prototype.getWhenPositiveOrElse = lamb.when(Foo.prototype.isPositive, Foo.prototype.getValue); +Foo.prototype.getUnlessIsPositiveOrElse = lamb.unless(Foo.prototype.isPositive, Foo.prototype.getValue); +Foo.prototype.isOdd = lamb.not(Foo.prototype.isEven); +Foo.prototype.isPositiveEven = lamb.allOf([Foo.prototype.isEven, Foo.prototype.isPositive]); +Foo.prototype.isPositiveOrEven = lamb.anyOf([Foo.prototype.isEven, Foo.prototype.isPositive]); + +export { + Foo, + hasEvens, + isEven, + isGreaterThanTwo, + isLessThanTen, + isVowel +}; diff --git a/src/logic/__tests__/adapter.spec.js b/src/logic/__tests__/adapter.spec.js new file mode 100644 index 0000000..30cc1e6 --- /dev/null +++ b/src/logic/__tests__/adapter.spec.js @@ -0,0 +1,57 @@ +import * as lamb from "../.."; +import { nonArrayLikes, nonFunctions } from "../../__tests__/commons"; +import { Foo, isEven } from "./_commons"; + +describe("adapter", function () { + it("should accept an array of functions and build another function that calls them one by one until a non-undefined value is returned", function () { + var filterString = lamb.case( + lamb.isType("String"), + lamb.compose(lamb.invoker("join", ""), lamb.filter) + ); + var filterAdapter = lamb.adapter([lamb.invoker("filter"), filterString]); + + expect(filterAdapter([1, 2, 3, 4, 5, 6], isEven)).toEqual([2, 4, 6]); + expect(filterAdapter("123456", isEven)).toBe("246"); + expect(filterAdapter({}, isEven)).toBeUndefined(); + + var filterWithDefault = lamb.adapter([filterAdapter, lamb.always("Not implemented")]); + + expect(filterWithDefault([1, 2, 3, 4, 5, 6], isEven)).toEqual([2, 4, 6]); + expect(filterWithDefault("123456", isEven)).toBe("246"); + expect(filterWithDefault({}, isEven)).toBe("Not implemented"); + }); + + it("should not modify the functions' context", function () { + var obj = { value: 5, getValue: lamb.adapter([Foo.prototype.getValue]) }; + + expect(obj.getValue()).toBe(5); + }); + + it("should not throw an exception if some parameter isn't a function if a previous function returned a non-undefined value", function () { + nonFunctions.forEach(function (value) { + expect(lamb.adapter([isEven, value])(2)).toBe(true); + }); + }); + + it("should build a function returning an exception if a parameter isn't a function and no previous function returned a non-unefined value", function () { + var fn = function (v) { return isEven(v) ? true : void 0; }; + + nonFunctions.forEach(function (value) { + expect(function () { lamb.adapter([value, fn])(2); }).toThrow(); + expect(function () { lamb.adapter([fn, value])(3); }).toThrow(); + }); + }); + + it("should return `undefined` if it receives an empty array", function () { + expect(lamb.adapter([])()).toBeUndefined(); + expect(lamb.adapter([])(1, 2, 3)).toBeUndefined(); + }); + + it("should throw an exception if the received parameter isn't an array", function () { + nonArrayLikes.forEach(function (value) { + expect(function () { lamb.adapter(value); }).toThrow(); + }); + + expect(lamb.adapter).toThrow(); + }); +}); diff --git a/src/logic/__tests__/allOf.spec.js b/src/logic/__tests__/allOf.spec.js new file mode 100644 index 0000000..d492e78 --- /dev/null +++ b/src/logic/__tests__/allOf.spec.js @@ -0,0 +1,57 @@ +import * as lamb from "../.."; +import { nonArrayLikes, nonFunctions, valuesList } from "../../__tests__/commons"; +import { + Foo, + hasEvens, + isEven, + isGreaterThanTwo, + isLessThanTen, + isVowel +} from "./_commons"; + +describe("allOf", function () { + it("should return true if all the given predicates are satisfied", function () { + var check = lamb.allOf([isEven, isGreaterThanTwo, isLessThanTen]); + + expect([4, 6, 8].map(check)).toEqual([true, true, true]); + }); + + it("should return false if one the given predicates isn't satisfied", function () { + var check = lamb.allOf([isEven, isGreaterThanTwo, isLessThanTen]); + + expect([2, 3, 16].map(check)).toEqual([false, false, false]); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.allOf([hasEvens])([1, 3, 5, 7])).toBe(false); + expect(lamb.allOf([hasEvens])([1, 2, 5, 7])).toBe(true); + expect(lamb.allOf([isVowel])("b")).toBe(false); + expect(lamb.allOf([isVowel])("a")).toBe(true); + }); + + it("should keep the predicate context", function () { + expect(new Foo(6).isPositiveEven()).toBe(true); + expect(new Foo(5).isPositiveEven()).toBe(false); + }); + + it("should return `true` for any value if not supplied with predicates because of vacuous truth", function () { + valuesList.forEach(function (value) { + expect(lamb.allOf([])(value)).toBe(true); + }); + }); + + it("should throw an exception if the received parameter isn't an array", function () { + nonArrayLikes.forEach(function (value) { + expect(function () { lamb.allOf(value); }).toThrow(); + }); + + expect(lamb.allOf).toThrow(); + }); + + it("should build a function returning an exception if any given predicate isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.allOf([value, isEven])(2); }).toThrow(); + expect(function () { lamb.allOf([isEven, value])(2); }).toThrow(); + }); + }); +}); diff --git a/src/logic/__tests__/anyOf.spec.js b/src/logic/__tests__/anyOf.spec.js new file mode 100644 index 0000000..1fbeea2 --- /dev/null +++ b/src/logic/__tests__/anyOf.spec.js @@ -0,0 +1,63 @@ +import * as lamb from "../.."; +import { nonArrayLikes, nonFunctions, valuesList } from "../../__tests__/commons"; +import { + Foo, + hasEvens, + isEven, + isGreaterThanTwo, + isLessThanTen, + isVowel +} from "./_commons"; + +describe("anyOf", function () { + it("should return true if at least one of the given predicates is satisfied", function () { + var check = lamb.anyOf([isEven, isGreaterThanTwo, isLessThanTen]); + + expect([33, 44, 5].map(check)).toEqual([true, true, true]); + }); + + it("should return false if none of the given predicates is satisfied", function () { + var check = lamb.anyOf([isEven, isLessThanTen]); + + expect([33, 35, 55].map(check)).toEqual([false, false, false]); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.anyOf([hasEvens])([1, 3, 5, 7])).toBe(false); + expect(lamb.anyOf([hasEvens])([1, 2, 5, 7])).toBe(true); + expect(lamb.anyOf([isVowel])("b")).toBe(false); + expect(lamb.anyOf([isVowel])("a")).toBe(true); + }); + + it("should keep the predicate context", function () { + expect(new Foo(5).isPositiveOrEven()).toBe(true); + expect(new Foo(-5).isPositiveOrEven()).toBe(false); + }); + + it("should return `false` for any value if not supplied with predicates", function () { + valuesList.forEach(function (value) { + expect(lamb.anyOf([])(value)).toBe(false); + }); + }); + + it("should throw an exception if the received parameter isn't an array", function () { + nonArrayLikes.forEach(function (value) { + expect(function () { lamb.anyOf(value); }).toThrow(); + }); + + expect(lamb.anyOf).toThrow(); + }); + + it("should not throw an exception if some predicate isn't a function if a previous predicate satisfies the condition", function () { + nonFunctions.forEach(function (value) { + expect(lamb.anyOf([isEven, value])(2)).toBe(true); + }); + }); + + it("should build a function returning an exception if a predicate isn't a function and the condition isn't satisfied yet", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.anyOf([value, isEven])(2); }).toThrow(); + expect(function () { lamb.anyOf([isEven, value])(3); }).toThrow(); + }); + }); +}); diff --git a/src/logic/__tests__/areSame.spec.js b/src/logic/__tests__/areSame.spec.js new file mode 100644 index 0000000..a7ce581 --- /dev/null +++ b/src/logic/__tests__/areSame.spec.js @@ -0,0 +1,21 @@ +import * as lamb from "../.."; + +describe("areSame / is", function () { + it("should verify the equality of two values", function () { + var o = { foo: "bar" }; + + expect(lamb.areSame(o, o)).toBe(true); + expect(lamb.areSame(o, { foo: "bar" })).toBe(false); + expect(lamb.areSame(42, 42)).toBe(true); + expect(lamb.areSame([], [])).toBe(false); + expect(lamb.areSame(0, -0)).toBe(false); + expect(lamb.areSame(NaN, NaN)).toBe(true); + + expect(lamb.is(o)(o)).toBe(true); + expect(lamb.is(o)({ foo: "bar" })).toBe(false); + expect(lamb.is(42)(42)).toBe(true); + expect(lamb.is([])([])).toBe(false); + expect(lamb.is(0)(-0)).toBe(false); + expect(lamb.is(NaN)(NaN)).toBe(true); + }); +}); diff --git a/src/logic/__tests__/comparisons.spec.js b/src/logic/__tests__/comparisons.spec.js new file mode 100644 index 0000000..68370b4 --- /dev/null +++ b/src/logic/__tests__/comparisons.spec.js @@ -0,0 +1,91 @@ +import * as lamb from "../.."; + +describe("comparison operators", function () { + var d1 = new Date(2010, 11, 2); + var d2 = new Date(2010, 11, 3); + var d3 = new Date(2010, 11, 2); + + describe("gt / isGT", function () { + it("should verify if the first argument is greater than the second", function () { + expect(lamb.gt(2, 1)).toBe(true); + expect(lamb.gt(1, 2)).toBe(false); + expect(lamb.gt(2, 2)).toBe(false); + expect(lamb.gt(true, false)).toBe(true); + expect(lamb.gt(d2, d1)).toBe(true); + expect(lamb.gt("a", "A")).toBe(true); + expect(lamb.gt("", "a")).toBe(false); + + expect(lamb.isGT(1)(2)).toBe(true); + expect(lamb.isGT(2)(1)).toBe(false); + expect(lamb.isGT(2)(2)).toBe(false); + expect(lamb.isGT(false)(true)).toBe(true); + expect(lamb.isGT(d1)(d2)).toBe(true); + expect(lamb.isGT("A")("a")).toBe(true); + expect(lamb.isGT("a")("")).toBe(false); + }); + }); + + describe("gte / isGTE", function () { + it("should verify if the first argument is greater than or equal to the second", function () { + expect(lamb.gte(2, 1)).toBe(true); + expect(lamb.gte(1, 2)).toBe(false); + expect(lamb.gte(2, 2)).toBe(true); + expect(lamb.gte(true, false)).toBe(true); + expect(lamb.gte(d2, d1)).toBe(true); + expect(lamb.gte(d1, d3)).toBe(true); + expect(lamb.gte("a", "A")).toBe(true); + expect(lamb.gte("", "a")).toBe(false); + + expect(lamb.isGTE(1)(2)).toBe(true); + expect(lamb.isGTE(2)(1)).toBe(false); + expect(lamb.isGTE(2)(2)).toBe(true); + expect(lamb.isGTE(false)(true)).toBe(true); + expect(lamb.isGTE(d1)(d2)).toBe(true); + expect(lamb.isGTE(d3)(d1)).toBe(true); + expect(lamb.isGTE("A")("a")).toBe(true); + expect(lamb.isGTE("a")("")).toBe(false); + }); + }); + + describe("lt / isLT", function () { + it("should verify if the first argument is less than the second", function () { + expect(lamb.lt(2, 1)).toBe(false); + expect(lamb.lt(1, 2)).toBe(true); + expect(lamb.lt(2, 2)).toBe(false); + expect(lamb.lt(true, false)).toBe(false); + expect(lamb.lt(d2, d1)).toBe(false); + expect(lamb.lt("a", "A")).toBe(false); + expect(lamb.lt("", "a")).toBe(true); + + expect(lamb.isLT(1)(2)).toBe(false); + expect(lamb.isLT(2)(1)).toBe(true); + expect(lamb.isLT(2)(2)).toBe(false); + expect(lamb.isLT(false)(true)).toBe(false); + expect(lamb.isLT(d1)(d2)).toBe(false); + expect(lamb.isLT("A")("a")).toBe(false); + expect(lamb.isLT("a")("")).toBe(true); + }); + }); + + describe("lte / isLTE", function () { + it("should verify if the first argument is less than or equal to the second", function () { + expect(lamb.lte(2, 1)).toBe(false); + expect(lamb.lte(1, 2)).toBe(true); + expect(lamb.lte(2, 2)).toBe(true); + expect(lamb.lte(true, false)).toBe(false); + expect(lamb.lte(d2, d1)).toBe(false); + expect(lamb.lte(d1, d3)).toBe(true); + expect(lamb.lte("a", "A")).toBe(false); + expect(lamb.lte("", "a")).toBe(true); + + expect(lamb.isLTE(1)(2)).toBe(false); + expect(lamb.isLTE(2)(1)).toBe(true); + expect(lamb.isLTE(2)(2)).toBe(true); + expect(lamb.isLTE(false)(true)).toBe(false); + expect(lamb.isLTE(d1)(d2)).toBe(false); + expect(lamb.isLTE(d3)(d1)).toBe(true); + expect(lamb.isLTE("A")("a")).toBe(false); + expect(lamb.isLTE("a")("")).toBe(true); + }); + }); +}); diff --git a/src/logic/__tests__/condition.spec.js b/src/logic/__tests__/condition.spec.js new file mode 100644 index 0000000..4530733 --- /dev/null +++ b/src/logic/__tests__/condition.spec.js @@ -0,0 +1,104 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; +import { Foo, hasEvens, isVowel } from "./_commons"; + +describe("case / condition", function () { + var halve = lamb.divideBy(2); + var isGreaterThan5 = lamb.isGT(5); + var halveIfGreaterThan5 = lamb.condition(isGreaterThan5, halve, lamb.identity); + + describe("case", function () { + it("should call the received function if the predicate is satisfied by the same arguments", function () { + expect(lamb.case(isGreaterThan5, halve)(10)).toBe(5); + }); + + it("should build a function that returns `undefined` if the predicate isn't satisfied", function () { + expect(lamb.case(isGreaterThan5, halve)(3)).toBeUndefined(); + }); + }); + + describe("condition", function () { + it("should build a function that conditionally executes the received functions evaluating a predicate", function () { + expect(halveIfGreaterThan5(3)).toBe(3); + expect(halveIfGreaterThan5(10)).toBe(5); + }); + + it("should build a function throwing an exception if `falseFn` isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.condition(lamb.always(false), lamb.always(99), value)).toThrow(); + }); + + expect(lamb.condition(lamb.always(false), lamb.always(99))).toThrow(); + }); + }); + + it("should pass all the received arguments to both the predicate and the chosen branching function", function () { + var satisfiedPredicate = jest.fn().mockReturnValue(true); + var notSatisfiedPredicate = jest.fn().mockReturnValue(false); + + var condA = lamb.condition(satisfiedPredicate, lamb.list, lamb.always([])); + var condB = lamb.condition(notSatisfiedPredicate, lamb.always([]), lamb.list); + var caseA = lamb.case(satisfiedPredicate, lamb.list); + var caseB = lamb.case(notSatisfiedPredicate, lamb.always([])); + + expect(condA(1, 2, 3, 4)).toEqual([1, 2, 3, 4]); + expect(condB(5, 6, 7)).toEqual([5, 6, 7]); + expect(caseA(8, 9, 10)).toEqual([8, 9, 10]); + expect(caseB(11, 12)).toBeUndefined(); + + expect(satisfiedPredicate).toHaveBeenCalledTimes(2); + expect(satisfiedPredicate.mock.calls[0]).toEqual([1, 2, 3, 4]); + expect(satisfiedPredicate.mock.calls[1]).toEqual([8, 9, 10]); + + expect(notSatisfiedPredicate).toHaveBeenCalledTimes(2); + expect(notSatisfiedPredicate.mock.calls[0]).toEqual([5, 6, 7]); + expect(notSatisfiedPredicate.mock.calls[1]).toEqual([11, 12]); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + var condA = lamb.condition(hasEvens, lamb.always("yes"), lamb.always("no")); + var condB = lamb.condition(isVowel, lamb.always("yes"), lamb.always("no")); + var caseA = lamb.case(hasEvens, lamb.always("yes")); + var caseB = lamb.case(isVowel, lamb.always("yes")); + + expect(condA([1, 2, 5, 7])).toBe("yes"); + expect(condA([1, 3, 5, 7])).toBe("no"); + expect(condB("a")).toBe("yes"); + expect(condB("b")).toBe("no"); + + expect(caseA([1, 2, 5, 7])).toBe("yes"); + expect(caseA([1, 3, 5, 7])).toBeUndefined(); + expect(caseB("a")).toBe("yes"); + expect(caseB("b")).toBeUndefined(); + }); + + it("should keep the functions' context", function () { + expect(new Foo(55).getIfPositiveOrGetSafe()).toBe(55); + expect(new Foo(-55).getIfPositiveOrGetSafe()).toBe(99); + + expect(new Foo(55).getIfPositiveOrUndefined()).toBe(55); + expect(new Foo(-55).getIfPositiveOrUndefined()).toBeUndefined(); + }); + + it("should build a function throwing an exception if the predicate isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.condition(value, lamb.always(99))).toThrow(); + expect(lamb.case(value, lamb.always(99))).toThrow(); + }); + }); + + it("should build a function throwing an exception if `trueFn` isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.condition(lamb.always(true), value, lamb.always(99))).toThrow(); + expect(lamb.case(lamb.always(true), value)).toThrow(); + }); + + expect(lamb.condition(lamb.always(true))).toThrow(); + expect(lamb.case(lamb.always(true))).toThrow(); + }); + + it("should build a function throwing an exception if called without arguments", function () { + expect(lamb.condition()).toThrow(); + expect(lamb.case()).toThrow(); + }); +}); diff --git a/src/logic/__tests__/not.spec.js b/src/logic/__tests__/not.spec.js new file mode 100644 index 0000000..fa847a9 --- /dev/null +++ b/src/logic/__tests__/not.spec.js @@ -0,0 +1,24 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; +import { Foo, isEven } from "./_commons"; + +describe("not", function () { + it("should reverse the truthiness of the given predicate", function () { + var isOdd = lamb.not(isEven); + + expect(isOdd(3)).toBe(true); + }); + + it("should keep the predicate context", function () { + expect(new Foo(4).isOdd()).toBe(false); + expect(new Foo(5).isOdd()).toBe(true); + }); + + it("should build a function returning an exception if the given predicate is missing or isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(lamb.not(value)).toThrow(); + }); + + expect(lamb.not()).toThrow(); + }); +}); diff --git a/src/logic/__tests__/unless-when.spec.js b/src/logic/__tests__/unless-when.spec.js new file mode 100644 index 0000000..bac67bc --- /dev/null +++ b/src/logic/__tests__/unless-when.spec.js @@ -0,0 +1,88 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; +import { Foo, hasEvens, isEven } from "./_commons"; + +describe("unless / when", function () { + var increment = lamb.add(1); + var incArray = lamb.mapWith(increment); + var a1 = [1, 3, 5]; + var a2 = [1, 4, 5]; + + it("should build a function that conditionally applies its `fn` parameter to the received value depending on the evaluation of a predicate", function () { + expect(lamb.unless(isEven, increment)(5)).toBe(6); + expect(lamb.when(isEven, increment)(4)).toBe(5); + }); + + it("should build a function returning the received value when the desired condition is not met", function () { + expect(lamb.unless(isEven, increment)(6)).toBe(6); + expect(lamb.when(isEven, increment)(5)).toBe(5); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(lamb.unless(hasEvens, incArray)(a1)).toEqual([2, 4, 6]); + expect(lamb.unless(hasEvens, incArray)(a2)).toBe(a2); + expect(lamb.when(hasEvens, incArray)(a1)).toBe(a1); + expect(lamb.when(hasEvens, incArray)(a2)).toEqual([2, 5, 6]); + }); + + it("should build a unary function and ignore extra arguments", function () { + var incMock = jest.fn(increment); + var isEvenMock = jest.fn(isEven); + var incUnlessEven = lamb.unless(isEvenMock, incMock); + var incWhenEven = lamb.when(isEvenMock, incMock); + + expect(incUnlessEven.length).toBe(1); + expect(incUnlessEven(5, 6, 7)).toBe(6); + expect(incUnlessEven(6, 7, 8)).toBe(6); + + expect(incWhenEven.length).toBe(1); + expect(incWhenEven(4, 5)).toBe(5); + expect(incWhenEven(5, 6)).toBe(5); + + expect(isEvenMock).toHaveBeenCalledTimes(4); + expect(isEvenMock.mock.calls[0]).toEqual([5]); + expect(isEvenMock.mock.calls[1]).toEqual([6]); + expect(isEvenMock.mock.calls[2]).toEqual([4]); + expect(isEvenMock.mock.calls[3]).toEqual([5]); + + expect(incMock).toHaveBeenCalledTimes(2); + expect(incMock.mock.calls[0]).toEqual([5]); + expect(incMock.mock.calls[1]).toEqual([4]); + }); + + it("should keep the functions' context", function () { + var positiveFoo = new Foo(33); + var negativeFoo = new Foo(-33); + + expect(positiveFoo.getWhenPositiveOrElse(88)).toBe(33); + expect(negativeFoo.getWhenPositiveOrElse(88)).toBe(88); + expect(positiveFoo.getUnlessIsPositiveOrElse(-88)).toBe(-88); + expect(negativeFoo.getUnlessIsPositiveOrElse(-88)).toBe(-33); + }); + + it("should build a function throwing an exception if the predicate isn't a function", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.unless(value, increment)(5); }).toThrow(); + expect(function () { lamb.when(value, increment)(2); }).toThrow(); + }); + }); + + it("should not throw an exception if the transformer isn't a function and the conditions aren't met", function () { + nonFunctions.forEach(function (value) { + expect(lamb.unless(isEven, value)(2)).toBe(2); + expect(lamb.when(isEven, value)(5)).toBe(5); + }); + }); + + it("should build a function throwing an exception if the transformer isn't a function and the conditions are met", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.unless(isEven, value)(5); }).toThrow(); + expect(function () { lamb.when(isEven, value)(2); }).toThrow(); + }); + }); + + it("should build a function throwing an exception if called without arguments", function () { + expect(lamb.unless()).toThrow(); + expect(lamb.when()).toThrow(); + }); +}); diff --git a/src/logic/adapter.js b/src/logic/adapter.js new file mode 100644 index 0000000..91cb804 --- /dev/null +++ b/src/logic/adapter.js @@ -0,0 +1,58 @@ +import isUndefined from "../core/isUndefined"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; + +/** + * Accepts a series of functions and builds a function that applies the received + * arguments to each one and returns the first non-undefined value.
+ * Meant to work in synergy with {@link module:lamb.case|case} and + * {@link module:lamb.invoker|invoker}, can be useful as a strategy pattern for functions, + * to mimic conditional logic or pattern matching, and also to build polymorphic functions. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var filterString = _.compose(_.invoker("join", ""), _.filter); + * var filterAdapter = _.adapter([ + * _.invoker("filter"), + * _.case(_.isType("String"), filterString) + * ]); + * + * filterAdapter([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] + * filterAdapter("123456", isEven) // => "246" + * filterAdapter({}, isEven) // => undefined + * + * // by its nature is composable + * var filterWithDefault = _.adapter([filterAdapter, _.always("Not implemented")]); + * + * filterWithDefault([1, 2, 3, 4, 5, 6], isEven) // => [2, 4, 6] + * filterWithDefault("123456", isEven) // => "246" + * filterWithDefault({}, isEven) // => "Not implemented" + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.case|case} + * @see {@link module:lamb.invoker|invoker} + * @since 0.6.0 + * @param {Function[]} functions + * @returns {Function} + */ +function adapter (functions) { + if (!Array.isArray(functions)) { + throw _makeTypeErrorFor(functions, "array"); + } + + return function () { + var len = functions.length; + var result; + + for (var i = 0; i < len; i++) { + result = functions[i].apply(this, arguments); + + if (!isUndefined(result)) { + break; + } + } + + return result; + }; +} + +export default adapter; diff --git a/src/logic/allOf.js b/src/logic/allOf.js new file mode 100644 index 0000000..a3303fd --- /dev/null +++ b/src/logic/allOf.js @@ -0,0 +1,25 @@ +import _checkPredicates from "../privates/_checkPredicates"; + +/** + * Accepts an array of predicates and builds a new one that returns true if they are all satisfied + * by the same arguments. The functions in the array will be applied one at a time until a + * false value is produced, which is returned immediately. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var isPositiveEven = _.allOf([isEven, _.isGT(0)]); + * + * isPositiveEven(-2) // => false + * isPositiveEven(11) // => false + * isPositiveEven(6) // => true + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.anyOf|anyOf} + * @since 0.1.0 + * @param {Function[]} predicates + * @returns {Function} + */ +var allOf = _checkPredicates(true); + +export default allOf; diff --git a/src/logic/anyOf.js b/src/logic/anyOf.js new file mode 100644 index 0000000..31880df --- /dev/null +++ b/src/logic/anyOf.js @@ -0,0 +1,30 @@ +import _checkPredicates from "../privates/_checkPredicates"; + +/** + * Accepts an array of predicates and builds a new one that returns true if at least one of them is + * satisfied by the received arguments. The functions in the array will be applied one at a time + * until a true value is produced, which is returned immediately. + * @example + * var users = [ + * {id: 1, name: "John", group: "guest"}, + * {id: 2, name: "Jane", group: "root"}, + * {id: 3, name: "Mario", group: "admin"} + * ]; + * var isInGroup = _.partial(_.hasKeyValue, ["group"]); + * var isSuperUser = _.anyOf([isInGroup("admin"), isInGroup("root")]); + * + * isSuperUser(users[0]) // => false + * isSuperUser(users[1]) // => true + * isSuperUser(users[2]) // => true + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.allOf|allOf} + * @since 0.1.0 + * @param {Function[]} predicates + * @returns {Function} + */ +var anyOf = _checkPredicates(false); + +export default anyOf; diff --git a/src/logic/areSame.js b/src/logic/areSame.js new file mode 100644 index 0000000..f41a258 --- /dev/null +++ b/src/logic/areSame.js @@ -0,0 +1,36 @@ +import areSVZ from "../core/areSVZ"; + +/** + * Verifies that the two supplied values are the same value using the "SameValue" comparison.
+ * Note that this doesn't behave as the strict equality operator, but rather as a shim of ES6's + * [Object.is]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is}. + * Differences are that 0 and -0 aren't the same value and, finally, + * NaN is equal to itself.
+ * See also {@link module:lamb.is|is} for a curried version building a predicate and + * {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} to perform a "SameValueZero" + * comparison. + * @example + * var testObject = {}; + * + * _.areSame({}, testObject) // => false + * _.areSame(testObject, testObject) // => true + * _.areSame("foo", "foo") // => true + * _.areSame(0, -0) // => false + * _.areSame(0 / 0, NaN) // => true + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.is|is} + * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.50.0 + * @param {*} a + * @param {*} b + * @returns {Boolean} + */ +function areSame (a, b) { + return a === 0 && b === 0 ? 1 / a === 1 / b : areSVZ(a, b); +} + +export default areSame; diff --git a/src/logic/case.js b/src/logic/case.js new file mode 100644 index 0000000..04f8986 --- /dev/null +++ b/src/logic/case.js @@ -0,0 +1,31 @@ +/** + * Builds a case for {@link module:lamb.adapter|adapter}.
+ * The function will apply the received arguments to fn if the predicate is satisfied + * with the same arguments, otherwise will return undefined.
+ * See also {@link module:lamb.condition|condition} to build a condition with two branching functions + * and {@link module:lamb.unless|unless} and {@link module:lamb.when|when} where one of the branches + * is the identity function. + * @example + * var halveIfNumber = _.case(_.isType("Number"), _.divideBy(2)); + * + * halveIfNumber(2) // => 1 + * halveIfNumber("2") // => undefined + * + * @alias module:lamb.case + * @category Logic + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.condition|condition} + * @see {@link module:lamb.unless|unless} + * @see {@link module:lamb.when|when} + * @since 0.51.0 + * @param {Function} predicate + * @param {Function} fn + * @returns {Function} + */ +function case_ (predicate, fn) { + return function () { + return predicate.apply(this, arguments) ? fn.apply(this, arguments) : void 0; + }; +} + +export default case_; diff --git a/src/logic/condition.js b/src/logic/condition.js new file mode 100644 index 0000000..8a546b0 --- /dev/null +++ b/src/logic/condition.js @@ -0,0 +1,33 @@ +/** + * Builds a function that will apply the received arguments to trueFn, + * if the predicate is satisfied with the same arguments, or to falseFn otherwise.
+ * Although you can use other conditions as trueFn or falseFn, + * it's probably better to use {@link module:lamb.adapter|adapter} to build more complex behaviours.
+ * See also {@link module:lamb.unless|unless} and {@link module:lamb.when|when} as they are + * shortcuts to common use cases. + * @example + * var isEven = function (n) { return n % 2 === 0}; + * var halveEvenAndDoubleOdd = _.condition(isEven, _.divideBy(2), _.multiplyBy(2)); + * + * halveEvenAndDoubleOdd(5) // => 10 + * halveEvenAndDoubleOdd(6) // => 3 + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.unless|unless} + * @see {@link module:lamb.when|when} + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.case|case} + * @since 0.2.0 + * @param {Function} predicate + * @param {Function} trueFn + * @param {Function} falseFn + * @returns {Function} + */ +function condition (predicate, trueFn, falseFn) { + return function () { + return (predicate.apply(this, arguments) ? trueFn : falseFn).apply(this, arguments); + }; +} + +export default condition; diff --git a/src/logic/gt.js b/src/logic/gt.js new file mode 100644 index 0000000..d52e54c --- /dev/null +++ b/src/logic/gt.js @@ -0,0 +1,33 @@ +/** + * Verifies that the first given value is greater than the second.
+ * Wraps the native > operator within a function. + * @example + * var pastDate = new Date(2010, 2, 12); + * var today = new Date(); + * + * _.gt(today, pastDate) // => true + * _.gt(pastDate, today) // => false + * _.gt(3, 4) // => false + * _.gt(3, 3) // => false + * _.gt(3, 2) // => true + * _.gt(0, -0) // => false + * _.gt(-0, 0) // => false + * _.gt("a", "A") // => true + * _.gt("b", "a") // => true + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.gte|gte} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} + */ +function gt (a, b) { + return a > b; +} + +export default gt; diff --git a/src/logic/gte.js b/src/logic/gte.js new file mode 100644 index 0000000..904ff2b --- /dev/null +++ b/src/logic/gte.js @@ -0,0 +1,27 @@ +/** + * Verifies that the first given value is greater than or equal to the second. + * Regarding equality, beware that this is simply a wrapper for the native + * >= operator, so -0 === 0. + * @example + * _.gte(3, 4) // => false + * _.gte(3, 3) // => true + * _.gte(3, 2) // => true + * _.gte(0, -0) // => true + * _.gte(-0, 0) // => true + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.gt|gt} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} + */ +function gte (a, b) { + return a >= b; +} + +export default gte; diff --git a/src/logic/is.js b/src/logic/is.js new file mode 100644 index 0000000..452eb66 --- /dev/null +++ b/src/logic/is.js @@ -0,0 +1,42 @@ +import _curry2 from "../privates/_curry2"; +import areSame from "./areSame"; + +/** + * A curried version of {@link module:lamb.areSame|areSame}.
+ * Accepts a value and builds a predicate that checks whether the value + * and the one received by the predicate are the same using the "SameValue" + * comparison.
+ * See also {@link module:lamb.areSVZ|areSVZ} and {@link module:lamb.isSVZ|isSVZ} + * to perform a "SameValueZero" comparison. + * @example + * var john = {name: "John", surname: "Doe"}; + * var isJohn = _.is(john); + * var isNegativeZero = _.is(-0); + * var isReallyNaN = _.is(NaN); + * + * isJohn(john) // => true + * isJohn({name: "John", surname: "Doe"}) // => false + * + * isNegativeZero(0) // => false + * isNegativeZero(-0) // => true + * + * isNaN(NaN) // => true + * isNaN("foo") // => true + * + * isReallyNaN(NaN) // => true + * isReallyNaN("foo") // => false + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.areSame|areSame} + * @see {@link module:lamb.areSVZ|areSVZ}, {@link module:lamb.isSVZ|isSVZ} + * @see [SameValue comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevalue} + * @see [SameValueZero comparison]{@link https://www.ecma-international.org/ecma-262/7.0/#sec-samevaluezero} + * @since 0.1.0 + * @param {*} value + * @returns {Function} + */ +var is = _curry2(areSame); + +export default is; diff --git a/src/logic/isGT.js b/src/logic/isGT.js new file mode 100644 index 0000000..57f17ef --- /dev/null +++ b/src/logic/isGT.js @@ -0,0 +1,28 @@ +import _curry2 from "../privates/_curry2"; +import gt from "./gt"; + +/** + * A right curried version of {@link module:lamb.gt|gt}.
+ * Accepts a value and builds a predicate that checks whether the value + * is greater than the one received by the predicate. + * @example + * var isGreaterThan5 = _.isGT(5); + * + * isGreaterThan5(3) // => false + * isGreaterThan5(5) // => false + * isGreaterThan5(7) // => true + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @since 0.1.0 + * @param {Number|String|Date|Boolean} value + * @returns {Function} + */ +var isGT = _curry2(gt, true); + +export default isGT; diff --git a/src/logic/isGTE.js b/src/logic/isGTE.js new file mode 100644 index 0000000..ca34c04 --- /dev/null +++ b/src/logic/isGTE.js @@ -0,0 +1,29 @@ +import _curry2 from "../privates/_curry2"; +import gte from "./gte"; + +/** + * A right curried version of {@link module:lamb.gte|gte}.
+ * Accepts a value and builds a predicate that checks whether the value + * is greater than or equal to the one received by the predicate. + * @example + * var isPositiveOrZero = _.isGTE(0); + * + * isPositiveOrZero(-3) // => false + * isPositiveOrZero(-0) // => true + * isPositiveOrZero(0) // => true + * isPositiveOrZero(5) // => true + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.isGT|isGT} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @since 0.1.0 + * @param {Number|String|Date|Boolean} value + * @returns {Function} + */ +var isGTE = _curry2(gte, true); + +export default isGTE; diff --git a/src/logic/isLT.js b/src/logic/isLT.js new file mode 100644 index 0000000..8ee8418 --- /dev/null +++ b/src/logic/isLT.js @@ -0,0 +1,28 @@ +import _curry2 from "../privates/_curry2"; +import lt from "./lt"; + +/** + * A right curried version of {@link module:lamb.lt|lt}.
+ * Accepts a value and builds a predicate that checks whether the value + * is less than the one received by the predicate. + * @example + * var isLessThan5 = _.isLT(5); + * + * isLessThan5(7) // => false + * isLessThan5(5) // => false + * isLessThan5(3) // => true + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @since 0.1.0 + * @param {Number|String|Date|Boolean} value + * @returns {Function} + */ +var isLT = _curry2(lt, true); + +export default isLT; diff --git a/src/logic/isLTE.js b/src/logic/isLTE.js new file mode 100644 index 0000000..6dbd694 --- /dev/null +++ b/src/logic/isLTE.js @@ -0,0 +1,29 @@ +import _curry2 from "../privates/_curry2"; +import lte from "./lte"; + +/** + * A right curried version of {@link module:lamb.lte|lte}.
+ * Accepts a value and builds a predicate that checks whether the value + * is less than or equal to the one received by the predicate. + * @example + * var isNegativeOrZero = _.isLTE(0); + * + * isNegativeOrZero(5) // => false + * isNegativeOrZero(-0) // => true + * isNegativeOrZero(0) // => true + * isNegativeOrZero(-3) // => true + * + * @memberof module:lamb + * @category Logic + * @function + * @see {@link module:lamb.isLT|isLT} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @see {@link module:lamb.lt|lt}, {@link module:lamb.lte|lte} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @since 0.1.0 + * @param {Number|String|Date|Boolean} value + * @returns {Function} + */ +var isLTE = _curry2(lte, true); + +export default isLTE; diff --git a/src/logic/lt.js b/src/logic/lt.js new file mode 100644 index 0000000..b2700c2 --- /dev/null +++ b/src/logic/lt.js @@ -0,0 +1,33 @@ +/** + * Verifies that the first given value is less than the second.
+ * Wraps the native < operator within a function. + * @example + * var pastDate = new Date(2010, 2, 12); + * var today = new Date(); + * + * _.lt(today, pastDate) // => false + * _.lt(pastDate, today) // => true + * _.lt(3, 4) // => true + * _.lt(3, 3) // => false + * _.lt(3, 2) // => false + * _.lt(0, -0) // => false + * _.lt(-0, 0) // => false + * _.lt("a", "A") // => false + * _.lt("a", "b") // => true + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.lte|lte} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} + */ +function lt (a, b) { + return a < b; +} + +export default lt; diff --git a/src/logic/lte.js b/src/logic/lte.js new file mode 100644 index 0000000..a6092d4 --- /dev/null +++ b/src/logic/lte.js @@ -0,0 +1,27 @@ +/** + * Verifies that the first given value is less than or equal to the second. + * Regarding equality, beware that this is simply a wrapper for the native + * <= operator, so -0 === 0. + * @example + * _.lte(3, 4) // => true + * _.lte(3, 3) // => true + * _.lte(3, 2) // => false + * _.lte(0, -0) // => true + * _.lte(-0, 0) // => true + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.lt|lt} + * @see {@link module:lamb.gt|gt}, {@link module:lamb.gte|gte} + * @see {@link module:lamb.isLT|isLT}, {@link module:lamb.isLTE|isLTE} + * @see {@link module:lamb.isGT|isGT}, {@link module:lamb.isGTE|isGTE} + * @since 0.50.0 + * @param {Number|String|Date|Boolean} a + * @param {Number|String|Date|Boolean} b + * @returns {Boolean} + */ +function lte (a, b) { + return a <= b; +} + +export default lte; diff --git a/src/logic/not.js b/src/logic/not.js new file mode 100644 index 0000000..ee139fa --- /dev/null +++ b/src/logic/not.js @@ -0,0 +1,22 @@ +/** + * Returns a predicate that negates the given one. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var isOdd = _.not(isEven); + * + * isOdd(5) // => true + * isOdd(4) // => false + * + * @memberof module:lamb + * @category Logic + * @since 0.1.0 + * @param {Function} predicate + * @returns {Function} + */ +function not (predicate) { + return function () { + return !predicate.apply(this, arguments); + }; +} + +export default not; diff --git a/src/logic/unless.js b/src/logic/unless.js new file mode 100644 index 0000000..217700e --- /dev/null +++ b/src/logic/unless.js @@ -0,0 +1,32 @@ +/** + * Builds a unary function that will check its argument against the given predicate. + * If the predicate isn't satisfied, the provided fn function will be + * applied to the same value. The received argument is returned as it is otherwise.
+ * See {@link module:lamb.when|when} for the opposite behaviour.
+ * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, + * where its trueFn parameter is the [identity function]{@link module:lamb.identity}. + * @example + * var isEven = function (n) { return n % 2 === 0}; + * var halveUnlessIsEven = _.unless(isEven, _.divideBy(2)); + * + * halveUnlessIsEven(5) // => 2.5 + * halveUnlessIsEven(6) // => 6 + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.condition|condition} + * @see {@link module:lamb.when|when} + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.case|case} + * @since 0.42.0 + * @param {Function} predicate + * @param {Function} fn + * @returns {Function} + */ +function unless (predicate, fn) { + return function (value) { + return predicate.call(this, value) ? value : fn.call(this, value); + }; +} + +export default unless; diff --git a/src/logic/when.js b/src/logic/when.js new file mode 100644 index 0000000..eb6808d --- /dev/null +++ b/src/logic/when.js @@ -0,0 +1,32 @@ +/** + * Builds a unary function that will check its argument against the given predicate. + * If the predicate is satisfied, the provided fn function will be + * applied to the same value. The received argument is returned as it is otherwise.
+ * See {@link module:lamb.unless|unless} for the opposite behaviour.
+ * It's a shortcut for a common use case of {@link module:lamb.condition|condition}, + * where its falseFn parameter is the [identity function]{@link module:lamb.identity}. + * @example + * var isEven = function (n) { return n % 2 === 0; }; + * var halveIfEven = _.when(isEven, _.divideBy(2)); + * + * halveIfEven(5) // => 5 + * halveIfEven(6) // => 3 + * + * @memberof module:lamb + * @category Logic + * @see {@link module:lamb.condition|condition} + * @see {@link module:lamb.unless|unless} + * @see {@link module:lamb.adapter|adapter} + * @see {@link module:lamb.case|case} + * @since 0.42.0 + * @param {Function} predicate + * @param {Function} fn + * @returns {Function} + */ +function when (predicate, fn) { + return function (value) { + return predicate.call(this, value) ? fn.call(this, value) : value; + }; +} + +export default when; diff --git a/src/math.js b/src/math.js deleted file mode 100644 index cfa9a09..0000000 --- a/src/math.js +++ /dev/null @@ -1,437 +0,0 @@ -/** - * A curried version of {@link module:lamb.sum|sum}. - * @example - * var add5 = _.add(5); - * - * _.add5(4) // => 9 - * _.add5(-2) // => 3 - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.sum|sum} - * @since 0.1.0 - * @param {Number} a - * @returns {Function} - */ -var add = _curry2(sum, true); - -/** - * "Clamps" a number within the given limits, both included.
- * The function will convert to number all its parameters before starting any - * evaluation, and will return NaN if min is greater - * than max. - * @example - * _.clamp(-5, 0, 10) // => 0 - * _.clamp(5, 0, 10) // => 5 - * _.clamp(15, 0, 10) // => 10 - * _.clamp(0, 0, 10) // => 0 - * _.clamp(10, 0, 10) // => 10 - * _.is(_.clamp(-0, 0, 10), -0) // => true - * _.clamp(10, 20, 15) // => NaN - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.clampWithin|clampWithin} - * @since 0.13.0 - * @param {Number} n - * @param {Number} min - * @param {Number} max - * @returns {Number} - */ -function clamp (n, min, max) { - n = +n; - min = +min; - max = +max; - - if (min > max) { - return NaN; - } else { - return n < min ? min : n > max ? max : n; - } -} - -/** - * A curried version of {@link module:lamb.clamp|clamp}, expecting a min - * and a max value, that builds a function waiting for the number to clamp. - * @example - * _.clampWithin(0, 10)(-5) // => 0 - * _.clampWithin(0, 10)(5) // => 5 - * _.clampWithin(0, 10)(15) // => 10 - * _.clampWithin(0, 10)(0) // => 0 - * _.clampWithin(0, 10)(10) // => 10 - * _.is(_.clampWithin(0, 10)(-0), -0) // => true - * _.clampWithin(20, 15)(10) // => NaN - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.clamp|clamp} - * @since 0.47.0 - * @param {Number} min - * @param {Number} max - * @returns {Function} - */ -var clampWithin = _makePartial3(clamp); - -/** - * A curried version of {@link module:lamb.subtract|subtract} that expects the - * subtrahend to build a function waiting for the minuend. - * @example - * var deduct5 = _.deduct(5); - * - * deduct5(12) // => 7 - * deduct5(3) // => -2 - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.subtract|subtract} - * @since 0.50.0 - * @param {Number} a - * @returns {Function} - */ -var deduct = _curry2(subtract, true); - -/** - * Divides two numbers. - * @example - * _.divide(5, 2) // => 2.5 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.divideBy|divideBy} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ -function divide (a, b) { - return a / b; -} - -/** - * A curried version of {@link module:lamb.divide|divide} that expects a divisor to - * build a function waiting for the dividend. - * @example - * var halve = divideBy(2); - * - * halve(10) // => 5 - * halve(5) // => 2.5 - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.divide|divide} - * @since 0.50.0 - * @param {Number} a - * @returns {Function} - */ -var divideBy = _curry2(divide, true); - -/** - * Generates a sequence of values of the desired length with the provided iteratee. - * The values being iterated, and received by the iteratee, are the results generated so far. - * @example - * var fibonacci = function (n, idx, results) { - * return n + (results[idx - 1] || 0); - * }; - * - * _.generate(1, 10, fibonacci) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.range|range} - * @since 0.21.0 - * @param {*} start - The starting value - * @param {Number} len - The desired length for the sequence - * @param {ListIteratorCallback} iteratee - * @returns {Array} - */ -function generate (start, len, iteratee) { - var result = [start]; - - for (var i = 0, limit = len - 1; i < limit; i++) { - result.push(iteratee(result[i], i, result)); - } - - return result; -} - -/** - * Verifies whether the received value is a finite number.
- * Behaves almost as a shim of ES6's [Number.isFinite]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite}, - * but with a difference: it will return true even for Number object's instances. - * @example - * _.isFinite(5) // => true - * _.isFinite(new Number(5)) // => true - * _.isFinite(Infinity) // => false - * _.isFinite(-Infinity) // => false - * _.isFinite("5") // => false - * _.isFinite(NaN) // => false - * _.isFinite(null) // => false - * - * @alias module:lamb.isFinite - * @category Math - * @since 0.46.0 - * @param {*} value - * @returns {Boolean} - */ -function isFinite_ (value) { - return type(value) === "Number" && isFinite(value); -} - -/** - * Verifies whether the received value is a number and an integer. - * Behaves almost as a shim of ES6's [Number.isInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger}, - * but with a difference: it will return true even for Number object's instances. - * @example - * _.isInteger(5) // => true - * _.isInteger(new Number(5)) // => true - * _.isInteger(2.5) // => false - * _.isInteger(Infinity) // => false - * _.isInteger(-Infinity) // => false - * _.isInteger("5") // => false - * _.isInteger(NaN) // => false - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.isSafeInteger|isSafeInteger} - * @since 0.46.0 - * @param {*} value - * @returns {Boolean} - */ -function isInteger (value) { - return type(value) === "Number" && value % 1 === 0; -} - -/** - * Verifies whether the received value is a "safe integer", meaning that is a number and that - * can be exactly represented as an IEEE-754 double precision number. - * The safe integers consist of all integers from -(253 - 1) inclusive to - * 253 - 1 inclusive.
- * Behaves almost as a shim of ES6's [Number.isSafeInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger}, - * but with a difference: it will return true even for Number object's instances. - * @example - * _.isSafeInteger(5) // => true - * _.isSafeInteger(new Number(5)) // => true - * _.isSafeInteger(Math.pow(2, 53) - 1) // => true - * _.isSafeInteger(Math.pow(2, 53)) // => false - * _.isSafeInteger(2e32) // => false - * _.isSafeInteger(2.5) // => false - * _.isSafeInteger(Infinity) // => false - * _.isSafeInteger(-Infinity) // => false - * _.isSafeInteger("5") // => false - * _.isSafeInteger(NaN) // => false - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.isInteger|isInteger} - * @since 0.46.0 - * @param {*} value - * @returns {Boolean} - */ -function isSafeInteger (value) { - return isInteger(value) && Math.abs(value) <= 9007199254740991; -} - -/** - * Performs the modulo operation and should not be confused with the - * {@link module:lamb.remainder|remainder}. - * The function performs a floored division to calculate the result and not - * a truncated one, hence the sign of the dividend is not kept, unlike the - * {@link module:lamb.remainder|remainder}. - * @example - * _.modulo(5, 3) // => 2 - * _.remainder(5, 3) // => 2 - * - * _.modulo(-5, 3) // => 1 - * _.remainder(-5, 3) // => -2 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.remainder|remainder} - * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ -function modulo (a, b) { - return a - (b * Math.floor(a / b)); -} - -/** - * Multiplies two numbers. - * @example - * _.multiply(5, 3) // => 15 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.multiplyBy|multiplyBy} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ -function multiply (a, b) { - return a * b; -} - -/** - * A curried version of {@link module:lamb.multiply|multiply}. - * @example - * var double = _.multiplyBy(2); - * - * double(5) // => 10 - * - * @memberof module:lamb - * @category Math - * @function - * @see {@link module:lamb.multiply|multiply} - * @since 0.50.0 - * @param {Number} a - * @returns {Function} - */ -var multiplyBy = _curry2(multiply, true); - -/** - * Generates a random integer between two given integers, both included. - * Note that no safety measure is taken if the provided arguments aren't integers, so - * you may end up with unexpected (not really) results. - * For example randomInt(0.1, 1.2) could be 2. - * @example - * - * _.randomInt(1, 10) // => an integer >=1 && <= 10 - * - * @memberof module:lamb - * @category Math - * @since 0.1.0 - * @param {Number} min - * @param {Number} max - * @returns {Number} - */ -function randomInt (min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); -} - -/** - * Generates an arithmetic progression of numbers starting from start up to, - * but not including, limit, using the given step. - * @example - * _.range(2, 10) // => [2, 3, 4, 5, 6, 7, 8, 9] - * _.range(1, -10, -2) // => [1, -1, -3, -5, -7, -9] - * _.range(0, 3, 1) // => [0, 1, 2] - * _.range(-0, 3, 1) // => [-0, 1, 2] - * _.range(1, -10, 2) // => [] - * _.range(3, 5, -1) // => [] - * - * @example Behaviour if step happens to be zero: - * _.range(2, 10, 0) // => [2] - * _.range(2, -10, 0) // => [2] - * _.range(2, 2, 0) // => [] - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.generate|generate} - * @since 0.1.0 - * @param {Number} start - * @param {Number} limit - * @param {Number} [step=1] - * @returns {Number[]} - */ -function range (start, limit, step) { - start = _forceToNumber(start); - limit = _forceToNumber(limit); - step = arguments.length === 3 ? _forceToNumber(step) : 1; - - if (step === 0) { - return limit === start ? [] : [start]; - } - - var len = Math.max(Math.ceil((limit - start) / step), 0); - var result = Array(len); - - for (var i = 0, last = start; i < len; i++) { - result[i] = last; - last += step; - } - - return result; -} - -/** - * Gets the remainder of the division of two numbers. - * Not to be confused with the {@link module:lamb.modulo|modulo} as the remainder - * keeps the sign of the dividend and may lead to some unexpected results. - * @example - * // example of wrong usage of the remainder - * // (in this case the modulo operation should be used) - * var isOdd = function (n) { return _.remainder(n, 2) === 1; }; - * isOdd(-3) // => false as -3 % 2 === -1 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.modulo|modulo} - * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ -function remainder (a, b) { - return a % b; -} - -/** - * Subtracts two numbers. - * @example - * _.subtract(5, 3) // => 2 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.deduct|deduct} - * @since 0.1.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ -function subtract (a, b) { - return a - b; -} - -/** - * Sums two numbers. - * @example - * _.sum(4, 5) // => 9 - * - * @memberof module:lamb - * @category Math - * @see {@link module:lamb.add|add} - * @since 0.50.0 - * @param {Number} a - * @param {Number} b - * @returns {Number} - */ -function sum (a, b) { - return a + b; -} - -lamb.add = add; -lamb.clamp = clamp; -lamb.clampWithin = clampWithin; -lamb.deduct = deduct; -lamb.divide = divide; -lamb.divideBy = divideBy; -lamb.generate = generate; -lamb.isFinite = isFinite_; -lamb.isInteger = isInteger; -lamb.isSafeInteger = isSafeInteger; -lamb.modulo = modulo; -lamb.multiply = multiply; -lamb.multiplyBy = multiplyBy; -lamb.randomInt = randomInt; -lamb.range = range; -lamb.remainder = remainder; -lamb.subtract = subtract; -lamb.sum = sum; diff --git a/src/math/__tests__/divide.spec.js b/src/math/__tests__/divide.spec.js new file mode 100644 index 0000000..549d64c --- /dev/null +++ b/src/math/__tests__/divide.spec.js @@ -0,0 +1,12 @@ +import * as lamb from "../.."; + +describe("divide / divideBy", function () { + it("should divide two numbers", function () { + var divideBy3 = lamb.divideBy(3); + + expect(lamb.divide(15, 3)).toBe(5); + expect(lamb.divide(15, 0)).toBe(Infinity); + expect(divideBy3(15)).toBe(5); + expect(divideBy3(16)).toBe(5.333333333333333); + }); +}); diff --git a/src/math/__tests__/generate.spec.js b/src/math/__tests__/generate.spec.js new file mode 100644 index 0000000..bb81fcd --- /dev/null +++ b/src/math/__tests__/generate.spec.js @@ -0,0 +1,13 @@ +import * as lamb from "../.."; + +describe("generate", function () { + it("should generate a sequence of values of the desired length with the provided iteratee", function () { + var fibonacci = function (n, idx, list) { + return n + (list[idx - 1] || 0); + }; + + expect(lamb.generate(1, 20, fibonacci)).toEqual( + [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] + ); + }); +}); diff --git a/src/math/__tests__/isFinite.spec.js b/src/math/__tests__/isFinite.spec.js new file mode 100644 index 0000000..e259d68 --- /dev/null +++ b/src/math/__tests__/isFinite.spec.js @@ -0,0 +1,21 @@ +import * as lamb from "../.."; +import { nonNumbers } from "../../__tests__/commons"; + +describe("isFinite", function () { + it("should verify whether the received value is a finite number", function () { + expect(lamb.isFinite(0)).toBe(true); + expect(lamb.isFinite(-2e64)).toBe(true); + expect(lamb.isFinite(new Number(5))).toBe(true); + expect(lamb.isFinite(Infinity)).toBe(false); + expect(lamb.isFinite(-Infinity)).toBe(false); + expect(lamb.isFinite(NaN)).toBe(false); + }); + + it("should return `false` for any non-number value and when it's called without arguments", function () { + nonNumbers.forEach(function (value) { + expect(lamb.isFinite(value)).toBe(false); + }); + + expect(lamb.isFinite()).toBe(false); + }); +}); diff --git a/src/math/__tests__/isInteger.spec.js b/src/math/__tests__/isInteger.spec.js new file mode 100644 index 0000000..c71a3fe --- /dev/null +++ b/src/math/__tests__/isInteger.spec.js @@ -0,0 +1,22 @@ +import * as lamb from "../.."; +import { nonNumbers } from "../../__tests__/commons"; + +describe("isInteger", function () { + it("should verify whether the received value is an integer", function () { + expect(lamb.isInteger(0)).toBe(true); + expect(lamb.isInteger(-2e64)).toBe(true); + expect(lamb.isInteger(new Number(5))).toBe(true); + expect(lamb.isInteger(2.4)).toBe(false); + expect(lamb.isInteger(Infinity)).toBe(false); + expect(lamb.isInteger(-Infinity)).toBe(false); + expect(lamb.isInteger(NaN)).toBe(false); + }); + + it("should return `false` for any non-number value and when it's called without arguments", function () { + nonNumbers.forEach(function (value) { + expect(lamb.isInteger(value)).toBe(false); + }); + + expect(lamb.isInteger()).toBe(false); + }); +}); diff --git a/src/math/__tests__/isSafeInteger.spec.js b/src/math/__tests__/isSafeInteger.spec.js new file mode 100644 index 0000000..376824e --- /dev/null +++ b/src/math/__tests__/isSafeInteger.spec.js @@ -0,0 +1,25 @@ +import * as lamb from "../.."; +import { nonNumbers } from "../../__tests__/commons"; + +describe("isSafeInteger", function () { + it("shoud verify whether the received value is a \"safe integer\"", function () { + expect(lamb.isSafeInteger(0)).toBe(true); + expect(lamb.isSafeInteger(-2e10)).toBe(true); + expect(lamb.isSafeInteger(new Number(5))).toBe(true); + expect(lamb.isSafeInteger(Math.pow(2, 53) - 1)).toBe(true); + expect(lamb.isSafeInteger(2.4)).toBe(false); + expect(lamb.isSafeInteger(Math.pow(2, 53))).toBe(false); + expect(lamb.isSafeInteger(-2e64)).toBe(false); + expect(lamb.isSafeInteger(Infinity)).toBe(false); + expect(lamb.isSafeInteger(-Infinity)).toBe(false); + expect(lamb.isSafeInteger(NaN)).toBe(false); + }); + + it("should return `false` for any non-number value and when it's called without arguments", function () { + nonNumbers.forEach(function (value) { + expect(lamb.isSafeInteger(value)).toBe(false); + }); + + expect(lamb.isSafeInteger()).toBe(false); + }); +}); diff --git a/src/math/__tests__/modulo.spec.js b/src/math/__tests__/modulo.spec.js new file mode 100644 index 0000000..7ec5666 --- /dev/null +++ b/src/math/__tests__/modulo.spec.js @@ -0,0 +1,9 @@ +import * as lamb from "../.."; + +describe("modulo", function () { + it("should calculate the modulo of two numbers", function () { + expect(lamb.modulo(5, 3)).toBe(2); + expect(lamb.modulo(-5, 3)).toBe(1); + expect(isNaN(lamb.modulo(-5, 0))).toBe(true); + }); +}); diff --git a/src/math/__tests__/multiply.spec.js b/src/math/__tests__/multiply.spec.js new file mode 100644 index 0000000..0123371 --- /dev/null +++ b/src/math/__tests__/multiply.spec.js @@ -0,0 +1,8 @@ +import * as lamb from "../.."; + +describe("multiply / multiplyBy", function () { + it("should multiply two numbers", function () { + expect(lamb.multiply(5, -3)).toBe(-15); + expect(lamb.multiplyBy(5)(-3)).toBe(-15); + }); +}); diff --git a/src/math/__tests__/randomInt.spec.js b/src/math/__tests__/randomInt.spec.js new file mode 100644 index 0000000..b8311dd --- /dev/null +++ b/src/math/__tests__/randomInt.spec.js @@ -0,0 +1,25 @@ +import * as lamb from "../.."; + +describe("randomInt", function () { + it("should generate a random integer between the min and max provided values", function () { + var n = lamb.randomInt(3, 42); + + expect(Math.floor(n)).toBe(n); + expect(n).toBeGreaterThan(2); + expect(n).toBeLessThan(43); + + var rndSpy = jest.spyOn(Math, "random").mockReturnValue(0.9999999999999999); + + expect(lamb.randomInt(0, 0)).toBe(0); + expect(lamb.randomInt(0, 1)).toBe(1); + expect(lamb.randomInt(0, 2)).toBe(2); + + rndSpy.mockReturnValue(0); + + expect(lamb.randomInt(0, 0)).toBe(0); + expect(lamb.randomInt(0, 1)).toBe(0); + expect(lamb.randomInt(0, 2)).toBe(0); + + rndSpy.mockRestore(); + }); +}); diff --git a/src/math/__tests__/range.spec.js b/src/math/__tests__/range.spec.js new file mode 100644 index 0000000..d5986c9 --- /dev/null +++ b/src/math/__tests__/range.spec.js @@ -0,0 +1,83 @@ +import * as lamb from "../.."; +import { zeroesAsNumbers } from "../../__tests__/commons"; + +describe("range", function () { + it("should generate an arithmetic progression of integers with the given parameters", function () { + expect(lamb.range(3, 15, 2)).toEqual([3, 5, 7, 9, 11, 13]); + expect(lamb.range(1, -10, -2)).toEqual([1, -1, -3, -5, -7, -9]); + }); + + it("should return an empty array if `start` isn't less than `limit` with a positive `step`", function () { + expect(lamb.range(5, 3, 1)).toEqual([]); + expect(lamb.range(1, -10, 2)).toEqual([]); + }); + + it("should return an empty array if `start` isn't greater than `limit` with a negative `step`", function () { + expect(lamb.range(3, 5, -1)).toEqual([]); + expect(lamb.range(-10, -1, -2)).toEqual([]); + }); + + it("should return an empty array if `step` is zero and `limit` equals `start`", function () { + expect(lamb.range(2, 2, 0)).toEqual([]); + }); + + it("should return an array containing `start` if `step` is zero and `limit` isn't equal to `start`", function () { + expect(lamb.range(2, 10, 0)).toEqual([2]); + }); + + it("should keep the sign of zeroes received as `start`", function () { + expect(lamb.range(0, 2, 1)).toEqual([0, 1]); + expect(lamb.range(-0, 2, 1)).toEqual([-0, 1]); + }); + + it("should convert the `start` parameter to number", function () { + zeroesAsNumbers.forEach(function (value) { + expect(lamb.range(value, 3)).toEqual([0, 1, 2]); + }); + + [[1], true, "1"].forEach(function (value) { + expect(lamb.range(value, 3)).toEqual([1, 2]); + }); + + [[1.5], "1.5"].forEach(function (value) { + expect(lamb.range(value, 3)).toEqual([1.5, 2.5]); + }); + }); + + it("should convert the `limit` parameter to number", function () { + zeroesAsNumbers.forEach(function (value) { + expect(lamb.range(-3, value)).toEqual([-3, -2, -1]); + }); + + [[1], true, "1"].forEach(function (value) { + expect(lamb.range(-1, value)).toEqual([-1, 0]); + }); + + [[1.5], "1.5"].forEach(function (value) { + expect(lamb.range(0, value, .5)).toEqual([0, 0.5, 1]); + }); + }); + + it("should use one as the default value of `step` if the parameter is missing", function () { + expect(lamb.range(2, 10)).toEqual([2, 3, 4, 5, 6, 7, 8, 9]); + }); + + it("should convert the `step` parameter to number", function () { + zeroesAsNumbers.forEach(function (value) { + expect(lamb.range(-3, 3, value)).toEqual([-3]); + expect(lamb.range(3, 3, value)).toEqual([]); + }); + + [[1], true, "1"].forEach(function (value) { + expect(lamb.range(-1, 1, value)).toEqual([-1, 0]); + }); + + [[.5], ".5"].forEach(function (value) { + expect(lamb.range(0, 1.5, value)).toEqual([0, 0.5, 1]); + }); + }); + + it("should return an empty array if called without parameters", function () { + expect(lamb.range()).toEqual([]); + }); +}); diff --git a/src/math/__tests__/remainder.spec.js b/src/math/__tests__/remainder.spec.js new file mode 100644 index 0000000..552ac99 --- /dev/null +++ b/src/math/__tests__/remainder.spec.js @@ -0,0 +1,9 @@ +import * as lamb from "../.."; + +describe("remainder", function () { + it("should calculate the remainder of the division of two numbers", function () { + expect(lamb.remainder(5, 3)).toBe(2); + expect(lamb.remainder(-5, 3)).toBe(-2); + expect(isNaN(lamb.remainder(-5, 0))).toBe(true); + }); +}); diff --git a/src/math/__tests__/subtract.spec.js b/src/math/__tests__/subtract.spec.js new file mode 100644 index 0000000..82bc0b6 --- /dev/null +++ b/src/math/__tests__/subtract.spec.js @@ -0,0 +1,8 @@ +import * as lamb from "../.."; + +describe("subtract / deduct", function () { + it("should subtract two numbers", function () { + expect(lamb.subtract(5, 7)).toBe(-2); + expect(lamb.deduct(7)(5)).toBe(-2); + }); +}); diff --git a/src/math/__tests__/sum.spec.js b/src/math/__tests__/sum.spec.js new file mode 100644 index 0000000..2caa956 --- /dev/null +++ b/src/math/__tests__/sum.spec.js @@ -0,0 +1,8 @@ +import * as lamb from "../.."; + +describe("add / sum", function () { + it("should add two numbers", function () { + expect(lamb.sum(2, -3)).toBe(-1); + expect(lamb.add(2)(-3)).toBe(-1); + }); +}); diff --git a/src/math/add.js b/src/math/add.js new file mode 100644 index 0000000..8dcc901 --- /dev/null +++ b/src/math/add.js @@ -0,0 +1,22 @@ +import _curry2 from "../privates/_curry2"; +import sum from "./sum"; + +/** + * A curried version of {@link module:lamb.sum|sum}. + * @example + * var add5 = _.add(5); + * + * _.add5(4) // => 9 + * _.add5(-2) // => 3 + * + * @memberof module:lamb + * @category Math + * @function + * @see {@link module:lamb.sum|sum} + * @since 0.1.0 + * @param {Number} a + * @returns {Function} + */ +var add = _curry2(sum, true); + +export default add; diff --git a/src/math/deduct.js b/src/math/deduct.js new file mode 100644 index 0000000..2256d7c --- /dev/null +++ b/src/math/deduct.js @@ -0,0 +1,23 @@ +import _curry2 from "../privates/_curry2"; +import subtract from "./subtract"; + +/** + * A curried version of {@link module:lamb.subtract|subtract} that expects the + * subtrahend to build a function waiting for the minuend. + * @example + * var deduct5 = _.deduct(5); + * + * deduct5(12) // => 7 + * deduct5(3) // => -2 + * + * @memberof module:lamb + * @category Math + * @function + * @see {@link module:lamb.subtract|subtract} + * @since 0.50.0 + * @param {Number} a + * @returns {Function} + */ +var deduct = _curry2(subtract, true); + +export default deduct; diff --git a/src/math/divide.js b/src/math/divide.js new file mode 100644 index 0000000..db241fd --- /dev/null +++ b/src/math/divide.js @@ -0,0 +1,18 @@ +/** + * Divides two numbers. + * @example + * _.divide(5, 2) // => 2.5 + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.divideBy|divideBy} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ +function divide (a, b) { + return a / b; +} + +export default divide; diff --git a/src/math/divideBy.js b/src/math/divideBy.js new file mode 100644 index 0000000..89eea67 --- /dev/null +++ b/src/math/divideBy.js @@ -0,0 +1,23 @@ +import _curry2 from "../privates/_curry2"; +import divide from "./divide"; + +/** + * A curried version of {@link module:lamb.divide|divide} that expects a divisor to + * build a function waiting for the dividend. + * @example + * var halve = divideBy(2); + * + * halve(10) // => 5 + * halve(5) // => 2.5 + * + * @memberof module:lamb + * @category Math + * @function + * @see {@link module:lamb.divide|divide} + * @since 0.50.0 + * @param {Number} a + * @returns {Function} + */ +var divideBy = _curry2(divide, true); + +export default divideBy; diff --git a/src/math/generate.js b/src/math/generate.js new file mode 100644 index 0000000..9b337bd --- /dev/null +++ b/src/math/generate.js @@ -0,0 +1,30 @@ +/** + * Generates a sequence of values of the desired length with the provided iteratee. + * The values being iterated, and received by the iteratee, are the results generated so far. + * @example + * var fibonacci = function (n, idx, results) { + * return n + (results[idx - 1] || 0); + * }; + * + * _.generate(1, 10, fibonacci) // => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.range|range} + * @since 0.21.0 + * @param {*} start - The starting value + * @param {Number} len - The desired length for the sequence + * @param {ListIteratorCallback} iteratee + * @returns {Array} + */ +function generate (start, len, iteratee) { + var result = [start]; + + for (var i = 0, limit = len - 1; i < limit; i++) { + result.push(iteratee(result[i], i, result)); + } + + return result; +} + +export default generate; diff --git a/src/math/isFinite.js b/src/math/isFinite.js new file mode 100644 index 0000000..cce3414 --- /dev/null +++ b/src/math/isFinite.js @@ -0,0 +1,26 @@ +import type from "../core/type"; + +/** + * Verifies whether the received value is a finite number.
+ * Behaves almost as a shim of ES6's [Number.isFinite]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite}, + * but with a difference: it will return true even for Number object's instances. + * @example + * _.isFinite(5) // => true + * _.isFinite(new Number(5)) // => true + * _.isFinite(Infinity) // => false + * _.isFinite(-Infinity) // => false + * _.isFinite("5") // => false + * _.isFinite(NaN) // => false + * _.isFinite(null) // => false + * + * @alias module:lamb.isFinite + * @category Math + * @since 0.46.0 + * @param {*} value + * @returns {Boolean} + */ +function isFinite_ (value) { + return type(value) === "Number" && isFinite(value); +} + +export default isFinite_; diff --git a/src/math/isInteger.js b/src/math/isInteger.js new file mode 100644 index 0000000..721fd89 --- /dev/null +++ b/src/math/isInteger.js @@ -0,0 +1,27 @@ +import type from "../core/type"; + +/** + * Verifies whether the received value is a number and an integer. + * Behaves almost as a shim of ES6's [Number.isInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger}, + * but with a difference: it will return true even for Number object's instances. + * @example + * _.isInteger(5) // => true + * _.isInteger(new Number(5)) // => true + * _.isInteger(2.5) // => false + * _.isInteger(Infinity) // => false + * _.isInteger(-Infinity) // => false + * _.isInteger("5") // => false + * _.isInteger(NaN) // => false + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.isSafeInteger|isSafeInteger} + * @since 0.46.0 + * @param {*} value + * @returns {Boolean} + */ +function isInteger (value) { + return type(value) === "Number" && value % 1 === 0; +} + +export default isInteger; diff --git a/src/math/isSafeInteger.js b/src/math/isSafeInteger.js new file mode 100644 index 0000000..35f8fac --- /dev/null +++ b/src/math/isSafeInteger.js @@ -0,0 +1,34 @@ +import { MAX_SAFE_INTEGER } from "../privates/_constants"; +import isInteger from "./isInteger"; + +/** + * Verifies whether the received value is a "safe integer", meaning that is a number and that + * can be exactly represented as an IEEE-754 double precision number. + * The safe integers consist of all integers from -(253 - 1) inclusive to + * 253 - 1 inclusive.
+ * Behaves almost as a shim of ES6's [Number.isSafeInteger]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger}, + * but with a difference: it will return true even for Number object's instances. + * @example + * _.isSafeInteger(5) // => true + * _.isSafeInteger(new Number(5)) // => true + * _.isSafeInteger(Math.pow(2, 53) - 1) // => true + * _.isSafeInteger(Math.pow(2, 53)) // => false + * _.isSafeInteger(2e32) // => false + * _.isSafeInteger(2.5) // => false + * _.isSafeInteger(Infinity) // => false + * _.isSafeInteger(-Infinity) // => false + * _.isSafeInteger("5") // => false + * _.isSafeInteger(NaN) // => false + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.isInteger|isInteger} + * @since 0.46.0 + * @param {*} value + * @returns {Boolean} + */ +function isSafeInteger (value) { + return isInteger(value) && Math.abs(value) <= MAX_SAFE_INTEGER; +} + +export default isSafeInteger; diff --git a/src/math/modulo.js b/src/math/modulo.js new file mode 100644 index 0000000..f96b7cf --- /dev/null +++ b/src/math/modulo.js @@ -0,0 +1,27 @@ +/** + * Performs the modulo operation and should not be confused with the + * {@link module:lamb.remainder|remainder}. + * The function performs a floored division to calculate the result and not + * a truncated one, hence the sign of the dividend is not kept, unlike the + * {@link module:lamb.remainder|remainder}. + * @example + * _.modulo(5, 3) // => 2 + * _.remainder(5, 3) // => 2 + * + * _.modulo(-5, 3) // => 1 + * _.remainder(-5, 3) // => -2 + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.remainder|remainder} + * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ +function modulo (a, b) { + return a - (b * Math.floor(a / b)); +} + +export default modulo; diff --git a/src/math/multiply.js b/src/math/multiply.js new file mode 100644 index 0000000..690eda6 --- /dev/null +++ b/src/math/multiply.js @@ -0,0 +1,18 @@ +/** + * Multiplies two numbers. + * @example + * _.multiply(5, 3) // => 15 + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.multiplyBy|multiplyBy} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ +function multiply (a, b) { + return a * b; +} + +export default multiply; diff --git a/src/math/multiplyBy.js b/src/math/multiplyBy.js new file mode 100644 index 0000000..aa0face --- /dev/null +++ b/src/math/multiplyBy.js @@ -0,0 +1,21 @@ +import _curry2 from "../privates/_curry2"; +import multiply from "./multiply"; + +/** + * A curried version of {@link module:lamb.multiply|multiply}. + * @example + * var double = _.multiplyBy(2); + * + * double(5) // => 10 + * + * @memberof module:lamb + * @category Math + * @function + * @see {@link module:lamb.multiply|multiply} + * @since 0.50.0 + * @param {Number} a + * @returns {Function} + */ +var multiplyBy = _curry2(multiply, true); + +export default multiplyBy; diff --git a/src/math/randomInt.js b/src/math/randomInt.js new file mode 100644 index 0000000..30b18fa --- /dev/null +++ b/src/math/randomInt.js @@ -0,0 +1,21 @@ +/** + * Generates a random integer between two given integers, both included. + * Note that no safety measure is taken if the provided arguments aren't integers, so + * you may end up with unexpected (not really) results. + * For example randomInt(0.1, 1.2) could be 2. + * @example + * + * _.randomInt(1, 10) // => an integer >=1 && <= 10 + * + * @memberof module:lamb + * @category Math + * @since 0.1.0 + * @param {Number} min + * @param {Number} max + * @returns {Number} + */ +function randomInt (min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +export default randomInt; diff --git a/src/math/range.js b/src/math/range.js new file mode 100644 index 0000000..a81e6c6 --- /dev/null +++ b/src/math/range.js @@ -0,0 +1,48 @@ +import _forceToNumber from "../privates/_forceToNumber"; + +/** + * Generates an arithmetic progression of numbers starting from start up to, + * but not including, limit, using the given step. + * @example + * _.range(2, 10) // => [2, 3, 4, 5, 6, 7, 8, 9] + * _.range(1, -10, -2) // => [1, -1, -3, -5, -7, -9] + * _.range(0, 3, 1) // => [0, 1, 2] + * _.range(-0, 3, 1) // => [-0, 1, 2] + * _.range(1, -10, 2) // => [] + * _.range(3, 5, -1) // => [] + * + * @example Behaviour if step happens to be zero: + * _.range(2, 10, 0) // => [2] + * _.range(2, -10, 0) // => [2] + * _.range(2, 2, 0) // => [] + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.generate|generate} + * @since 0.1.0 + * @param {Number} start + * @param {Number} limit + * @param {Number} [step=1] + * @returns {Number[]} + */ +function range (start, limit, step) { + start = _forceToNumber(start); + limit = _forceToNumber(limit); + step = arguments.length === 3 ? _forceToNumber(step) : 1; + + if (step === 0) { + return limit === start ? [] : [start]; + } + + var len = Math.max(Math.ceil((limit - start) / step), 0); + var result = Array(len); + + for (var i = 0, last = start; i < len; i++) { + result[i] = last; + last += step; + } + + return result; +} + +export default range; diff --git a/src/math/remainder.js b/src/math/remainder.js new file mode 100644 index 0000000..d465f95 --- /dev/null +++ b/src/math/remainder.js @@ -0,0 +1,24 @@ +/** + * Gets the remainder of the division of two numbers. + * Not to be confused with the {@link module:lamb.modulo|modulo} as the remainder + * keeps the sign of the dividend and may lead to some unexpected results. + * @example + * // example of wrong usage of the remainder + * // (in this case the modulo operation should be used) + * var isOdd = function (n) { return _.remainder(n, 2) === 1; }; + * isOdd(-3) // => false as -3 % 2 === -1 + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.modulo|modulo} + * @see [Modulo operation on Wikipedia]{@link http://en.wikipedia.org/wiki/Modulo_operation} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ +function remainder (a, b) { + return a % b; +} + +export default remainder; diff --git a/src/math/subtract.js b/src/math/subtract.js new file mode 100644 index 0000000..877657a --- /dev/null +++ b/src/math/subtract.js @@ -0,0 +1,18 @@ +/** + * Subtracts two numbers. + * @example + * _.subtract(5, 3) // => 2 + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.deduct|deduct} + * @since 0.1.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ +function subtract (a, b) { + return a - b; +} + +export default subtract; diff --git a/src/math/sum.js b/src/math/sum.js new file mode 100644 index 0000000..6d329f0 --- /dev/null +++ b/src/math/sum.js @@ -0,0 +1,18 @@ +/** + * Sums two numbers. + * @example + * _.sum(4, 5) // => 9 + * + * @memberof module:lamb + * @category Math + * @see {@link module:lamb.add|add} + * @since 0.50.0 + * @param {Number} a + * @param {Number} b + * @returns {Number} + */ +function sum (a, b) { + return a + b; +} + +export default sum; diff --git a/src/object.js b/src/object.js deleted file mode 100644 index f494b86..0000000 --- a/src/object.js +++ /dev/null @@ -1,710 +0,0 @@ -/** - * Creates an array with all the enumerable properties of the given object. - * @example Showing the difference with {@link module:lamb.keys|keys}: - * var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3}, - * d: {value: 4, enumerable: true} - * }); - * - * _.keys(foo) // => ["d"] - * _.enumerables(foo) // => ["d", "a"] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.keys|keys} - * @since 0.12.0 - * @param {Object} obj - * @returns {String[]} - */ -var enumerables = _unsafeKeyListFrom(_safeEnumerables); - -/** - * Builds an object from a list of key / value pairs like the one - * returned by {@link module:lamb.pairs|pairs} or {@link module:lamb.ownPairs|ownPairs}.
- * In case of duplicate keys the last key / value pair is used. - * @example - * _.fromPairs([["a", 1], ["b", 2], ["c", 3]]) // => {"a": 1, "b": 2, "c": 3} - * _.fromPairs([["a", 1], ["b", 2], ["a", 3]]) // => {"a": 3, "b": 2} - * _.fromPairs([[1], [void 0, 2], [null, 3]]) // => {"1": undefined, "undefined": 2, "null": 3} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.ownPairs|ownPairs}, {@link module:lamb.pairs|pairs} - * @since 0.8.0 - * @param {Array>} pairsList - * @returns {Object} - */ -function fromPairs (pairsList) { - var result = {}; - - forEach(pairsList, function (pair) { - result[pair[0]] = pair[1]; - }); - - return result; -} - -/** - * Makes an object immutable by recursively calling [Object.freeze]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze} - * on its members.
- * Any attempt to extend or modify the object can throw a TypeError or fail silently, - * depending on the environment and the [strict mode]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode} directive. - * @example - * var user = _.immutable({ - * name: "John", - * surname: "Doe", - * login: { - * username: "jdoe", - * password: "abc123" - * }, - * luckyNumbers: [13, 17] - * }); - * - * // All of these statements will fail and possibly - * // throw a TypeError (see the function description) - * user.name = "Joe"; - * delete user.name; - * user.newProperty = []; - * user.login.password = "foo"; - * user.luckyNumbers.push(-13); - * - * @memberof module:lamb - * @category Object - * @since 0.8.0 - * @param {Object} obj - * @returns {Object} - */ -function immutable (obj) { - return _immutable(obj, []); -} - -/** - * Retrieves the list of the own enumerable properties of an object.
- * Although [Object.keys]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys} - * is already present in ECMAScript 5, its behaviour changed in the subsequent specifications - * of the standard.
- * This function shims the ECMAScript 6 version, by forcing a conversion to - * object for any value but null and undefined. - * @example Showing the difference with {@link module:lamb.enumerables|enumerables}: - * var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3}, - * d: {value: 4, enumerable: true} - * }); - * - * _.enumerables(foo) // => ["d", "a"] - * _.keys(foo) // => ["d"] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.enumerables|enumerables} - * @since 0.25.1 - * @param {Object} obj - * @returns {String[]} - */ -var keys = _unsafeKeyListFrom(_safeKeys); - -/** - * Builds an object from the two given lists, using the first one as keys and the last - * one as values.
- * If the list of keys is longer than the values one, the keys will be created with - * undefined values.
- * If more values than keys are supplied, the extra values will be ignored. - * @example - * _.make(["a", "b", "c"], [1, 2, 3]) // => {a: 1, b: 2, c: 3} - * _.make(["a", "b", "c"], [1, 2]) // => {a: 1, b: 2, c: undefined} - * _.make(["a", "b"], [1, 2, 3]) // => {a: 1, b: 2} - * _.make([null, void 0, 2], [1, 2, 3]) // => {"null": 1, "undefined": 2, "2": 3} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.tear|tear}, {@link module:lamb.tearOwn|tearOwn} for the reverse operation - * @since 0.8.0 - * @param {String[]} names - * @param {ArrayLike} values - * @returns {Object} - */ -function make (names, values) { - var result = {}; - var valuesLen = values.length; - - for (var i = 0, len = names.length; i < len; i++) { - result[names[i]] = i < valuesLen ? values[i] : void 0; - } - - return result; -} - -/** - * Creates a new object by applying the given function - * to all enumerable properties of the source one. - * @example - * var weights = { - * john: "72.5 Kg", - * jane: "52.3 Kg" - * }; - * - * _.mapValues(weights, parseFloat) // => {john: 72.5, jane: 52.3} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.mapValuesWith|mapValuesWith} - * @since 0.54.0 - * @param {Object} source - * @param {ObjectIteratorCallback} fn - * @returns {Object} - */ -function mapValues (source, fn) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "object"); - } - - var result = {}; - - for (var key in source) { - result[key] = fn(source[key], key, source); - } - - return result; -} - -/** - * A curried version of {@link module:lamb.mapValues|mapValues}.
- * Expects a mapping function to build a new function waiting for the - * object to act upon. - * @example - * var incValues = _.mapValuesWith(_.add(1)); - * var results = { - * first: 10, - * second: 5, - * third: 3 - * }; - * - * incValues(results) // => {first: 11, second: 6, third: 4} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.mapValues|mapValues} - * @since 0.54.0 - * @function - * @param {ObjectIteratorCallback} fn - * @returns {Function} - */ -var mapValuesWith = _curry2(mapValues, true); - -/** - * Merges the enumerable properties of the provided sources into a new object.
- * In case of key homonymy the last source has precedence over the first. - * @example - * _.merge({a: 1, b: 3}, {b: 5, c: 4}) // => {a: 1, b: 5, c: 4} - * - * @example Array-like objects will be transformed to objects with numbers as keys: - * _.merge([1, 2], {a: 2}) // => {"0": 1, "1": 2, a: 2} - * _.merge("foo", {a: 2}) // => {"0": "f", "1": "o", "2": "o", a: 2} - * - * @example Every other non-nil value will be treated as an empty object: - * _.merge({a: 2}, 99) // => {a: 2} - * _.merge({a: 2}, NaN) // => {a: 2} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.mergeOwn|mergeOwn} to merge own properties only - * @since 0.10.0 - * @function - * @param {Object} a - * @param {Object} b - * @returns {Object} - */ -var merge = partial(_merge, [enumerables]); - -/** - * Same as {@link module:lamb.merge|merge}, but only the own properties of the - * sources are taken into account. - * @example Showing the difference with merge: - * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3, enumerable: true} - * }); - * - * var bar = {d: 4}; - * - * _.merge(foo, bar) // => {a: 1, b: 2, c: 3, d: 4} - * _.mergeOwn(foo, bar) // => {c: 3, d: 4} - * - * @example Array-like objects will be transformed to objects with numbers as keys: - * _.mergeOwn([1, 2], {a: 2}) // => {"0": 1, "1": 2, a: 2} - * _.mergeOwn("foo", {a: 2}) // => {"0": "f", "1": "o", "2": "o", a: 2} - * - * @example Every other non-nil value will be treated as an empty object: - * _.mergeOwn({a: 2}, 99) // => {a: 2} - * _.mergeOwn({a: 2}, NaN) // => {a: 2} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.merge|merge} to merge all enumerable properties - * @since 0.12.0 - * @function - * @param {Object} a - * @param {Object} b - * @returns {Object} - */ -var mergeOwn = partial(_merge, [keys]); - -/** - * Same as {@link module:lamb.pairs|pairs}, but only the own enumerable properties of the object are - * taken into account.
- * See also {@link module:lamb.fromPairs|fromPairs} for the reverse operation. - * @example Showing the difference with pairs: - * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3, enumerable: true} - * }); - * - * _.pairs(foo) // => [["c", 3], ["b", 2], ["a", 1]] - * _.ownPairs(foo) // => [["c", 3]] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.pairs|pairs} - * @see {@link module:lamb.fromPairs|fromPairs} - * @since 0.12.0 - * @param {Object} obj - * @returns {Array>} - */ -var ownPairs = _pairsFrom(keys); - -/** - * Same as {@link module:lamb.values|values}, but only the own enumerable properties of the object are - * taken into account.
- * @example Showing the difference with values: - * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3, enumerable: true} - * }); - * - * _.values(foo) // => [3, 2, 1] - * _.ownValues(foo) // => [3] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.values|values} - * @since 0.12.0 - * @param {Object} obj - * @returns {Array} - */ -var ownValues = _valuesFrom(keys); - -/** - * Converts an object into an array of key / value pairs of its enumerable properties.
- * See also {@link module:lamb.ownPairs|ownPairs} for picking only the own enumerable - * properties and {@link module:lamb.fromPairs|fromPairs} for the reverse operation. - * @example - * _.pairs({a: 1, b: 2, c: 3}) // => [["a", 1], ["b", 2], ["c", 3]] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.ownPairs|ownPairs} - * @see {@link module:lamb.fromPairs|fromPairs} - * @since 0.8.0 - * @param {Object} obj - * @returns {Array>} - */ -var pairs = _pairsFrom(enumerables); - -/** - * Returns an object containing only the specified properties of the given object.
- * Non existent properties will be ignored. - * @example - * var user = {name: "john", surname: "doe", age: 30}; - * - * _.pick(user, ["name", "age"]) // => {"name": "john", "age": 30}; - * _.pick(user, ["name", "email"]) // => {"name": "john"} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.pickIf|pickIf}, {@link module:lamb.pickKeys|pickKeys} - * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf} - * @since 0.1.0 - * @param {Object} source - * @param {String[]} whitelist - * @returns {Object} - */ -function pick (source, whitelist) { - var result = {}; - - for (var i = 0, len = whitelist.length, key; i < len; i++) { - key = whitelist[i]; - - if (has(source, key)) { - result[key] = source[key]; - } - } - - return result; -} - -/** - * Builds a function expecting an object whose enumerable properties will be checked - * against the given predicate.
- * The properties satisfying the predicate will be included in the resulting object. - * @example - * var user = {name: "john", surname: "doe", age: 30}; - * var pickIfIsString = _.pickIf(_.isType("String")); - * - * pickIfIsString(user) // => {name: "john", surname: "doe"} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys} - * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys}, - * {@link module:lamb.skipIf|skipIf} - * @since 0.1.0 - * @param {ObjectIteratorCallback} predicate - * @returns {Function} - */ -function pickIf (predicate) { - return function (source) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "object"); - } - - var result = {}; - - for (var key in source) { - if (predicate(source[key], key, source)) { - result[key] = source[key]; - } - } - - return result; - }; -} - -/** - * A curried version of {@link module:lamb.pick|pick}, expecting a whitelist of keys to build - * a function waiting for the object to act upon. - * @example - * var user = {id: 1, name: "Jane", surname: "Doe", active: false}; - * var getUserInfo = _.pickKeys(["id", "active"]); - * - * getUserInfo(user) // => {id: 1, active: false} - * - * @example A useful composition with mapWith: - * var users = [ - * {id: 1, name: "Jane", surname: "Doe", active: false}, - * {id: 2, name: "John", surname: "Doe", active: true}, - * {id: 3, name: "Mario", surname: "Rossi", active: true}, - * {id: 4, name: "Paolo", surname: "Bianchi", active: false} - * ]; - * var select = _.compose(_.mapWith, _.pickKeys); - * var selectUserInfo = select(["id", "active"]); - * - * selectUserInfo(users) // => - * // [ - * // {id: 1, active: false}, - * // {id: 2, active: true}, - * // {id: 3, active: true}, - * // {id: 4, active: false} - * // ] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickIf|pickIf} - * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys}, - * {@link module:lamb.skipIf|skipIf} - * @since 0.35.0 - * @param {String[]} whitelist - * @returns {Function} - */ -var pickKeys = _curry2(pick, true); - -/** - * Creates a copy of the given object with its enumerable keys renamed as - * indicated in the provided lookup table. - * @example - * var person = {"firstName": "John", "lastName": "Doe"}; - * var keysMap = {"firstName": "name", "lastName": "surname"}; - * - * _.rename(person, keysMap) // => {"name": "John", "surname": "Doe"} - * - * @example It's safe using it to swap keys: - * var keysMap = {"firstName": "lastName", "lastName": "firstName"}; - * - * _.rename(person, keysMap) // => {"lastName": "John", "firstName": "Doe"} - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.renameKeys|renameKeys}, {@link module:lamb.renameWith|renameWith} - * @since 0.26.0 - * @param {Object} source - * @param {Object} keysMap - * @returns {Object} - */ -function rename (source, keysMap) { - keysMap = Object(keysMap); - var result = {}; - var oldKeys = enumerables(source); - - for (var prop in keysMap) { - if (~oldKeys.indexOf(prop)) { - result[keysMap[prop]] = source[prop]; - } - } - - for (var i = 0, len = oldKeys.length, key; i < len; i++) { - key = oldKeys[i]; - - if (!(key in keysMap || key in result)) { - result[key] = source[key]; - } - } - - return result; -} - -/** - * A curried version of {@link module:lamb.rename|rename} expecting a - * keysMap to build a function waiting for the object to act upon. - * @example - * var persons = [ - * {"firstName": "John", "lastName": "Doe"}, - * {"first_name": "Mario", "last_name": "Rossi"}, - * ]; - * var normalizeKeys = _.renameKeys({ - * "firstName": "name", - * "first_name": "name", - * "lastName": "surname", - * "last_name": "surname" - * }); - * - * _.map(persons, normalizeKeys) // => - * // [ - * // {"name": "John", "surname": "Doe"}, - * // {"name": "Mario", "surname": "Rossi"} - * // ] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameWith|renameWith} - * @since 0.26.0 - * @param {Object} keysMap - * @returns {Function} - */ -var renameKeys = _curry2(rename, true); - -/** - * Uses the provided function as a keysMap generator and returns - * a function expecting the object whose keys we want to {@link module:lamb.rename|rename}. - * @example - * var person = {"NAME": "John", "SURNAME": "Doe"}; - * var arrayToLower = _.mapWith(_.invoker("toLowerCase")); - * var makeLowerKeysMap = function (source) { - * var sourceKeys = _.keys(source); - * - * return _.make(sourceKeys, arrayToLower(sourceKeys)); - * }; - * var lowerKeysFor = _.renameWith(makeLowerKeysMap); - * - * lowerKeysFor(person) // => {"name": "John", "surname": "doe"}; - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameKeys|renameKeys} - * @since 0.26.0 - * @param {Function} fn - * @returns {Function} - */ -function renameWith (fn) { - return function (source) { - return rename(source, fn(source)); - }; -} - -/** - * Returns a copy of the source object without the specified properties. - * @example - * var user = {name: "john", surname: "doe", age: 30}; - * - * _.skip(user, ["name", "age"]) // => {surname: "doe"}; - * _.skip(user, ["name", "email"]) // => {surname: "doe", age: 30}; - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.skipKeys|skipKeys}, {@link module:lamb.skipIf|skipIf} - * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, - * {@link module:lamb.pickIf|pickIf} - * @since 0.1.0 - * @param {Object} source - * @param {String[]} blacklist - * @returns {Object} - */ -function skip (source, blacklist) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "object"); - } - - var result = {}; - var props = make(blacklist, []); - - for (var key in source) { - if (!(key in props)) { - result[key] = source[key]; - } - } - - return result; -} - -/** - * Builds a function expecting an object whose enumerable properties will be checked - * against the given predicate.
- * The properties satisfying the predicate will be omitted in the resulting object. - * @example - * var user = {name: "john", surname: "doe", age: 30}; - * var skipIfIstring = _.skipIf(_.isType("String")); - * - * skipIfIstring(user) // => {age: 30} - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys} - * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, - * {@link module:lamb.pickIf|pickIf} - * @since 0.1.0 - * @param {ObjectIteratorCallback} predicate - * @returns {Function} - */ -var skipIf = compose(pickIf, not); - -/** - * A curried version of {@link module:lamb.skip|skip}, expecting a blacklist of keys to build - * a function waiting for the object to act upon. - * @example - * var user = {id: 1, name: "Jane", surname: "Doe", active: false}; - * var getUserInfo = _.skipKeys(["name", "surname"]); - * - * getUserInfo(user) // => {id: 1, active: false} - * - * @example A useful composition with mapWith: - * var users = [ - * {id: 1, name: "Jane", surname: "Doe", active: false}, - * {id: 2, name: "John", surname: "Doe", active: true}, - * {id: 3, name: "Mario", surname: "Rossi", active: true}, - * {id: 4, name: "Paolo", surname: "Bianchi", active: false} - * ]; - * var discard = _.compose(_.mapWith, _.skipKeys); - * var discardNames = discard(["name", "surname"]); - * - * discardNames(users) // => - * // [ - * // {id: 1, active: false}, - * // {id: 2, active: true}, - * // {id: 3, active: true}, - * // {id: 4, active: false} - * // ] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf} - * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, - * {@link module:lamb.pickIf|pickIf} - * @since 0.35.0 - * @param {String[]} blacklist - * @returns {Function} - */ -var skipKeys = _curry2(skip, true); - -/** - * Tears an object apart by transforming it in an array of two lists: one containing - * its enumerable keys, the other containing the corresponding values.
- * Although this "tearing apart" may sound as a rather violent process, the source - * object will be unharmed. - * @example - * _.tear({a: 1, b: 2, c: 3}) // => [["a", "b", "c"], [1, 2, 3]] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.tearOwn|tearOwn} - * @see {@link module:lamb.make|make} for the reverse operation - * @since 0.8.0 - * @param {Object} obj - * @returns {Array} - */ -var tear = _tearFrom(enumerables); - -/** - * Same as {@link module:lamb.tear|tear}, but only the own properties of the object are - * taken into account. - * @example Showing the difference with tear: - * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - * var foo = Object.create(baseFoo, { - * c: {value: 3, enumerable: true} - * }); - * - * _.tear(foo) // => [["c", "b", "a"], [3, 2, 1]] - * _.tearOwn(foo) // => [["c"], [3]] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.tear|tear} - * @see {@link module:lamb.make|make} for the reverse operation - * @since 0.12.0 - * @param {Object} obj - * @returns {Array} - */ -var tearOwn = _tearFrom(keys); - -/** - * Generates an array with the values of the enumerable properties of the given object.
- * See also {@link module:lamb.ownValues|ownValues} to pick only from the own properties of the object. - * @example - * var user = {name: "john", surname: "doe", age: 30}; - * - * _.values(user) // => ["john", "doe", 30] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.ownValues|ownValues} - * @since 0.1.0 - * @param {Object} obj - * @returns {Array} - */ -var values = _valuesFrom(enumerables); - -lamb.enumerables = enumerables; -lamb.fromPairs = fromPairs; -lamb.immutable = immutable; -lamb.keys = keys; -lamb.make = make; -lamb.mapValues = mapValues; -lamb.mapValuesWith = mapValuesWith; -lamb.merge = merge; -lamb.mergeOwn = mergeOwn; -lamb.ownPairs = ownPairs; -lamb.ownValues = ownValues; -lamb.pairs = pairs; -lamb.pick = pick; -lamb.pickIf = pickIf; -lamb.pickKeys = pickKeys; -lamb.rename = rename; -lamb.renameKeys = renameKeys; -lamb.renameWith = renameWith; -lamb.skip = skip; -lamb.skipIf = skipIf; -lamb.skipKeys = skipKeys; -lamb.tear = tear; -lamb.tearOwn = tearOwn; -lamb.values = values; diff --git a/src/object/__tests__/enumerables.spec.js b/src/object/__tests__/enumerables.spec.js new file mode 100644 index 0000000..cf52029 --- /dev/null +++ b/src/object/__tests__/enumerables.spec.js @@ -0,0 +1,39 @@ +import * as lamb from "../.."; +import { wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("enumerables", function () { + it("should build an array with all the enumerables keys of an object", function () { + var baseFoo = Object.create({ a: 1 }, { b: { value: 2 } }); + var foo = Object.create(baseFoo, { + c: { value: 3 }, + d: { value: 4, enumerable: true } + }); + + expect(lamb.enumerables(foo)).toEqual(["d", "a"]); + }); + + it("should work with arrays and array-like objects", function () { + expect(lamb.enumerables([1, 2, 3])).toEqual(["0", "1", "2"]); + expect(lamb.enumerables("abc")).toEqual(["0", "1", "2"]); + }); + + it("should retrieve only defined keys in sparse arrays", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + expect(lamb.enumerables([, 5, 6, ,])).toStrictEqual(["1", "2"]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.enumerables).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.enumerables(null); }).toThrow(); + expect(function () { lamb.enumerables(void 0); }).toThrow(); + }); + + it("should consider other values as empty objects", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.enumerables(value)).toEqual([]); + }); + }); +}); diff --git a/src/object/__tests__/fromPairs.spec.js b/src/object/__tests__/fromPairs.spec.js new file mode 100644 index 0000000..36de7f8 --- /dev/null +++ b/src/object/__tests__/fromPairs.spec.js @@ -0,0 +1,54 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("fromPairs", function () { + it("should build an object from a list of key / value pairs", function () { + expect(lamb.fromPairs([["a", 1], ["b", 2], ["c", 3]])).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it("should use the last key / value pair in case of duplicate keys", function () { + expect(lamb.fromPairs([["a", 1], ["b", 2], ["a", 3]])).toEqual({ a: 3, b: 2 }); + }); + + it("should convert missing or non-string keys to strings and missing values to `undefined`", function () { + /* eslint-disable comma-spacing, no-sparse-arrays */ + var pairs = [[1], [void 0, 2], [null, 3], ["z", ,]]; + var result = { 1: void 0, undefined: 2, null: 3, z: void 0 }; + + expect(lamb.fromPairs(pairs)).toStrictEqual(result); + expect(lamb.fromPairs([[, 4]])).toStrictEqual({ undefined: 4 }); + /* eslint-enable comma-spacing, no-sparse-arrays */ + }); + + it("should return an empty object if supplied with an empty array", function () { + expect(lamb.fromPairs([])).toEqual({}); + }); + + it("should accept array-like objects as pairs", function () { + expect(lamb.fromPairs(["a1", "b2"])).toEqual({ a: "1", b: "2" }); + }); + + it("should try to retrieve pairs from array-like objects", function () { + expect(lamb.fromPairs("foo")).toEqual({ f: void 0, o: void 0 }); + }); + + it("should throw an exception if any of the pairs is `nil`", function () { + expect(function () { lamb.fromPairs([["a", 1], null, ["c", 3]]); }).toThrow(); + expect(function () { lamb.fromPairs([["a", 1], void 0, ["c", 3]]); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.fromPairs).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.fromPairs(null); }).toThrow(); + expect(function () { lamb.fromPairs(void 0); }).toThrow(); + }); + + it("should treat every other value as an empty array and return an empty object", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.fromPairs(value)).toEqual({}); + }); + }); +}); diff --git a/src/object/__tests__/getIn.spec.js b/src/object/__tests__/getIn.spec.js new file mode 100644 index 0000000..b4a3904 --- /dev/null +++ b/src/object/__tests__/getIn.spec.js @@ -0,0 +1,90 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("getIn / getKey", function () { + var obj = { foo: 1, bar: 2, baz: 3 }; + + Object.defineProperty(obj, "qux", { value: 4 }); + + it("should return the value of the given object property", function () { + expect(lamb.getIn(obj, "bar")).toBe(2); + expect(lamb.getKey("foo")(obj)).toBe(1); + }); + + it("should return `undefined` for a non-existent property", function () { + expect(lamb.getIn(obj, "a")).toBeUndefined(); + expect(lamb.getKey("z")(obj)).toBeUndefined(); + }); + + it("should be able to retrieve non-enumerable properties", function () { + expect(lamb.getIn(obj, "qux")).toBe(4); + expect(lamb.getKey("qux")(obj)).toBe(4); + }); + + it("should accept integers as keys and accept array-like objects", function () { + var o = { 1: "a", 2: "b" }; + var arr = [1, 2, 3, 4]; + var s = "abcd"; + + expect(lamb.getIn(o, 1)).toBe("a"); + expect(lamb.getKey(2)(o)).toBe("b"); + expect(lamb.getIn(arr, 1)).toBe(2); + expect(lamb.getKey(2)(arr)).toBe(3); + expect(lamb.getIn(s, 1)).toBe("b"); + expect(lamb.getKey(2)(s)).toBe("c"); + }); + + it("should work with sparse arrays", function () { + var sparseArr = Array(3); + + sparseArr[1] = 99; + + expect(lamb.getIn(sparseArr, 1)).toBe(99); + expect(lamb.getIn(sparseArr, 2)).toBeUndefined(); + expect(lamb.getKey(1)(sparseArr)).toBe(99); + expect(lamb.getKey(2)(sparseArr)).toBeUndefined(); + }); + + it("should convert other values for the `key` parameter to string", function () { + var values = lamb.range(0, nonStringsAsStrings.length, 1); + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + var value = values[nonStringsAsStrings.indexOf(String(key))]; + + expect(lamb.getIn(testObj, key)).toBe(value); + expect(lamb.getKey(key)(testObj)).toBe(value); + }); + + var idx = nonStringsAsStrings.indexOf("undefined"); + + expect(lamb.getIn(testObj)).toBe(idx); + expect(lamb.getKey()(testObj)).toBe(idx); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.getIn).toThrow(); + expect(lamb.getKey("a")).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.getIn(null, "a"); }).toThrow(); + expect(function () { lamb.getIn(void 0, "a"); }).toThrow(); + expect(function () { lamb.getKey("a")(null); }).toThrow(); + expect(function () { lamb.getKey("a")(void 0); }).toThrow(); + }); + + it("should return convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.getIn(v, "a")).toBeUndefined(); + expect(lamb.getKey("a")(v)).toBeUndefined(); + }); + + expect(lamb.getIn(/foo/, "lastIndex")).toBe(0); + expect(lamb.getKey("lastIndex")(/foo/)).toBe(0); + }); +}); diff --git a/src/object/__tests__/getPath.spec.js b/src/object/__tests__/getPath.spec.js new file mode 100644 index 0000000..d1ea150 --- /dev/null +++ b/src/object/__tests__/getPath.spec.js @@ -0,0 +1,153 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("getPath / getPathIn", function () { + var obj = { a: 2, b: { a: 3, b: [4, 5], c: "foo" }, "c.d": { "e.f": 6 } }; + + obj.b.d = Array(3); + obj.b.d[1] = 99; + + Object.defineProperty(obj, "e", { value: 10 }); + obj.f = Object.create({}, { g: { value: 20 } }); + + it("should retrieve a nested object property using the supplied path", function () { + expect(lamb.getPath("a")(obj)).toBe(2); + expect(lamb.getPath("b.a")(obj)).toBe(3); + expect(lamb.getPath("b.b")(obj)).toBe(obj.b.b); + expect(lamb.getPathIn(obj, "a")).toBe(2); + expect(lamb.getPathIn(obj, "b.a")).toBe(3); + expect(lamb.getPathIn(obj, "b.b")).toBe(obj.b.b); + }); + + it("should be able to access non-enumerable properties", function () { + expect(lamb.getPath("e")(obj)).toBe(10); + expect(lamb.getPathIn(obj, "e")).toBe(10); + expect(lamb.getPath("f.g")(obj)).toBe(20); + expect(lamb.getPathIn(obj, "f.g")).toBe(20); + }); + + it("should be able to retrieve values from arrays and array-like objects", function () { + expect(lamb.getPath("b.b.0")(obj)).toBe(4); + expect(lamb.getPath("b.c.0")(obj)).toBe("f"); + expect(lamb.getPathIn(obj, "b.b.0")).toBe(4); + expect(lamb.getPathIn(obj, "b.c.0")).toBe("f"); + }); + + it("should allow negative indexes", function () { + expect(lamb.getPath("b.b.-1")(obj)).toBe(5); + expect(lamb.getPathIn(obj, "b.b.-1")).toBe(5); + expect(lamb.getPath("b.c.-3")(obj)).toBe("f"); + expect(lamb.getPathIn(obj, "b.c.-3")).toBe("f"); + }); + + it("should work with sparse arrays", function () { + expect(lamb.getPathIn(obj, "b.d.1")).toBe(99); + expect(lamb.getPathIn(obj, "b.d.-2")).toBe(99); + expect(lamb.getPath("b.d.1")(obj)).toBe(99); + expect(lamb.getPath("b.d.-2")(obj)).toBe(99); + }); + + it("should be able to retrieve values nested in arrays", function () { + var o = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ] + }; + + expect(lamb.getPath("data.1.value")(o)).toBe(20); + expect(lamb.getPathIn(o, "data.1.value")).toBe(20); + expect(lamb.getPath("data.-1.value")(o)).toBe(30); + expect(lamb.getPathIn(o, "data.-1.value")).toBe(30); + }); + + it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { + var o = { a: ["abc", new String("def"), "ghi"] }; + + o.a["-1"] = "foo"; + o.a[1]["-2"] = "bar"; + + Object.defineProperty(o.a, "-2", { value: 99 }); + + expect(lamb.getPath("a.-1")(o)).toBe("foo"); + expect(lamb.getPathIn(o, "a.-1")).toBe("foo"); + expect(lamb.getPath("a.1.-2")(o)).toBe("bar"); + expect(lamb.getPathIn(o, "a.1.-2")).toBe("bar"); + expect(lamb.getPath("a.-2")(o)).toBe(99); + expect(lamb.getPathIn(o, "a.-2")).toBe(99); + }); + + it("should accept a custom path separator", function () { + expect(lamb.getPath("b->b->0", "->")(obj)).toBe(4); + expect(lamb.getPath("c.d/e.f", "/")(obj)).toBe(6); + expect(lamb.getPathIn(obj, "b->b->0", "->")).toBe(4); + expect(lamb.getPathIn(obj, "c.d/e.f", "/")).toBe(6); + }); + + it("should return `undefined` for a non-existent path in a valid source", function () { + expect(lamb.getPath("b.a.z")(obj)).toBeUndefined(); + expect(lamb.getPathIn(obj, "b.a.z")).toBeUndefined(); + expect(lamb.getPath("b.z.a")(obj)).toBeUndefined(); + expect(lamb.getPathIn(obj, "b.z.a")).toBeUndefined(); + expect(lamb.getPath("b.b.10")(obj)).toBeUndefined(); + expect(lamb.getPathIn(obj, "b.b.10")).toBeUndefined(); + expect(lamb.getPath("b.b.10.z")(obj)).toBeUndefined(); + expect(lamb.getPathIn(obj, "b.b.10.z")).toBeUndefined(); + }); + + it("should accept integers as paths containing a single key", function () { + expect(lamb.getPathIn([1, 2], 1)).toBe(2); + expect(lamb.getPath(1)([1, 2])).toBe(2); + expect(lamb.getPathIn([1, 2], -1)).toBe(2); + expect(lamb.getPath(-1)([1, 2])).toBe(2); + expect(lamb.getPathIn({ 1: "a" }, 1)).toBe("a"); + expect(lamb.getPath(1)({ 1: "a" })).toBe("a"); + }); + + it("should convert other values for the `path` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + var value = values[nonStringsAsStrings.indexOf(String(key))]; + + expect(lamb.getPathIn(testObj, key, "_")).toBe(value); + expect(lamb.getPath(key, "_")(testObj)).toBe(value); + }); + + var fooObj = { a: 2, 1: { 5: 3 }, undefined: 4 }; + + expect(lamb.getPathIn(fooObj, 1.5)).toBe(3); + expect(lamb.getPath(1.5)(fooObj)).toBe(3); + + expect(lamb.getPathIn(fooObj)).toBe(4); + expect(lamb.getPath()(fooObj)).toBe(4); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.getPathIn).toThrow(); + expect(lamb.getPath()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.getPathIn(null, "a"); }).toThrow(); + expect(function () { lamb.getPathIn(void 0, "a"); }).toThrow(); + expect(function () { lamb.getPath("a")(null); }).toThrow(); + expect(function () { lamb.getPath("a")(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.getPathIn(value, "a")).toBeUndefined(); + expect(lamb.getPath("a")(value)).toBeUndefined(); + }); + + expect(lamb.getPathIn(/foo/, "lastIndex")).toBe(0); + expect(lamb.getPath("lastIndex")(/foo/)).toBe(0); + }); +}); diff --git a/src/object/__tests__/has.spec.js b/src/object/__tests__/has.spec.js new file mode 100644 index 0000000..f9c8b8e --- /dev/null +++ b/src/object/__tests__/has.spec.js @@ -0,0 +1,76 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("has / hasKey", function () { + var obj = { foo: "bar" }; + + it("should check the existence of the property in an object", function () { + expect(lamb.has(obj, "toString")).toBe(true); + expect(lamb.has(obj, "foo")).toBe(true); + expect(lamb.hasKey("toString")(obj)).toBe(true); + expect(lamb.hasKey("foo")(obj)).toBe(true); + }); + + it("should return `false` for a non-existent property", function () { + expect(lamb.has(obj, "baz")).toBe(false); + expect(lamb.hasKey("baz")(obj)).toBe(false); + }); + + it("should accept integers as keys and accept array-like objects", function () { + var o = { 1: "a", 2: "b" }; + var arr = [1, 2, 3, 4]; + var s = "abcd"; + + expect(lamb.has(o, 2)).toBe(true); + expect(lamb.has(arr, 2)).toBe(true); + expect(lamb.has(s, 2)).toBe(true); + expect(lamb.hasKey(2)(o)).toBe(true); + expect(lamb.hasKey(2)(arr)).toBe(true); + expect(lamb.hasKey(2)(s)).toBe(true); + }); + + it("should consider only defined indexes in sparse arrays", function () { + var arr = [1, , 3]; // eslint-disable-line no-sparse-arrays + + expect(lamb.has(arr, 1)).toBe(false); + expect(lamb.hasKey(1)(arr)).toBe(false); + }); + + it("should convert other values for the `key` parameter to string", function () { + var testObj = lamb.make(nonStringsAsStrings, []); + + nonStrings.forEach(function (key) { + expect(lamb.has(testObj, key)).toBe(true); + expect(lamb.hasKey(key)(testObj)).toBe(true); + }); + + expect(lamb.has({ undefined: void 0 })).toBe(true); + expect(lamb.hasKey()({ undefined: void 0 })).toBe(true); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.has).toThrow(); + expect(lamb.hasKey("foo")).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.has(null, "a"); }).toThrow(); + expect(function () { lamb.has(void 0, "a"); }).toThrow(); + expect(function () { lamb.hasKey("a")(null); }).toThrow(); + expect(function () { lamb.hasKey("a")(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.has(v, "a")).toBe(false); + expect(lamb.hasKey("a")(v)).toBe(false); + }); + + expect(lamb.has(/foo/, "lastIndex")).toBe(true); + expect(lamb.hasKey("lastIndex")(/foo/)).toBe(true); + }); +}); diff --git a/src/object/__tests__/hasKeyValue.spec.js b/src/object/__tests__/hasKeyValue.spec.js new file mode 100644 index 0000000..cf5394c --- /dev/null +++ b/src/object/__tests__/hasKeyValue.spec.js @@ -0,0 +1,94 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("hasKeyValue", function () { + var persons = [ + { name: "Jane", surname: "Doe" }, + { name: "John", surname: "Doe" }, + { name: "Mario", surname: "Rossi" } + ]; + + var isDoe = lamb.hasKeyValue("surname", "Doe"); + + it("should build a function that checks if an object holds the desired key / value pair", function () { + expect(lamb.hasKeyValue("a", 45)({ a: 45 })).toBe(true); + expect(lamb.hasKeyValue("a", [45])({ a: 45 })).toBe(false); + expect(persons.map(isDoe)).toEqual([true, true, false]); + }); + + it("should return `false` for a non-existent property", function () { + expect(lamb.hasKeyValue("z", void 0)(persons[0])).toBe(false); + }); + + it("should be able to check for `undefined` values in existing keys", function () { + var obj = { a: void 0, b: 5 }; + + expect(lamb.hasKeyValue("a", void 0)(obj)).toBe(true); + expect(lamb.hasKeyValue("a")(obj)).toBe(true); + expect(lamb.hasKeyValue("b", void 0)(obj)).toBe(false); + expect(lamb.hasKeyValue("b")(obj)).toBe(false); + }); + + it("should use the \"SameValueZero\" comparison", function () { + var obj = { a: NaN, b: 0, c: -0 }; + + expect(lamb.hasKeyValue("a", NaN)(obj)).toBe(true); + expect(lamb.hasKeyValue("b", 0)(obj)).toBe(true); + expect(lamb.hasKeyValue("b", -0)(obj)).toBe(true); + expect(lamb.hasKeyValue("c", -0)(obj)).toBe(true); + expect(lamb.hasKeyValue("c", 0)(obj)).toBe(true); + }); + + it("should accept integers as keys and accept array-like objects", function () { + var o = { 1: "a", 2: "b" }; + var arr = [1, 2, 3, 4]; + var s = "abcd"; + + expect(lamb.hasKeyValue(2, "b")(o)).toBe(true); + expect(lamb.hasKeyValue(2, 3)(arr)).toBe(true); + expect(lamb.hasKeyValue(2, "c")(s)).toBe(true); + }); + + it("should consider only defined indexes in sparse arrays", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.hasKeyValue("1", void 0)([1, , 3])).toBe(false); + expect(lamb.hasKeyValue("-2", void 0)([1, , 3])).toBe(false); + /* eslint-enable no-sparse-arrays */ + }); + + it("should convert other values for the `key` parameter to string", function () { + var testObj = lamb.make( + nonStringsAsStrings, + lamb.range(0, nonStringsAsStrings.length, 1) + ); + + nonStrings.forEach(function (key) { + var value = nonStringsAsStrings.indexOf(String(key)); + + expect(lamb.hasKeyValue(key, value)(testObj)).toBe(true); + }); + + expect(lamb.hasKeyValue()({ undefined: void 0 })).toBe(true); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.hasKeyValue("foo", 2)).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.hasKeyValue("a", 2)(null); }).toThrow(); + expect(function () { lamb.hasKeyValue("a", 2)(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.hasKeyValue("a", 2)(v)).toBe(false); + }); + + expect(lamb.hasKeyValue("lastIndex", 0)(/foo/)).toBe(true); + }); +}); diff --git a/src/object/__tests__/hasOwn.spec.js b/src/object/__tests__/hasOwn.spec.js new file mode 100644 index 0000000..927beb1 --- /dev/null +++ b/src/object/__tests__/hasOwn.spec.js @@ -0,0 +1,76 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("hasOwn / hasOwnKey", function () { + var obj = { foo: "bar" }; + + it("should check the existence of an owned property in an object", function () { + expect(lamb.hasOwn(obj, "toString")).toBe(false); + expect(lamb.hasOwn(obj, "foo")).toBe(true); + expect(lamb.hasOwnKey("toString")(obj)).toBe(false); + expect(lamb.hasOwnKey("foo")(obj)).toBe(true); + }); + + it("should return `false` for a non-existent property", function () { + expect(lamb.hasOwn(obj, "baz")).toBe(false); + expect(lamb.hasOwnKey("baz")(obj)).toBe(false); + }); + + it("should accept integers as keys and accept array-like objects", function () { + var o = { 1: "a", 2: "b" }; + var arr = [1, 2, 3, 4]; + var s = "abcd"; + + expect(lamb.hasOwn(o, 2)).toBe(true); + expect(lamb.hasOwn(arr, 2)).toBe(true); + expect(lamb.hasOwn(s, 2)).toBe(true); + expect(lamb.hasOwnKey(2)(o)).toBe(true); + expect(lamb.hasOwnKey(2)(arr)).toBe(true); + expect(lamb.hasOwnKey(2)(s)).toBe(true); + }); + + it("should consider only defined indexes in sparse arrays", function () { + var arr = [1, , 3]; // eslint-disable-line no-sparse-arrays + + expect(lamb.hasOwn(arr, 1)).toBe(false); + expect(lamb.hasOwnKey(1)(arr)).toBe(false); + }); + + it("should convert other values for the `key` parameter to string", function () { + var testObj = lamb.make(nonStringsAsStrings, []); + + nonStrings.forEach(function (key) { + expect(lamb.hasOwn(testObj, key)).toBe(true); + expect(lamb.hasOwnKey(key)(testObj)).toBe(true); + }); + + expect(lamb.hasOwn({ undefined: void 0 })).toBe(true); + expect(lamb.hasOwnKey()({ undefined: void 0 })).toBe(true); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.hasOwn).toThrow(); + expect(lamb.hasOwnKey("foo")).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.hasOwn(null, "a"); }).toThrow(); + expect(function () { lamb.hasOwn(void 0, "a"); }).toThrow(); + expect(function () { lamb.hasOwnKey("a")(null); }).toThrow(); + expect(function () { lamb.hasOwnKey("a")(void 0); }).toThrow(); + }); + + it("should return convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.hasOwn(v, "a")).toBe(false); + expect(lamb.hasOwnKey("a")(v)).toBe(false); + }); + + expect(lamb.hasOwn(/foo/, "lastIndex")).toBe(true); + expect(lamb.hasOwnKey("lastIndex")(/foo/)).toBe(true); + }); +}); diff --git a/src/object/__tests__/hasPathValue.spec.js b/src/object/__tests__/hasPathValue.spec.js new file mode 100644 index 0000000..86a2f71 --- /dev/null +++ b/src/object/__tests__/hasPathValue.spec.js @@ -0,0 +1,150 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("hasPathValue", function () { + var obj = { + a: 2, + b: { + a: 3, + b: [4, 5], + c: "foo", + e: { a: 45, b: void 0 } + }, + "c.d": { "e.f": 6 }, + c: { a: -0, b: NaN } + }; + + obj.b.d = Array(3); + obj.b.d[1] = 99; + + Object.defineProperty(obj, "e", { value: 10 }); + obj.f = Object.create({}, { g: { value: 20 } }); + + it("should verify if the given path points to the desired value", function () { + expect(lamb.hasPathValue("a", 2)(obj)).toBe(true); + expect(lamb.hasPathValue("b.a", 3)(obj)).toBe(true); + expect(lamb.hasPathValue("b.b", obj.b.b)(obj)).toBe(true); + expect(lamb.hasPathValue("b.e.a", 45)(obj)).toBe(true); + expect(lamb.hasPathValue("a", "2")(obj)).toBe(false); + expect(lamb.hasPathValue("b.a", -3)(obj)).toBe(false); + expect(lamb.hasPathValue("b.b", [4, 5])(obj)).toBe(false); + expect(lamb.hasPathValue("b.e.a", [45])(obj)).toBe(false); + }); + + it("should use the SameValueZero comparison", function () { + expect(lamb.hasPathValue("c.a", 0)(obj)).toBe(true); + expect(lamb.hasPathValue("c.a", -0)(obj)).toBe(true); + expect(lamb.hasPathValue("c.b", NaN)(obj)).toBe(true); + }); + + it("should be able to verify non-enumerable properties", function () { + expect(lamb.hasPathValue("e", 10)(obj)).toBe(true); + expect(lamb.hasPathValue("f.g", 20)(obj)).toBe(true); + }); + + it("should be able to verify values in arrays and array-like objects", function () { + expect(lamb.hasPathValue("b.b.0", 4)(obj)).toBe(true); + expect(lamb.hasPathValue("b.c.0", "f")(obj)).toBe(true); + }); + + it("should allow negative indexes in paths", function () { + expect(lamb.hasPathValue("b.b.-1", 5)(obj)).toBe(true); + expect(lamb.hasPathValue("b.c.-3", "f")(obj)).toBe(true); + }); + + it("should return `false` for a non-existent property in a valid source", function () { + expect(lamb.hasPathValue("b.a.z", void 0)(obj)).toBe(false); + expect(lamb.hasPathValue("b.z.a", void 0)(obj)).toBe(false); + expect(lamb.hasPathValue("b.b.2", void 0)(obj)).toBe(false); + expect(lamb.hasPathValue("b.b.-3", void 0)(obj)).toBe(false); + expect(lamb.hasPathValue("b.c.3", void 0)(obj)).toBe(false); + expect(lamb.hasPathValue("b.c.-4", void 0)(obj)).toBe(false); + expect(lamb.hasPathValue("b.e.z", void 0)(obj)).toBe(false); + }); + + it("should be able to check for `undefined` values in existing paths", function () { + expect(lamb.hasPathValue("b.e.b", void 0)(obj)).toBe(true); + expect(lamb.hasPathValue("b.e.b")(obj)).toBe(true); + expect(lamb.hasPathValue("b.e.a", void 0)(obj)).toBe(false); + expect(lamb.hasPathValue("b.e.a")(obj)).toBe(false); + }); + + it("should work with sparse arrays", function () { + expect(lamb.hasPathValue("b.d.0", void 0)(obj)).toBe(true); + expect(lamb.hasPathValue("b.d.-3", void 0)(obj)).toBe(true); + expect(lamb.hasPathValue("b.d.1", 99)(obj)).toBe(true); + expect(lamb.hasPathValue("b.d.-2", 99)(obj)).toBe(true); + }); + + it("should be able to verify values nested in arrays", function () { + var o = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ] + }; + + expect(lamb.hasPathValue("data.1.value", 20)(o)).toBe(true); + expect(lamb.hasPathValue("data.-1.value", 30)(o)).toBe(true); + }); + + it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { + var o = { a: ["abc", new String("def"), "ghi"] }; + + o.a["-1"] = "foo"; + o.a[1]["-2"] = "bar"; + Object.defineProperty(o.a, "-2", { value: 99 }); + + expect(lamb.hasPathValue("a.-1", "foo")(o)).toBe(true); + expect(lamb.hasPathValue("a.1.-2", "bar")(o)).toBe(true); + expect(lamb.hasPathValue("a.-2", 99)(o)).toBe(true); + }); + + it("should accept a custom path separator", function () { + expect(lamb.hasPathValue("b->b->0", 4, "->")(obj)).toBe(true); + expect(lamb.hasPathValue("c.d/e.f", 6, "/")(obj)).toBe(true); + }); + + it("should accept integers as paths containing a single key", function () { + expect(lamb.hasPathValue(1, 2)([1, 2])).toBe(true); + expect(lamb.hasPathValue(-1, 2)([1, 2])).toBe(true); + expect(lamb.hasPathValue(1, "a")({ 1: "a" })).toBe(true); + }); + + it("should convert other values for the `path` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + var value = values[nonStringsAsStrings.indexOf(String(key))]; + + expect(lamb.hasPathValue(key, value, "_")(testObj)).toBe(true); + }); + + var fooObj = { a: 2, 1: { 5: 3 } }; + + expect(lamb.hasPathValue(1.5, 3)(fooObj)).toBe(true); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.hasPathValue()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.hasPathValue("a", 99)(null); }).toThrow(); + expect(function () { lamb.hasPathValue("a", 99)(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.hasPathValue("a", 99)(value)).toBe(false); + }); + + expect(lamb.hasPathValue("lastIndex", 0)(/foo/)).toBe(true); + }); +}); diff --git a/src/object/__tests__/immutable.spec.js b/src/object/__tests__/immutable.spec.js new file mode 100644 index 0000000..a50a857 --- /dev/null +++ b/src/object/__tests__/immutable.spec.js @@ -0,0 +1,87 @@ +import * as lamb from "../.."; + +describe("immutable", function () { + var persons; + var newPerson; + + beforeEach(function () { + persons = [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "John", surname: "Doe", age: 40, city: "London" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" } + ]; + + newPerson = { + name: "Paolo", + surname: "Bianchi", + age: null, + city: "Amsterdam", + contact: { + mail: "paolo@bianchi.it", + phone: "+39123456789" + }, + luckyNumbers: [13, 17] + }; + }); + + it("should make an object immutable", function () { + var immutableNewPerson = lamb.immutable(newPerson); + + expect(immutableNewPerson).toBe(newPerson); + expect(function () { + newPerson.name = "Foo"; + }).toThrow(); + expect(function () { + newPerson.luckyNumbers.splice(0, 1); + }).toThrow(); + expect(Array.isArray(newPerson.luckyNumbers)).toBe(true); + expect(newPerson.name).toBe("Paolo"); + + var immutablePersons = lamb.immutable(persons); + + expect(immutablePersons).toBe(persons); + expect(Array.isArray(immutablePersons)).toBe(true); + expect(function () { + persons.push(newPerson); + }).toThrow(); + expect(persons.length).toBe(3); + + expect(function () { + persons[0].age = 50; + }).toThrow(); + expect(persons[0].age).toBe(12); + }); + + it("should handle circular references", function () { + var foo = { + bar: 2, + baz: persons + }; + + persons.push(foo); + + lamb.immutable(persons); + + expect(Object.isFrozen(persons[3])).toBe(true); + expect(persons[3].baz).toBe(persons); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.immutable).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.immutable(null); }).toThrow(); + expect(function () { lamb.immutable(void 0); }).toThrow(); + }); + + it("should not throw an exception when a `nil` value is in a nested property", function () { + var o = { + a: null, + b: void 0, + c: { d: null, e: void 0 } + }; + + expect(function () { lamb.immutable(o); }).not.toThrow(); + }); +}); diff --git a/src/object/__tests__/keySatisfies.spec.js b/src/object/__tests__/keySatisfies.spec.js new file mode 100644 index 0000000..bd206a3 --- /dev/null +++ b/src/object/__tests__/keySatisfies.spec.js @@ -0,0 +1,114 @@ +import * as lamb from "../.."; +import { + nonFunctions, + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("keySatisfies", function () { + var users = [ + { name: "Jane", age: 12, active: false }, + { name: "John", age: 40, active: false }, + { name: "Mario", age: 18, active: true }, + { name: "Paolo", age: 15, active: true } + ]; + + it("should use a predicate and a property name to build a new predicate that will be applied on an object's key", function () { + var isAdult = lamb.keySatisfies(lamb.isGTE(18), "age"); + + expect(users.map(isAdult)).toEqual([false, true, true, false]); + }); + + it("should pass an `undefined` value to the predicate if the given property doesn't exist", function () { + var isGreaterThan17 = function (n) { + expect(arguments.length).toBe(1); + expect(n).toBeUndefined(); + + return n > 17; + }; + + expect(lamb.keySatisfies(isGreaterThan17, "foo")(users[0])).toBe(false); + expect(lamb.keySatisfies(isGreaterThan17, "bar")(users[2])).toBe(false); + }); + + it("should apply the function's calling context to the predicate", function () { + var ageValidator = { + minAllowedAge: 15, + maxAllowedAge: 50, + hasValidAge: lamb.keySatisfies(function (age) { + return age >= this.minAllowedAge && age <= this.maxAllowedAge; + }, "age") + }; + + expect(ageValidator.hasValidAge({ age: 20 })).toBe(true); + expect(ageValidator.hasValidAge({ age: 55 })).toBe(false); + + var ageValidator2 = Object.create(ageValidator, { + minAllowedAge: { value: 21 }, + maxAllowedAge: { value: 55 } + }); + + expect(ageValidator2.hasValidAge({ age: 20 })).toBe(false); + expect(ageValidator2.hasValidAge({ age: 55 })).toBe(true); + }); + + it("should accept integers as keys and accept array-like objects", function () { + var o = { 1: "a", 2: "b" }; + var arr = [1, 2, 3, 4]; + var s = "abcd"; + + expect(lamb.keySatisfies(lamb.is("a"), 1)(o)).toBe(true); + expect(lamb.keySatisfies(lamb.is(2), 1)(arr)).toBe(true); + expect(lamb.keySatisfies(lamb.is("c"), 2)(s)).toBe(true); + }); + + it("should pass an `undefined` value to the predicate for unassigned or deleted indexes in sparse arrays", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.keySatisfies(lamb.isUndefined, "1")([1, , 3])).toBe(true); + expect(lamb.keySatisfies(lamb.isUndefined, "-2")([1, , 3])).toBe(true); + /* eslint-enable no-sparse-arrays */ + }); + + it("should convert other values for the `key` parameter to string", function () { + var testObj = lamb.make( + nonStringsAsStrings, + lamb.range(0, nonStringsAsStrings.length, 1) + ); + + nonStrings.forEach(function (key) { + var value = nonStringsAsStrings.indexOf(String(key)); + + expect(lamb.keySatisfies(lamb.is(value), key)(testObj)).toBe(true); + }); + + expect(lamb.keySatisfies(lamb.is(99))({ undefined: 99 })).toBe(true); + }); + + it("should throw an exception if the predicate isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.keySatisfies(value, "foo")({}); }).toThrow(); + }); + + expect(function () { lamb.keySatisfies()({}); }).toThrow(); + }); + + it("should throw an exception if called without the data argument", function () { + expect(lamb.keySatisfies(lamb.contains, "foo")).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.keySatisfies(lamb.contains(99), "foo")(null); }).toThrow(); + expect(function () { lamb.keySatisfies(lamb.contains(99), "foo")(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + var isZero = lamb.isSVZ(0); + + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.keySatisfies(isZero, "foo")(v)).toBe(false); + }); + + expect(lamb.keySatisfies(isZero, "lastIndex")(/foo/)).toBe(true); + }); +}); diff --git a/src/object/__tests__/keys.spec.js b/src/object/__tests__/keys.spec.js new file mode 100644 index 0000000..63cac81 --- /dev/null +++ b/src/object/__tests__/keys.spec.js @@ -0,0 +1,39 @@ +import * as lamb from "../.."; +import { wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("keys", function () { + it("should build an array with all the enumerables own keys of an object", function () { + var baseFoo = Object.create({ a: 1 }, { b: { value: 2 } }); + var foo = Object.create(baseFoo, { + c: { value: 3 }, + d: { value: 4, enumerable: true } + }); + + expect(lamb.keys(foo)).toEqual(["d"]); + }); + + it("should work with arrays and array-like objects", function () { + expect(lamb.keys([1, 2, 3])).toEqual(["0", "1", "2"]); + expect(lamb.keys("abc")).toEqual(["0", "1", "2"]); + }); + + it("should retrieve only defined keys in sparse arrays", function () { + // eslint-disable-next-line comma-spacing, no-sparse-arrays + expect(lamb.keys([, 5, 6, ,])).toStrictEqual(["1", "2"]); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.keys).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.keys(null); }).toThrow(); + expect(function () { lamb.keys(void 0); }).toThrow(); + }); + + it("should consider other values as empty objects", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.keys(value)).toEqual([]); + }); + }); +}); diff --git a/src/object/__tests__/make.spec.js b/src/object/__tests__/make.spec.js new file mode 100644 index 0000000..bf9cab4 --- /dev/null +++ b/src/object/__tests__/make.spec.js @@ -0,0 +1,54 @@ +import * as lamb from "../.."; +import { wannabeEmptyArrays } from "../../__tests__/commons"; + +describe("make", function () { + it("should build an object with the given keys and values lists", function () { + expect(lamb.make(["a", "b", "c"], [1, 2, 3])).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it("should create undefined values if the keys list is longer", function () { + expect(lamb.make(["a", "b", "c"], [1, 2])).toStrictEqual({ a: 1, b: 2, c: void 0 }); + }); + + it("should ignore extra values if the keys list is shorter", function () { + expect(lamb.make(["a", "b"], [1, 2, 3])).toEqual({ a: 1, b: 2 }); + }); + + it("should convert non-string keys to strings", function () { + expect(lamb.make([null, void 0, 2], [1, 2, 3])).toEqual({ null: 1, undefined: 2, 2: 3 }); + }); + + it("should convert unassigned or deleted indexes in sparse arrays to `undefined` values", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.make(["a", "b", "c"], [1, , 3])).toStrictEqual({ a: 1, b: void 0, c: 3 }); + expect(lamb.make(["a", , "c"], [1, 2, 3])).toEqual({ a: 1, undefined: 2, c: 3 }); + /* eslint-enable no-sparse-arrays */ + }); + + it("should accept array-like objects in both parameters", function () { + expect(lamb.make("abcd", "1234")).toEqual({ a: "1", b: "2", c: "3", d: "4" }); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.make).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` in the `keys` or in the `values` parameter", function () { + expect(function () { lamb.make(null, []); }).toThrow(); + expect(function () { lamb.make(void 0, []); }).toThrow(); + expect(function () { lamb.make([], null); }).toThrow(); + expect(function () { lamb.make([], void 0); }).toThrow(); + }); + + it("should consider other values for the `keys` parameter as empty arrays and return an empty object", function () { + wannabeEmptyArrays.forEach(function (v) { + expect(lamb.make(v, [])).toEqual({}); + }); + }); + + it("should consider other values for the `values` parameter to be empty arrays", function () { + wannabeEmptyArrays.forEach(function (v) { + expect(lamb.make(["foo", "bar"], v)).toStrictEqual({ foo: void 0, bar: void 0 }); + }); + }); +}); diff --git a/src/object/__tests__/mapValues.spec.js b/src/object/__tests__/mapValues.spec.js new file mode 100644 index 0000000..d48909d --- /dev/null +++ b/src/object/__tests__/mapValues.spec.js @@ -0,0 +1,85 @@ +import * as lamb from "../.."; +import { wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("mapValues / mapValuesWith", function () { + var baseFoo = Object.create({ a: 1 }, { b: { value: 2, enumerable: true }, z: { value: 5 } }); + var foo = Object.create(baseFoo, { c: { value: 3, enumerable: true } }); + + var fooEquivalent = { a: 1, b: 2, c: 3, z: 5 }; + + var inc = lamb.add(1); + + // The "toEqual" matcher would have checked only own enumerable properties + afterEach(function () { + for (var key in fooEquivalent) { + expect(fooEquivalent[key]).toBe(foo[key]); + } + }); + + it("should create a new object by applying the received function to the values of the source one", function () { + var times3 = lamb.multiplyBy(3); + var r = { a: 3, b: 6, c: 9 }; + + expect(lamb.mapValues(foo, times3)).toEqual(r); + expect(lamb.mapValuesWith(times3)(foo)).toEqual(r); + }); + + it("should use all the enumerable properties of the source object, inherited or not", function () { + var r = { a: 2, b: 3, c: 4 }; + + expect(lamb.mapValues(foo, inc)).toEqual(r); + expect(lamb.mapValuesWith(inc)(foo)).toEqual(r); + }); + + it("should pass the key value, its name and the object being traversed to the mapping function", function () { + var deductFive = function (v, k, o) { + expect(o).toBe(foo); + expect(o[k]).toBe(v); + + return v - 5; + }; + var r = { a: -4, b: -3, c: -2 }; + + expect(lamb.mapValues(foo, deductFive)).toEqual(r); + expect(lamb.mapValuesWith(deductFive)(foo)).toEqual(r); + }); + + it("should work with array-like objects", function () { + var a = [97, 97, 98]; + var s = "abc"; + var r1 = { 0: 98, 1: 98, 2: 99 }; + var r2 = { 0: "A", 1: "B", 2: "C" }; + + var toUpperCase = lamb.invoker("toUpperCase"); + + expect(lamb.mapValues(a, inc)).toEqual(r1); + expect(lamb.mapValuesWith(inc)(a)).toEqual(r1); + + expect(lamb.mapValues(s, toUpperCase)).toEqual(r2); + expect(lamb.mapValuesWith(toUpperCase)(s)).toEqual(r2); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.mapValues).toThrow(); + expect(lamb.mapValuesWith()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.mapValues(null, inc); }).toThrow(); + expect(function () { lamb.mapValues(void 0, inc); }).toThrow(); + expect(function () { lamb.mapValuesWith(inc)(null); }).toThrow(); + expect(function () { lamb.mapValuesWith(inc)(void 0); }).toThrow(); + }); + + it("should consider other values as empty objects", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.mapValues(value, inc)).toEqual({}); + expect(lamb.mapValuesWith(inc)(value)).toEqual({}); + }); + }); + + it("should throw an exception if `fn` isn't a function or if it's missing", function () { + expect(function () { lamb.mapValues(foo); }).toThrow(); + expect(function () { lamb.mapValuesWith()(foo); }).toThrow(); + }); +}); diff --git a/src/object/__tests__/merge.spec.js b/src/object/__tests__/merge.spec.js new file mode 100644 index 0000000..f7b3305 --- /dev/null +++ b/src/object/__tests__/merge.spec.js @@ -0,0 +1,75 @@ +import * as lamb from "../.."; +import { wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("merge / mergeOwn", function () { + var baseFoo = Object.create({ a: 1 }, { b: { value: 2, enumerable: true }, z: { value: 5 } }); + var foo = Object.create(baseFoo, { c: { value: 3, enumerable: true } }); + var bar = { d: 4 }; + var fooEquivalent = { a: 1, b: 2, c: 3, z: 5 }; + + // The "toEqual" matcher would have checked only own enumerable properties + afterEach(function () { + for (var key in fooEquivalent) { + expect(fooEquivalent[key]).toBe(foo[key]); + } + + expect(bar).toEqual({ d: 4 }); + }); + + describe("merge", function () { + it("should merge the enumerable properties of the provided sources into a new object without mutating them", function () { + var newObj = lamb.merge(foo, bar); + + expect(newObj).toEqual({ a: 1, b: 2, c: 3, d: 4 }); + expect(newObj.z).toBeUndefined(); + }); + }); + + describe("mergeOwn", function () { + it("should merge the enumerable own properties of the provided sources into a new object without mutating them", function () { + var newObj = lamb.mergeOwn(foo, bar); + + expect(newObj).toEqual({ c: 3, d: 4 }); + expect(newObj.a).toBeUndefined(); + expect(newObj.b).toBeUndefined(); + expect(newObj.z).toBeUndefined(); + }); + }); + + it("should transform array-like objects in objects with numbered string as properties", function () { + expect(lamb.merge([1, 2], { a: 2 })).toEqual({ 0: 1, 1: 2, a: 2 }); + expect(lamb.mergeOwn([1, 2], { a: 2 })).toEqual({ 0: 1, 1: 2, a: 2 }); + expect(lamb.merge("foo", { a: 2 })).toEqual({ 0: "f", 1: "o", 2: "o", a: 2 }); + expect(lamb.mergeOwn("foo", { a: 2 })).toEqual({ 0: "f", 1: "o", 2: "o", a: 2 }); + }); + + it("should handle key homonymy by giving the last source precedence over the first one", function () { + expect(lamb.merge({ a: 1, b: 3 }, { b: 5, c: 4 })).toEqual({ a: 1, b: 5, c: 4 }); + expect(lamb.mergeOwn({ a: 1, b: 3 }, { b: 5, c: 4 })).toEqual({ a: 1, b: 5, c: 4 }); + }); + + it("should throw an exception if called with `nil` values", function () { + expect(function () { lamb.merge({ a: 2 }, null); }).toThrow(); + expect(function () { lamb.merge(null, { a: 2 }); }).toThrow(); + expect(function () { lamb.merge({ a: 2 }, void 0); }).toThrow(); + expect(function () { lamb.merge(void 0, { a: 2 }); }).toThrow(); + + expect(function () { lamb.mergeOwn({ a: 2 }, null); }).toThrow(); + expect(function () { lamb.mergeOwn(null, { a: 2 }); }).toThrow(); + expect(function () { lamb.mergeOwn({ a: 2 }, void 0); }).toThrow(); + expect(function () { lamb.mergeOwn(void 0, { a: 2 }); }).toThrow(); + + expect(lamb.merge).toThrow(); + expect(lamb.mergeOwn).toThrow(); + }); + + it("should consider other values as empty objects", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.merge({ a: 2 }, value)).toEqual({ a: 2 }); + expect(lamb.merge(value, { a: 2 })).toEqual({ a: 2 }); + + expect(lamb.mergeOwn({ a: 2 }, value)).toEqual({ a: 2 }); + expect(lamb.mergeOwn(value, { a: 2 })).toEqual({ a: 2 }); + }); + }); +}); diff --git a/src/object/__tests__/pairs.spec.js b/src/object/__tests__/pairs.spec.js new file mode 100644 index 0000000..4f7c5b2 --- /dev/null +++ b/src/object/__tests__/pairs.spec.js @@ -0,0 +1,67 @@ +import * as lamb from "../.."; +import { wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("ownPairs / pairs", function () { + var baseFoo = Object.create({ a: 1 }, { b: { value: 2 } }); + var foo = Object.create(baseFoo, { + c: { value: 3 }, + d: { value: 4, enumerable: true } + }); + + it("should convert an object in a list of key / value pairs", function () { + var source = { a: 1, b: 2, c: 3 }; + var result = [["a", 1], ["b", 2], ["c", 3]]; + + expect(lamb.ownPairs(source)).toEqual(result); + expect(lamb.pairs(source)).toEqual(result); + }); + + it("should keep `undefined` values in the result", function () { + var source = { a: null, b: void 0 }; + var result = [["a", null], ["b", void 0]]; + + expect(lamb.ownPairs(source)).toStrictEqual(result); + expect(lamb.pairs(source)).toStrictEqual(result); + }); + + it("should work with array-like objects", function () { + var r1 = [["0", 1], ["1", 2], ["2", 3]]; + var r2 = [["0", "a"], ["1", "b"], ["2", "c"]]; + + expect(lamb.ownPairs([1, 2, 3])).toEqual(r1); + expect(lamb.ownPairs("abc")).toEqual(r2); + expect(lamb.pairs([1, 2, 3])).toEqual(r1); + expect(lamb.pairs("abc")).toEqual(r2); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.ownPairs).toThrow(); + expect(lamb.pairs).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.ownPairs(null); }).toThrow(); + expect(function () { lamb.ownPairs(void 0); }).toThrow(); + expect(function () { lamb.pairs(null); }).toThrow(); + expect(function () { lamb.pairs(void 0); }).toThrow(); + }); + + it("should consider other values as empty objects", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.ownPairs(value)).toEqual([]); + expect(lamb.pairs(value)).toEqual([]); + }); + }); + + describe("ownPairs", function () { + it("should use only the own enumerable properties of the source object", function () { + expect(lamb.ownPairs(foo)).toEqual([["d", 4]]); + }); + }); + + describe("pairs", function () { + it("should use all the enumerable properties of the source object, inherited or not", function () { + expect(lamb.pairs(foo)).toEqual([["d", 4], ["a", 1]]); + }); + }); +}); diff --git a/src/object/__tests__/pathExistsIn.spec.js b/src/object/__tests__/pathExistsIn.spec.js new file mode 100644 index 0000000..c9c6208 --- /dev/null +++ b/src/object/__tests__/pathExistsIn.spec.js @@ -0,0 +1,183 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("pathExists / pathExistsIn", function () { + var obj = { + a: 2, + b: { + a: 3, + b: [4, 5], + c: "foo", + e: { a: 45, b: void 0 } + }, + "c.d": { "e.f": 6 }, + c: { a: -0, b: NaN } + }; + + obj.b.d = Array(3); + obj.b.d[1] = 99; + + Object.defineProperty(obj, "e", { value: 10 }); + obj.f = Object.create({}, { g: { value: 20 } }); + + it("should verify if the provided path exists in the given object", function () { + expect(lamb.pathExists("a")(obj)).toBe(true); + expect(lamb.pathExists("b.a")(obj)).toBe(true); + expect(lamb.pathExists("b.b")(obj)).toBe(true); + expect(lamb.pathExists("b.e.a")(obj)).toBe(true); + expect(lamb.pathExists("z")(obj)).toBe(false); + expect(lamb.pathExists("a.z")(obj)).toBe(false); + expect(lamb.pathExists("b.a.z")(obj)).toBe(false); + + expect(lamb.pathExistsIn(obj, "a")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.a")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.b")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.e.a")).toBe(true); + expect(lamb.pathExistsIn(obj, "z")).toBe(false); + expect(lamb.pathExistsIn(obj, "a.z")).toBe(false); + expect(lamb.pathExistsIn(obj, "b.a.z")).toBe(false); + }); + + it("should see existent paths when checking properties holding `undefined` values", function () { + expect(lamb.pathExists("b.e.b")(obj)).toBe(true); + expect(lamb.pathExistsIn(obj, "b.e.b")).toBe(true); + }); + + it("should be able to check paths with non-enumerable properties", function () { + expect(lamb.pathExists("e")(obj)).toBe(true); + expect(lamb.pathExists("f.g")(obj)).toBe(true); + + expect(lamb.pathExistsIn(obj, "e")).toBe(true); + expect(lamb.pathExistsIn(obj, "f.g")).toBe(true); + }); + + it("should be able to check arrays and array-like objects", function () { + expect(lamb.pathExists("b.b.0")(obj)).toBe(true); + expect(lamb.pathExists("b.c.0")(obj)).toBe(true); + expect(lamb.pathExists("b.b.2")(obj)).toBe(false); + expect(lamb.pathExists("b.c.3")(obj)).toBe(false); + + expect(lamb.pathExistsIn(obj, "b.b.0")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.c.0")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.b.2")).toBe(false); + expect(lamb.pathExistsIn(obj, "b.c.3")).toBe(false); + }); + + it("should allow negative indexes in paths", function () { + expect(lamb.pathExists("b.b.-1")(obj)).toBe(true); + expect(lamb.pathExists("b.c.-3")(obj)).toBe(true); + expect(lamb.pathExists("b.b.-3")(obj)).toBe(false); + expect(lamb.pathExists("b.c.-4")(obj)).toBe(false); + + expect(lamb.pathExistsIn(obj, "b.b.-1")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.c.-3")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.b.-3")).toBe(false); + expect(lamb.pathExistsIn(obj, "b.c.-4")).toBe(false); + }); + + it("should work with sparse arrays", function () { + expect(lamb.pathExists("b.d.0")(obj)).toBe(true); + expect(lamb.pathExists("b.d.1")(obj)).toBe(true); + expect(lamb.pathExists("b.d.-2")(obj)).toBe(true); + + expect(lamb.pathExistsIn(obj, "b.d.0")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.d.1")).toBe(true); + expect(lamb.pathExistsIn(obj, "b.d.-2")).toBe(true); + }); + + it("should be able to check objects nested in arrays", function () { + var o = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ] + }; + + expect(lamb.pathExists("data.1.value")(o)).toBe(true); + expect(lamb.pathExists("data.-1.value")(o)).toBe(true); + expect(lamb.pathExists("data.3.value")(o)).toBe(false); + expect(lamb.pathExists("data.-4.value")(o)).toBe(false); + + expect(lamb.pathExistsIn(o, "data.1.value")).toBe(true); + expect(lamb.pathExistsIn(o, "data.-1.value")).toBe(true); + expect(lamb.pathExistsIn(o, "data.3.value")).toBe(false); + expect(lamb.pathExistsIn(o, "data.-4.value")).toBe(false); + }); + + it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { + var o = { a: ["abc", new String("def"), "ghi"] }; + + o.a["-1"] = "foo"; + o.a[1]["-2"] = "bar"; + Object.defineProperty(o.a, "-2", { value: 99 }); + + expect(lamb.pathExists("a.-1")(o)).toBe(true); + expect(lamb.pathExists("a.1.-2")(o)).toBe(true); + expect(lamb.pathExists("a.-2")(o)).toBe(true); + + expect(lamb.pathExistsIn(o, "a.-1")).toBe(true); + expect(lamb.pathExistsIn(o, "a.1.-2")).toBe(true); + expect(lamb.pathExistsIn(o, "a.-2")).toBe(true); + }); + + it("should accept a custom path separator", function () { + expect(lamb.pathExists("b->b->0", "->")(obj)).toBe(true); + expect(lamb.pathExists("c.d/e.f", "/")(obj)).toBe(true); + + expect(lamb.pathExistsIn(obj, "b->b->0", "->")).toBe(true); + expect(lamb.pathExistsIn(obj, "c.d/e.f", "/")).toBe(true); + }); + + it("should accept integers as paths containing a single key", function () { + expect(lamb.pathExists(1)([1, 2])).toBe(true); + expect(lamb.pathExists(-1)([1, 2])).toBe(true); + expect(lamb.pathExists(1)({ 1: "a" })).toBe(true); + + expect(lamb.pathExistsIn([1, 2], 1)).toBe(true); + expect(lamb.pathExistsIn([1, 2], -1)).toBe(true); + expect(lamb.pathExistsIn({ 1: "a" }, 1)).toBe(true); + }); + + it("should convert other values for the `path` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + expect(lamb.pathExists(key, "_")(testObj)).toBe(true); + expect(lamb.pathExistsIn(testObj, key, "_")).toBe(true); + }); + + var fooObj = { a: 2, 1: { 5: 3 } }; + + expect(lamb.pathExists(1.5)(fooObj)).toBe(true); + expect(lamb.pathExistsIn(fooObj, 1.5)).toBe(true); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.pathExists()).toThrow(); + expect(function () { lamb.pathExistsIn(); }).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.pathExists("a")(null); }).toThrow(); + expect(function () { lamb.pathExists("a")(void 0); }).toThrow(); + + expect(function () { lamb.pathExistsIn(null, "a"); }).toThrow(); + expect(function () { lamb.pathExistsIn(void 0, "a"); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.pathExists("a")(value)).toBe(false); + expect(lamb.pathExistsIn(value, "a")).toBe(false); + }); + + expect(lamb.pathExists("lastIndex")(/foo/)).toBe(true); + expect(lamb.pathExistsIn(/foo/, "lastIndex")).toBe(true); + }); +}); diff --git a/src/object/__tests__/pathSatisfies.spec.js b/src/object/__tests__/pathSatisfies.spec.js new file mode 100644 index 0000000..69a8f45 --- /dev/null +++ b/src/object/__tests__/pathSatisfies.spec.js @@ -0,0 +1,153 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("pathSatisfies", function () { + var obj = { + a: 2, + b: { + a: 3, + b: [4, 5], + c: "foo", + e: { a: 45, b: void 0 } + }, + "c.d": { "e.f": 6 }, + c: { a: -0, b: NaN } + }; + + obj.b.d = Array(3); + obj.b.d[1] = 99; + + Object.defineProperty(obj, "e", { value: 10 }); + obj.f = Object.create({}, { g: { value: 20 } }); + + var isDefined = lamb.not(lamb.isUndefined); + + it("should verify if the provided path satisfies a predicate in the given object", function () { + expect(lamb.pathSatisfies(lamb.is(2), "a")(obj)).toBe(true); + expect(lamb.pathSatisfies(lamb.is(3), "b.a")(obj)).toBe(true); + expect(lamb.pathSatisfies(Array.isArray, "b.b")(obj)).toBe(true); + expect(lamb.pathSatisfies(lamb.is(99), "z")(obj)).toBe(false); + expect(lamb.pathSatisfies(isDefined, "a.z")(obj)).toBe(false); + expect(lamb.pathSatisfies(isDefined, "b.a.z")(obj)).toBe(false); + }); + + it("should pass an `undefined` value to the predicate if the path doesn't exist", function () { + var isDefinedCheck = function (v) { + expect(arguments.length).toBe(1); + expect(v).toBeUndefined(); + + return !lamb.isUndefined(v); + }; + + expect(lamb.pathSatisfies(isDefinedCheck, "a.z")(obj)).toBe(false); + expect(lamb.pathSatisfies(isDefinedCheck, "b.a.z")(obj)).toBe(false); + }); + + it("should apply the function's calling context to the predicate", function () { + var validator = { + minAllowedValue: 3, + maxAllowedValue: 10, + hasValidValue: lamb.pathSatisfies(function (value) { + return value >= this.minAllowedValue && value <= this.maxAllowedValue; + }, "b.a") + }; + + expect(validator.hasValidValue(obj)).toBe(true); + expect(validator.hasValidValue({ b: { a: 1 } })).toBe(false); + }); + + it("should be able to check paths with non-enumerable properties", function () { + expect(lamb.pathSatisfies(lamb.is(10), "e")(obj)).toBe(true); + expect(lamb.pathSatisfies(lamb.is(20), "f.g")(obj)).toBe(true); + }); + + it("should be able to check arrays and array-like objects", function () { + expect(lamb.pathSatisfies(lamb.is(4), "b.b.0")(obj)).toBe(true); + expect(lamb.pathSatisfies(lamb.is("f"), "b.c.0")(obj)).toBe(true); + }); + + it("should allow negative indexes in paths", function () { + expect(lamb.pathSatisfies(lamb.is(5), "b.b.-1")(obj)).toBe(true); + expect(lamb.pathSatisfies(lamb.is("f"), "b.c.-3")(obj)).toBe(true); + }); + + it("should pass an `undefined` value to the predicate for unassigned or deleted indexes in sparse arrays", function () { + expect(lamb.pathSatisfies(lamb.isUndefined, "b.d.0")(obj)).toBe(true); + expect(lamb.pathSatisfies(lamb.isUndefined, "b.d.-3")(obj)).toBe(true); + + /* eslint-disable no-sparse-arrays */ + expect(lamb.pathSatisfies(lamb.isUndefined, "1")([1, , 3])).toBe(true); + expect(lamb.pathSatisfies(lamb.isUndefined, "-2")([1, , 3])).toBe(true); + /* eslint-enable no-sparse-arrays */ + }); + + it("should be able to check objects nested in arrays", function () { + var o = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ] + }; + + expect(lamb.pathSatisfies(lamb.is(20), "data.1.value")(o)).toBe(true); + expect(lamb.pathSatisfies(lamb.is(30), "data.-1.value")(o)).toBe(true); + }); + + it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { + var o = { a: ["abc", new String("def"), "ghi"] }; + + o.a["-1"] = "foo"; + o.a[1]["-2"] = "bar"; + Object.defineProperty(o.a, "-2", { value: 99 }); + + expect(lamb.pathSatisfies(lamb.is("foo"), "a.-1")(o)).toBe(true); + expect(lamb.pathSatisfies(lamb.is("bar"), "a.1.-2")(o)).toBe(true); + expect(lamb.pathSatisfies(lamb.is(99), "a.-2")(o)).toBe(true); + }); + + it("should accept a custom path separator", function () { + expect(lamb.pathSatisfies(lamb.is(4), "b->b->0", "->")(obj)).toBe(true); + expect(lamb.pathSatisfies(lamb.is(6), "c.d/e.f", "/")(obj)).toBe(true); + }); + + it("should accept integers as paths containing a single key", function () { + expect(lamb.pathSatisfies(lamb.is(2), 1)([1, 2])).toBe(true); + expect(lamb.pathSatisfies(lamb.is(2), -1)([1, 2])).toBe(true); + expect(lamb.pathSatisfies(lamb.is("a"), 1)({ 1: "a" })).toBe(true); + }); + + it("should convert other values for the `path` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key, idx) { + expect(lamb.pathSatisfies(lamb.is(values[idx]), key, "_")(testObj)).toBe(true); + }); + + var fooObj = { a: 2, 1: { 5: 3 } }; + + expect(lamb.pathSatisfies(lamb.is(3), 1.5)(fooObj)).toBe(true); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.pathSatisfies()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.pathSatisfies(lamb.is(99), "a")(null); }).toThrow(); + expect(function () { lamb.pathSatisfies(lamb.is(99), "a")(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.pathSatisfies(isDefined, "a")(value)).toBe(false); + }); + + expect(lamb.pathSatisfies(isDefined, "lastIndex")(/foo/)).toBe(true); + }); +}); diff --git a/src/object/__tests__/pick.spec.js b/src/object/__tests__/pick.spec.js new file mode 100644 index 0000000..57ad638 --- /dev/null +++ b/src/object/__tests__/pick.spec.js @@ -0,0 +1,122 @@ +import * as lamb from "../.."; +import { + nonStringsAsStrings, + wannabeEmptyArrays, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("pick / pickKeys", function () { + var baseSimpleObj = { bar: 2 }; + var simpleObj = Object.create(baseSimpleObj, { + foo: { value: 1, enumerable: true }, + baz: { value: 3, enumerable: true } + }); + + var names = [ + { name: "Jane", surname: "Doe" }, + { name: "John", surname: "Doe" }, + { name: "Mario", surname: "Rossi" }, + { name: "Paolo", surname: "Bianchi" } + ]; + + var oddObject = {}; + + nonStringsAsStrings.forEach(function (key, idx) { + oddObject[key] = idx; + }); + + it("should return an object having only the specified properties of the source object (if they exist)", function () { + expect(lamb.pick(simpleObj, ["foo", "baz", "foobaz"])).toEqual({ foo: 1, baz: 3 }); + expect(lamb.pickKeys(["foo", "baz", "foobaz"])(simpleObj)).toEqual({ foo: 1, baz: 3 }); + }); + + it("should include inherited properties", function () { + expect(lamb.pick(simpleObj, ["foo", "bar"])).toEqual({ foo: 1, bar: 2 }); + expect(lamb.pickKeys(["foo", "bar"])(simpleObj)).toEqual({ foo: 1, bar: 2 }); + }); + + it("should include properties with `undefined` values in the result", function () { + expect(lamb.pick({ a: null, b: void 0 }, ["a", "b"])).toStrictEqual({ a: null, b: void 0 }); + expect(lamb.pickKeys(["a", "b"])({ a: null, b: void 0 })).toStrictEqual({ a: null, b: void 0 }); + }); + + it("should accept arrays and array-like objects and integers as keys", function () { + var result = { + 0: { name: "Jane", surname: "Doe" }, + 2: { name: "Mario", surname: "Rossi" } + }; + + expect(lamb.pick(names, ["0", 2])).toEqual(result); + expect(lamb.pickKeys(["0", 2])(names)).toEqual(result); + expect(lamb.pick("bar", [0, 2])).toEqual({ 0: "b", 2: "r" }); + expect(lamb.pickKeys([0, 2])("bar")).toEqual({ 0: "b", 2: "r" }); + }); + + it("should see unassigned or deleted indexes in sparse arrays as non-existing keys", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.pick([1, , 3], [0, 1])).toStrictEqual({ 0: 1 }); + expect(lamb.pickKeys([0, 1])([1, , 3])).toStrictEqual({ 0: 1 }); + /* eslint-enable no-sparse-arrays */ + }); + + it("should see unassigned or deleted indexes in sparse arrays received as the `whitelist` as `undefined` values", function () { + /* eslint-disable comma-spacing, no-sparse-arrays */ + expect(lamb.pick({ undefined: 1, a: 2, b: 3 }, ["a", ,])).toStrictEqual({ undefined: 1, a: 2 }); + expect(lamb.pickKeys(["a", ,])({ undefined: 1, a: 2, b: 3 })).toStrictEqual({ undefined: 1, a: 2 }); + /* eslint-enable comma-spacing, no-sparse-arrays */ + }); + + it("should return an empty object if supplied with an empty list of keys", function () { + expect(lamb.pick(simpleObj, [])).toEqual({}); + expect(lamb.pickKeys([])(simpleObj)).toEqual({}); + }); + + it("should accept an array-like object as the `whitelist` parameter", function () { + expect(lamb.pick({ a: 1, b: 2, c: 3 }, "ac")).toEqual({ a: 1, c: 3 }); + expect(lamb.pickKeys("ac")({ a: 1, b: 2, c: 3 })).toEqual({ a: 1, c: 3 }); + }); + + it("should convert to string every value in the `whitelist` parameter", function () { + var testObj = lamb.merge({ bar: "baz" }, oddObject); + + expect(lamb.pick(testObj, nonStringsAsStrings)).toEqual(oddObject); + expect(lamb.pickKeys(nonStringsAsStrings)(testObj)).toEqual(oddObject); + }); + + it("should throw an exception if called without the main data argument", function () { + expect(lamb.pick).toThrow(); + expect(lamb.pickKeys(["a", "b"])).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` in place of the `whitelist`", function () { + expect(function () { lamb.pick({ a: 1 }, null); }).toThrow(); + expect(function () { lamb.pick({ a: 1 }, void 0); }).toThrow(); + expect(function () { lamb.pickKeys(null)({ a: 1 }); }).toThrow(); + expect(function () { lamb.pickKeys(void 0)({ a: 1 }); }).toThrow(); + expect(function () { lamb.pickKeys()({ a: 1 }); }).toThrow(); + }); + + it("should treat other values for the `whitelist` parameter as an empty array and return an empty object", function () { + wannabeEmptyArrays.forEach(function (value) { + expect(lamb.pick({ a: 1, b: 2 }, value)).toEqual({}); + expect(lamb.pickKeys(value)({ a: 1, b: 2 })).toEqual({}); + }); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.pick(null, ["a", "b"]); }).toThrow(); + expect(function () { lamb.pick(void 0, ["a", "b"]); }).toThrow(); + expect(function () { lamb.pickKeys(["a", "b"])(null); }).toThrow(); + expect(function () { lamb.pickKeys(["a", "b"])(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.pick(v, ["a"])).toEqual({}); + expect(lamb.pickKeys(["a"])(v)).toEqual({}); + }); + + expect(lamb.pick(/foo/, ["lastIndex"])).toEqual({ lastIndex: 0 }); + expect(lamb.pickKeys(["lastIndex"])(/foo/)).toEqual({ lastIndex: 0 }); + }); +}); diff --git a/src/object/__tests__/pickIf.spec.js b/src/object/__tests__/pickIf.spec.js new file mode 100644 index 0000000..e9fac4d --- /dev/null +++ b/src/object/__tests__/pickIf.spec.js @@ -0,0 +1,69 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("pickIf", function () { + var names = [ + { name: "Jane", surname: "Doe" }, + { name: "John", surname: "Doe" }, + { name: "Mario", surname: "Rossi" }, + { name: "Paolo", surname: "Bianchi" } + ]; + + var persons = [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "John", surname: "Doe", age: 40, city: "London" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + { name: "Paolo", surname: "Bianchi", age: 15, city: "Amsterdam" } + ]; + + var isNumber = function (v) { return typeof v === "number"; }; + var isNameKey = function (value, key) { return key.indexOf("name") !== -1; }; + + // to check "truthy" and "falsy" values returned by predicates + var isNameKey2 = function (value, key) { return ~key.indexOf("name"); }; + + it("should pick object properties using a predicate", function () { + expect(lamb.pickIf(isNumber)(persons[0])).toEqual({ age: 12 }); + expect(persons.map(lamb.pickIf(isNameKey))).toEqual(names); + }); + + it("should include properties with `undefined` values in the result", function () { + expect(lamb.pickIf(lamb.isNil)({ a: null, b: void 0 })).toStrictEqual({ a: null, b: void 0 }); + }); + + it("should accept array-like objects", function () { + var isEven = function (n) { return n % 2 === 0; }; + + expect(lamb.pickIf(isEven)([1, 2, 3, 4])).toEqual({ 1: 2, 3: 4 }); + expect(lamb.pickIf(isEven)("1234")).toEqual({ 1: "2", 3: "4" }); + }); + + it("should not consider unassigned or deleted indexes in sparse arrays", function () { + // eslint-disable-next-line no-sparse-arrays + expect(lamb.pickIf(lamb.isUndefined)([1, , 3, void 0])).toStrictEqual({ 3: void 0 }); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(persons.map(lamb.pickIf(isNameKey2))).toEqual(names); + }); + + it("should throw an exception if the predicate isn't a function or if is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.pickIf(value)({ a: 2 }); }).toThrow(); + }); + + expect(function () { lamb.pickIf()({ a: 2 }); }).toThrow(); + }); + + it("should throw an exception if the `source` is `null` or `undefined` or if is missing", function () { + expect(lamb.pickIf(isNumber)).toThrow(); + expect(function () { lamb.pickIf(isNumber)(null); }).toThrow(); + expect(function () { lamb.pickIf(isNumber)(void 0); }).toThrow(); + }); + + it("should convert to object every other value received as `source`", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.pickIf(isNumber)(v)).toEqual({}); + }); + }); +}); diff --git a/src/object/__tests__/renaming.spec.js b/src/object/__tests__/renaming.spec.js new file mode 100644 index 0000000..29a6ec2 --- /dev/null +++ b/src/object/__tests__/renaming.spec.js @@ -0,0 +1,195 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("rename / renameKeys / renameWith", function () { + var baseObj = { "": 0, a: 1, b: 2, c: 3, d: 4 }; + var obj = Object.create(baseObj, { + e: { value: 5, enumerable: true }, + f: { value: 6 } + }); + var objEquivalent = { "": 0, a: 1, b: 2, c: 3, d: 4, e: 5 }; + + // The "toEqual" matcher would have checked only own enumerable properties + afterEach(function () { + for (var key in objEquivalent) { + expect(objEquivalent[key]).toEqual(obj[key]); + } + + expect(obj.f).toBe(6); + }); + + describe("renameWith", function () { + it("should use the provided function to generate a keys' map for `rename`", function () { + var person = { NAME: "John", SURNAME: "Doe" }; + var makeLowerKeysMap = jest.fn(function (source) { + var sourceKeys = lamb.keys(source); + + return lamb.make(sourceKeys, sourceKeys.map(lamb.invoker("toLowerCase"))); + }); + + expect(lamb.renameWith(makeLowerKeysMap)(person)).toEqual({ name: "John", surname: "Doe" }); + expect(makeLowerKeysMap).toHaveBeenCalledTimes(1); + expect(makeLowerKeysMap.mock.calls[0].length).toBe(1); + expect(makeLowerKeysMap.mock.calls[0][0]).toBe(person); + }); + + it("should build a function throwing an exception if `fn` isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.renameWith(value)).toThrow(); + }); + + expect(lamb.renameWith()).toThrow(); + }); + }); + + it("should rename the keys of the given object according to the provided keys' map", function () { + var keysMap = { a: "w", b: "x", c: "y", d: "z" }; + var result = { "": 0, w: 1, x: 2, y: 3, z: 4, e: 5 }; + + expect(lamb.rename(obj, keysMap)).toEqual(result); + expect(lamb.renameKeys(keysMap)(obj)).toEqual(result); + expect(lamb.renameWith(lamb.always(keysMap))(obj)).toEqual(result); + }); + + it("should be possible to use existing key names", function () { + var keysMap = { c: "x", e: "b", b: "e" }; + var result = { "": 0, a: 1, e: 2, x: 3, d: 4, b: 5 }; + + expect(lamb.rename(obj, keysMap)).toEqual(result); + expect(lamb.renameKeys(keysMap)(obj)).toEqual(result); + expect(lamb.renameWith(lamb.always(keysMap))(obj)).toEqual(result); + }); + + it("should not add non-existing keys", function () { + var keysMap = { z: "x", y: "c" }; + var r1 = lamb.rename(obj, keysMap); + var r2 = lamb.renameKeys(keysMap)(obj); + var r3 = lamb.renameWith(lamb.always(keysMap))(obj); + + expect(r1).toEqual(objEquivalent); + expect(r2).toEqual(objEquivalent); + expect(r3).toEqual(objEquivalent); + expect(r1).not.toBe(obj); + expect(r2).not.toBe(obj); + expect(r3).not.toBe(obj); + }); + + it("should give priority to the map and overwrite an existing key if necessary", function () { + var keysMap = { a: "", b: "c" }; + var result = { "": 1, c: 2, d: 4, e: 5 }; + + expect(lamb.rename(obj, keysMap)).toEqual(result); + expect(lamb.renameKeys(keysMap)(obj)).toEqual(result); + expect(lamb.renameWith(lamb.always(keysMap))(obj)).toEqual(result); + }); + + it("should return a copy of the source if the keys' map is empty or contains only non-enumerable properties", function () { + var r1 = lamb.rename(obj, {}); + var r2 = lamb.renameKeys({})(obj); + var r3 = lamb.renameWith(lamb.always({}))(obj); + var r4 = lamb.rename(obj, { f: "z" }); + var r5 = lamb.renameKeys({ f: "z" })(obj); + var r6 = lamb.renameWith(lamb.always({ f: "z" }))(obj); + + expect(r1).toEqual(objEquivalent); + expect(r2).toEqual(objEquivalent); + expect(r3).toEqual(objEquivalent); + expect(r4).toEqual(objEquivalent); + expect(r5).toEqual(objEquivalent); + expect(r6).toEqual(objEquivalent); + expect(r1).not.toBe(obj); + expect(r2).not.toBe(obj); + expect(r3).not.toBe(obj); + expect(r4).not.toBe(obj); + expect(r5).not.toBe(obj); + expect(r6).not.toBe(obj); + }); + + it("should accept array-like objects as a source", function () { + var arr = [1, 2, 3]; + var s = "foo"; + var keysMap = { 0: "a", 1: "b", 2: "c" }; + var r1 = { a: 1, b: 2, c: 3 }; + var r2 = { a: "f", b: "o", c: "o" }; + + expect(lamb.rename(arr, keysMap)).toEqual(r1); + expect(lamb.rename(s, keysMap)).toEqual(r2); + expect(lamb.renameKeys(keysMap)(arr)).toEqual(r1); + expect(lamb.renameKeys(keysMap)(s)).toEqual(r2); + expect(lamb.renameWith(lamb.always(keysMap))(arr)).toEqual(r1); + expect(lamb.renameWith(lamb.always(keysMap))(s)).toEqual(r2); + }); + + it("should not consider unassigned or deleted indexes when the source is a sparse array", function () { + var arr = [1, , 3]; // eslint-disable-line no-sparse-arrays + var keysMap = { 0: "a", 1: "b", 2: "c" }; + var result = { a: 1, c: 3 }; + + expect(lamb.rename(arr, keysMap)).toStrictEqual(result); + expect(lamb.renameKeys(keysMap)(arr)).toStrictEqual(result); + expect(lamb.renameWith(lamb.always(keysMap))(arr)).toStrictEqual(result); + }); + + it("should accept array-like objects as key maps", function () { + var arr = [1, 2, 3]; + var s = "bar"; + var someObj = { 0: "a", 1: "b", 2: "c" }; + var r1 = { 1: "a", 2: "b", 3: "c" }; + var r2 = { b: "a", a: "b", r: "c" }; + + expect(lamb.rename(someObj, arr)).toEqual(r1); + expect(lamb.rename(someObj, s)).toEqual(r2); + expect(lamb.renameKeys(arr)(someObj)).toEqual(r1); + expect(lamb.renameKeys(s)(someObj)).toEqual(r2); + expect(lamb.renameWith(lamb.always(arr))(someObj)).toEqual(r1); + expect(lamb.renameWith(lamb.always(s))(someObj)).toEqual(r2); + }); + + it("should not consider unassigned or deleted indexes when a sparse array is supplied as a key map", function () { + var someObj = { 0: "a", 1: "b", 2: "c" }; + var arrKeyMap = [1, , 3]; // eslint-disable-line no-sparse-arrays + var result = { 1: "a", 3: "c" }; + + expect(lamb.rename(someObj, arrKeyMap)).toStrictEqual(result); + expect(lamb.renameKeys(arrKeyMap)(someObj)).toStrictEqual(result); + expect(lamb.renameWith(lamb.always(arrKeyMap))(someObj)).toStrictEqual(result); + }); + + it("should return a copy of the source object for any other value passed as the keys' map", function () { + wannabeEmptyObjects.forEach(function (value) { + var r1 = lamb.rename(obj, value); + var r2 = lamb.renameKeys(value)(obj); + var r3 = lamb.renameWith(lamb.always(value))(obj); + + expect(r1).toEqual(objEquivalent); + expect(r2).toEqual(objEquivalent); + expect(r3).toEqual(objEquivalent); + expect(r1).not.toBe(obj); + expect(r2).not.toBe(obj); + expect(r3).not.toBe(obj); + }); + }); + + it("should throw an exception if called without the source or the keys' map", function () { + expect(lamb.rename).toThrow(); + expect(lamb.renameKeys()).toThrow(); + expect(lamb.renameWith(lamb.always({}))).toThrow(); + }); + + it("should throw an exception if the source is `null` or `undefined`", function () { + expect(function () { lamb.rename(null, {}); }).toThrow(); + expect(function () { lamb.rename(void 0, {}); }).toThrow(); + expect(function () { lamb.renameKeys({})(null); }).toThrow(); + expect(function () { lamb.renameKeys({})(void 0); }).toThrow(); + expect(function () { lamb.renameWith(lamb.always({}))(null); }).toThrow(); + expect(function () { lamb.renameWith(lamb.always({}))(void 0); }).toThrow(); + }); + + it("should return an empty object for any other value passed as the source object", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.rename(value, { 0: 9 })).toEqual({}); + expect(lamb.renameKeys({ 0: 9 })(value)).toEqual({}); + expect(lamb.renameWith(lamb.always({ 0: 9 }))(value)).toEqual({}); + }); + }); +}); diff --git a/src/object/__tests__/setIn.spec.js b/src/object/__tests__/setIn.spec.js new file mode 100644 index 0000000..2b10846 --- /dev/null +++ b/src/object/__tests__/setIn.spec.js @@ -0,0 +1,105 @@ +import * as lamb from "../.."; +import { + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("setIn / setKey", function () { + var arr = [1, 2, 3]; + var sparseArr = [, 5, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays + var sparseArrCopy = sparseArr.slice(); + var baseFoo = Object.create({ a: arr }, { b: { value: 2, enumerable: true }, z: { value: 5 } }); + var foo = Object.create(baseFoo, { c: { value: 3, enumerable: true } }); + + var fooEquivalent = { a: [1, 2, 3], b: 2, c: 3, z: 5 }; + + afterEach(function () { + // The "toEqual" matcher would have checked only own enumerable properties + for (var key in fooEquivalent) { + expect(fooEquivalent[key]).toEqual(foo[key]); + } + + expect(foo.a).toBe(arr); + expect(sparseArr).toStrictArrayEqual(sparseArrCopy); + }); + + it("should build a copy of the source object with all its enumerable properties and the desired key set to the provided value", function () { + var newObjA = lamb.setIn(foo, "c", 99); + var newObjB = lamb.setKey("a", ["a", "b"])(foo); + + expect(newObjA).toEqual({ a: [1, 2, 3], b: 2, c: 99 }); + expect(newObjA.a).toBe(arr); + expect(newObjA.z).toBeUndefined(); + expect(newObjB).toEqual({ a: ["a", "b"], b: 2, c: 3 }); + expect(newObjB.a).not.toBe(arr); + expect(newObjB.z).toBeUndefined(); + }); + + it("should add a new property if the given key doesn't exist on the source or if it isn't enumerable", function () { + var newObjA = lamb.setIn(foo, "z", 99); + var newObjB = lamb.setKey("z", 0)(foo); + + expect(newObjA).toEqual({ a: [1, 2, 3], b: 2, c: 3, z: 99 }); + expect(newObjB).toEqual({ a: [1, 2, 3], b: 2, c: 3, z: 0 }); + }); + + it("should transform array-like objects in objects with numbered string as properties", function () { + expect(lamb.setIn([1, 2], "a", 99)).toEqual({ 0: 1, 1: 2, a: 99 }); + expect(lamb.setKey("a", 99)([1, 2])).toEqual({ 0: 1, 1: 2, a: 99 }); + expect(lamb.setIn("foo", "a", 99)).toEqual({ 0: "f", 1: "o", 2: "o", a: 99 }); + expect(lamb.setKey("a", 99)("foo")).toEqual({ 0: "f", 1: "o", 2: "o", a: 99 }); + }); + + it("should accept integers as keys", function () { + expect(lamb.setIn([1, 2], 1, 3)).toEqual({ 0: 1, 1: 3 }); + expect(lamb.setKey(1, 3)([1, 2])).toEqual({ 0: 1, 1: 3 }); + }); + + it("should use only defined keys in sparse arrays", function () { + var r1 = { 1: 99 }; + var r2 = { 1: 5, 2: 99 }; + + expect(lamb.setIn(sparseArr, 1, 99)).toStrictEqual(r1); + expect(lamb.setIn(sparseArr, 2, 99)).toStrictEqual(r2); + expect(lamb.setKey(1, 99)(sparseArr)).toStrictEqual(r1); + expect(lamb.setKey(2, 99)(sparseArr)).toStrictEqual(r2); + }); + + it("should convert other values for the `key` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + var expected = lamb.merge({}, testObj); + + expected[String(key)] = 99; + + expect(lamb.setIn(testObj, key, 99)).toEqual(expected); + expect(lamb.setKey(key, 99)(testObj)).toEqual(expected); + }); + + expect(lamb.setIn({ a: 2 })).toStrictEqual({ a: 2, undefined: void 0 }); + expect(lamb.setKey()({ a: 2 })).toStrictEqual({ a: 2, undefined: void 0 }); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.setIn).toThrow(); + expect(lamb.setKey()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.setIn(null, "a", 99); }).toThrow(); + expect(function () { lamb.setIn(void 0, "a", 99); }).toThrow(); + expect(function () { lamb.setKey("a", 99)(null); }).toThrow(); + expect(function () { lamb.setKey("a", 99)(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.setIn(value, "a", 99)).toEqual({ a: 99 }); + expect(lamb.setKey("a", 99)(value)).toEqual({ a: 99 }); + }); + }); +}); diff --git a/src/object/__tests__/setPathIn.spec.js b/src/object/__tests__/setPathIn.spec.js new file mode 100644 index 0000000..4992643 --- /dev/null +++ b/src/object/__tests__/setPathIn.spec.js @@ -0,0 +1,425 @@ +import * as lamb from "../.."; +import { nonStrings, nonStringsAsStrings, wannabeEmptyObjects } from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("setPath / setPathIn", function () { + var obj = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo" + }, + "c.d": { "e.f": 6 } + }; + + var sparseArr = Object.freeze([, 55, ,]); // eslint-disable-line comma-spacing, no-sparse-arrays + + obj.b.d = sparseArr; + + Object.defineProperty(obj.b, "w", { + value: { + x: 22, + y: { z: 33 } + } + }); + + var objCopy = JSON.parse(JSON.stringify(obj)); + + objCopy.b.d = sparseArr; + + afterEach(function () { + expect(obj).toEqual(objCopy); + expect(obj.b.d).toStrictArrayEqual(objCopy.b.d); + }); + + it("should allow to set a nested property in a copy of the given object", function () { + var r1 = lamb.setPath("a", 99, ".")(obj); + var r2 = lamb.setPathIn(obj, "a", 99, "."); + + expect(r1).toEqual({ + a: 99, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: sparseArr + }, + "c.d": { "e.f": 6 } + }); + + expect(r1.b).toBe(obj.b); + expect(r1["c.d"]).toBe(obj["c.d"]); + expect(r1.b.d).toStrictArrayEqual(sparseArr); + + expect(r2).toEqual(r1); + expect(r2.b.d).toStrictArrayEqual(sparseArr); + + var r3 = lamb.setPath("b.c", "bar", ".")(obj); + var r4 = lamb.setPathIn(obj, "b.c", "bar", "."); + + expect(r3).toEqual({ + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "bar", + d: sparseArr + }, + "c.d": { "e.f": 6 } + }); + + expect(r3.b.a).toBe(obj.b.a); + expect(r3.b.b).toBe(obj.b.b); + expect(r3["c.d"]).toBe(obj["c.d"]); + expect(r3.b.d).toStrictArrayEqual(sparseArr); + + expect(r4).toEqual(r3); + expect(r4.b.d).toStrictArrayEqual(sparseArr); + }); + + it("should use the dot as the default separator", function () { + var r = lamb.setPath("b.a.g", 99)(obj); + + expect(r).toEqual({ + a: 2, + b: { + a: { g: 99, h: 11 }, + b: [4, 5], + c: "foo", + d: sparseArr + }, + "c.d": { "e.f": 6 } + }); + expect(r.b.b).toBe(obj.b.b); + expect(r["c.d"]).toBe(obj["c.d"]); + expect(lamb.setPathIn(obj, "b.a.g", 99)).toEqual(r); + }); + + it("should ignore extra arguments passed to the built function in its partially applied form", function () { + var r = lamb.setPath("b.c", "bar")(obj, {}); + + expect(r).toEqual({ + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "bar", + d: sparseArr + }, + "c.d": { "e.f": 6 } + }); + }); + + it("should allow custom separators", function () { + var r = lamb.setPath("c.d->e.f", 99, "->")(obj); + + expect(r).toEqual({ + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 99 } + }); + expect(r.b).toBe(obj.b); + expect(lamb.setPathIn(obj, "c.d->e.f", 99, "->")).toEqual(r); + }); + + it("should add non-existent properties to existing objects", function () { + var r1 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, , ], // eslint-disable-line comma-spacing, no-sparse-arrays + z: 99 + }, + "c.d": { "e.f": 6 } + }; + var r2 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 }, + z: { a: 99 } + }; + var r3 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 }, + z: { a: { b: 99 } } + }; + + expect(lamb.setPath("b.z", 99)(obj)).toEqual(r1); + expect(lamb.setPathIn(obj, "b.z", 99)).toEqual(r1); + expect(lamb.setPath("z.a", 99)(obj)).toEqual(r2); + expect(lamb.setPathIn(obj, "z.a", 99)).toEqual(r2); + expect(lamb.setPath("z.a.b", 99)(obj)).toEqual(r3); + expect(lamb.setPathIn(obj, "z.a.b", 99)).toEqual(r3); + + var o = { a: null }; + var r4 = { a: { b: { c: 99 } } }; + + expect(lamb.setPath("a.b.c", 99)(o)).toEqual(r4); + expect(lamb.setPathIn(o, "a.b.c", 99)).toEqual(r4); + }); + + it("should treat non-enumerable properties encountered in a path as non-existent properties", function () { + var r1 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, , ], // eslint-disable-line comma-spacing, no-sparse-arrays + w: { z: 99 } + }, + "c.d": { "e.f": 6 } + }; + var r2 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, , ], // eslint-disable-line comma-spacing, no-sparse-arrays + w: { y: { z: 99 } } + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.setPathIn(obj, "b.w.z", 99)).toEqual(r1); + expect(lamb.setPath("b.w.z", 99)(obj)).toEqual(r1); + expect(lamb.setPathIn(obj, "b.w.y.z", 99)).toEqual(r2); + expect(lamb.setPath("b.w.y.z", 99)(obj)).toEqual(r2); + }); + + it("should replace indexes when an array is found and the key is a string containing an integer", function () { + var r = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 99], + c: "foo", + d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.setPath("b.b.1", 99)(obj)).toEqual(r); + expect(lamb.setPathIn(obj, "b.b.1", 99)).toEqual(r); + expect(lamb.setPath("1", 99)([1, 2, 3])).toEqual([1, 99, 3]); + expect(lamb.setPathIn([1, 2, 3], "1", 99)).toEqual([1, 99, 3]); + }); + + it("should allow using negative array indexes in path parts", function () { + var r = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [99, 5], + c: "foo", + d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.setPath("b.b.-2", 99)(obj)).toEqual(r); + expect(lamb.setPathIn(obj, "b.b.-2", 99)).toEqual(r); + expect(lamb.setPath("-2", 99)([1, 2, 3])).toEqual([1, 99, 3]); + expect(lamb.setPathIn([1, 2, 3], "-2", 99)).toEqual([1, 99, 3]); + }); + + it("should build dense arrays when the path target is a sparse array index", function () { + var expected1 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [void 0, 99, void 0] + }, + "c.d": { "e.f": 6 } + }; + var expected2 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [void 0, 55, 99] + }, + "c.d": { "e.f": 6 } + }; + + ["b.d.1", "b.d.-2", "b.d.-1", "b.d.2"].forEach(function (path, idx) { + var r1 = lamb.setPathIn(obj, path, 99); + var r2 = lamb.setPath(path, 99)(obj); + var expected = idx < 2 ? expected1 : expected2; + + expect(r1).toEqual(expected); + expect(r1.b.d).toStrictArrayEqual(expected.b.d); + + expect(r2).toEqual(expected); + expect(r2.b.d).toStrictArrayEqual(expected.b.d); + }); + }); + + it("should not add new elements to an array and behave like `setAt` which returns a copy of the array", function () { + var r1 = lamb.setPath("b.b.2", 99)(obj); + var r2 = lamb.setPathIn(obj, "b.b.2", 99); + var r3 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [void 0, 55, void 0] + }, + "c.d": { "e.f": 6 } + }; + + expect(r1).toEqual(obj); + expect(r2).toEqual(obj); + expect(r1.b.b).not.toBe(obj.b.b); + expect(r2.b.b).not.toBe(obj.b.b); + + expect(lamb.setPathIn(obj, "b.d.11", 99)).toEqual(r3); + expect(lamb.setPath("b.d.11", 99)(obj)).toEqual(r3); + }); + + it("should allow to change values nested in an array", function () { + var o = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ] + }; + var r = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 99 }, + { id: 3, value: 30 } + ] + }; + + expect(lamb.setPath("data.1.value", 99)(o)).toEqual(r); + expect(lamb.setPathIn(o, "data.1.value", 99)).toEqual(r); + expect(lamb.setPath("data.-2.value", 99)(o)).toEqual(r); + expect(lamb.setPathIn(o, "data.-2.value", 99)).toEqual(r); + }); + + it("should build an object with numbered keys when an array-like object is found", function () { + var r = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: { 0: "m", 1: "o", 2: "o" }, + d: sparseArr + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.setPath("b.c.0", "m")(obj)).toEqual(r); + expect(lamb.setPathIn(obj, "b.c.0", "m")).toEqual(r); + }); + + it("should build an object with numbered keys when an array is found and the key is not a string containing an integer", function () { + var r = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: { 0: 4, 1: 5, z: 99 }, + c: "foo", + d: sparseArr + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.setPath("b.b.z", 99)(obj)).toEqual(r); + expect(lamb.setPathIn(obj, "b.b.z", 99)).toEqual(r); + }); + + it("should accept integers as paths containing a single key", function () { + expect(lamb.setPath(1, 99)([1, 2, 3])).toEqual([1, 99, 3]); + expect(lamb.setPathIn([1, 2, 3], -1, 99)).toEqual([1, 2, 99]); + expect(lamb.setPath(2, 99)([1, 2])).toEqual([1, 2]); + expect(lamb.setPathIn({ a: 1 }, 1, 99)).toEqual({ a: 1, 1: 99 }); + }); + + it("should give priority to object keys over array indexes when a negative index is encountered", function () { + var o = { a: ["abc", "def", "ghi"] }; + + o.a["-1"] = "foo"; + + var r = { a: { 0: "abc", 1: "def", 2: "ghi", "-1": 99 } }; + + expect(lamb.setPath("a.-1", 99)(o)).toEqual(r); + expect(lamb.setPathIn(o, "a.-1", 99)).toEqual(r); + }); + + it("should consider a negative integer to be an index if the property exists but it's not enumerable", function () { + var o = { a: ["abc", "def", "ghi"] }; + + Object.defineProperty(o.a, "-1", { value: 99 }); + + var r = { a: ["abc", "def", "foo"] }; + + expect(lamb.setPath("a.-1", "foo")(o)).toEqual(r); + expect(lamb.setPathIn(o, "a.-1", "foo")).toEqual(r); + }); + + it("should convert other values for the `path` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + var expected = lamb.merge({}, testObj); + + expected[String(key)] = 99; + + expect(lamb.setPathIn(testObj, key, 99, "_")).toEqual(expected); + expect(lamb.setPath(key, 99, "_")(testObj)).toEqual(expected); + }); + + expect(lamb.setPathIn({ a: 2 }, 1.5, 99)).toEqual({ a: 2, 1: { 5: 99 } }); + expect(lamb.setPath(1.5, 99)({ a: 2 })).toEqual({ a: 2, 1: { 5: 99 } }); + + expect(lamb.setPathIn({ a: 2 })).toEqual({ a: 2, undefined: void 0 }); + expect(lamb.setPath()({ a: 2 })).toEqual({ a: 2, undefined: void 0 }); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.setPathIn).toThrow(); + expect(lamb.setPath()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.setPathIn(null, "a", 99); }).toThrow(); + expect(function () { lamb.setPathIn(void 0, "a", 99); }).toThrow(); + expect(function () { lamb.setPath("a", 99)(null); }).toThrow(); + expect(function () { lamb.setPath("a", 99)(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.setPathIn(value, "a", 99)).toEqual({ a: 99 }); + expect(lamb.setPath("a", 99)(value)).toEqual({ a: 99 }); + }); + }); +}); diff --git a/src/object/__tests__/skip.spec.js b/src/object/__tests__/skip.spec.js new file mode 100644 index 0000000..e22b2d8 --- /dev/null +++ b/src/object/__tests__/skip.spec.js @@ -0,0 +1,132 @@ +import * as lamb from "../.."; +import { + nonStringsAsStrings, + wannabeEmptyArrays, + wannabeEmptyObjects +} from "../../__tests__/commons"; + +describe("skip / skipKeys", function () { + var baseSimpleObj = { bar: 2 }; + var simpleObj = Object.create(baseSimpleObj, { + foo: { value: 1, enumerable: true }, + baz: { value: 3, enumerable: true } + }); + + var names = [ + { name: "Jane", surname: "Doe" }, + { name: "John", surname: "Doe" }, + { name: "Mario", surname: "Rossi" }, + { name: "Paolo", surname: "Bianchi" } + ]; + + var oddObject = {}; + + nonStringsAsStrings.forEach(function (key, idx) { + oddObject[key] = idx; + }); + + it("should return a copy of the given object without the specified properties", function () { + expect(lamb.skip(simpleObj, ["bar", "baz"])).toEqual({ foo: 1 }); + expect(lamb.skipKeys(["bar", "baz"])(simpleObj)).toEqual({ foo: 1 }); + }); + + it("should include inherited properties", function () { + expect(lamb.skip(simpleObj, ["foo"])).toEqual({ bar: 2, baz: 3 }); + expect(lamb.skipKeys(["foo"])(simpleObj)).toEqual({ bar: 2, baz: 3 }); + }); + + it("should include properties with `undefined` values in the result", function () { + expect(lamb.skip({ a: null, b: void 0 }, ["c"])).toEqual({ a: null, b: void 0 }); + expect(lamb.skipKeys(["c"])({ a: null, b: void 0 })).toEqual({ a: null, b: void 0 }); + }); + + it("should accept arrays and array-like objects and integers as keys", function () { + var result = { + 0: { name: "Jane", surname: "Doe" }, + 2: { name: "Mario", surname: "Rossi" } + }; + + expect(lamb.skip(names, ["1", 3])).toEqual(result); + expect(lamb.skipKeys(["1", 3])(names)).toEqual(result); + expect(lamb.skip("bar", [0, 2])).toEqual({ 1: "a" }); + expect(lamb.skipKeys([0, 2])("bar")).toEqual({ 1: "a" }); + }); + + it("should see unassigned or deleted indexes in sparse arrays as non-existing keys", function () { + /* eslint-disable no-sparse-arrays */ + expect(lamb.skip([1, , 3], [2])).toEqual({ 0: 1 }); + expect(lamb.skipKeys([2])([1, , 3])).toEqual({ 0: 1 }); + /* eslint-enable no-sparse-arrays */ + }); + + it("should see unassigned or deleted indexes in sparse arrays received as the `blacklist` as `undefined` values", function () { + /* eslint-disable comma-spacing, no-sparse-arrays */ + expect(lamb.skip({ undefined: 1, a: 2, b: 3 }, ["a", ,])).toEqual({ b: 3 }); + expect(lamb.skipKeys(["a", ,])({ undefined: 1, a: 2, b: 3 })).toEqual({ b: 3 }); + /* eslint-enable comma-spacing, no-sparse-arrays */ + }); + + it("should return a copy of the source object if supplied with an empty list of keys", function () { + var r1 = lamb.skip(simpleObj, []); + var r2 = lamb.skipKeys([])(simpleObj); + + expect(r1).toEqual({ foo: 1, bar: 2, baz: 3 }); + expect(r1).not.toBe(simpleObj); + expect(r2).toEqual({ foo: 1, bar: 2, baz: 3 }); + expect(r2).not.toBe(simpleObj); + }); + + it("should accept an array-like object as the `blacklist` parameter", function () { + expect(lamb.skip({ a: 1, b: 2, c: 3 }, "ac")).toEqual({ b: 2 }); + expect(lamb.skipKeys("ac")({ a: 1, b: 2, c: 3 })).toEqual({ b: 2 }); + }); + + it("should convert to string every value in the `blacklist` parameter", function () { + var testObj = lamb.merge({ bar: "baz" }, oddObject); + + expect(lamb.skip(testObj, nonStringsAsStrings)).toEqual({ bar: "baz" }); + expect(lamb.skipKeys(nonStringsAsStrings)(testObj)).toEqual({ bar: "baz" }); + }); + + it("should throw an exception if called without the main data argument", function () { + expect(lamb.skip).toThrow(); + expect(lamb.skipKeys(["a", "b"])).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` in place of the `blacklist`", function () { + expect(function () { lamb.skip({ a: 1 }, null); }).toThrow(); + expect(function () { lamb.skip({ a: 1 }, void 0); }).toThrow(); + expect(function () { lamb.skipKeys(null)({ a: 1 }); }).toThrow(); + expect(function () { lamb.skipKeys(void 0)({ a: 1 }); }).toThrow(); + expect(function () { lamb.skipKeys()({ a: 1 }); }).toThrow(); + }); + + it("should treat other values for the `blacklist` parameter as an empty array and return a copy of the source object", function () { + wannabeEmptyArrays.forEach(function (value) { + var r1 = lamb.skip(simpleObj, value); + var r2 = lamb.skipKeys(value)(simpleObj); + + expect(r1).toEqual({ foo: 1, bar: 2, baz: 3 }); + expect(r1).not.toBe(simpleObj); + expect(r2).toEqual({ foo: 1, bar: 2, baz: 3 }); + expect(r2).not.toBe(simpleObj); + }); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.skip(null, ["a", "b"]); }).toThrow(); + expect(function () { lamb.skip(void 0, ["a", "b"]); }).toThrow(); + expect(function () { lamb.skipKeys(["a", "b"])(null); }).toThrow(); + expect(function () { lamb.skipKeys(["a", "b"])(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.skip(v, ["a"])).toEqual({}); + expect(lamb.skipKeys(["a"])(v)).toEqual({}); + }); + + expect(lamb.skip(/foo/, ["lastIndex"])).toEqual({}); + expect(lamb.skipKeys(["lastIndex"])(/foo/)).toEqual({}); + }); +}); diff --git a/src/object/__tests__/skipIf.spec.js b/src/object/__tests__/skipIf.spec.js new file mode 100644 index 0000000..50fee43 --- /dev/null +++ b/src/object/__tests__/skipIf.spec.js @@ -0,0 +1,73 @@ +import * as lamb from "../.."; +import { nonFunctions, wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("skipIf", function () { + var agesAndCities = [ + { age: 12, city: "New York" }, + { age: 40, city: "London" }, + { age: 18, city: "Rome" }, + { age: 15, city: "Amsterdam" } + ]; + + var persons = [ + { name: "Jane", surname: "Doe", age: 12, city: "New York" }, + { name: "John", surname: "Doe", age: 40, city: "London" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome" }, + { name: "Paolo", surname: "Bianchi", age: 15, city: "Amsterdam" } + ]; + + var isNumber = function (v) { return typeof v === "number"; }; + var isNameKey = function (value, key) { return key.indexOf("name") !== -1; }; + + // to check "truthy" and "falsy" values returned by predicates + var isNameKey2 = function (value, key) { return ~key.indexOf("name"); }; + + it("should skip object properties using a predicate", function () { + expect(persons.map(lamb.skipIf(isNameKey))).toEqual(agesAndCities); + expect(lamb.skipIf(isNumber)(persons[0])).toEqual( + { name: "Jane", surname: "Doe", city: "New York" } + ); + }); + + it("should include properties with `undefined` values in the result", function () { + var isNotNil = lamb.not(lamb.isNil); + + expect(lamb.skipIf(isNotNil)({ a: null, b: void 0 })).toStrictEqual({ a: null, b: void 0 }); + }); + + it("should accept array-like objects", function () { + var isEven = function (n) { return n % 2 === 0; }; + + expect(lamb.skipIf(isEven)([1, 2, 3, 4])).toEqual({ 0: 1, 2: 3 }); + expect(lamb.skipIf(isEven)("1234")).toEqual({ 0: "1", 2: "3" }); + }); + + it("should not consider unassigned or deleted indexes in sparse arrays", function () { + // eslint-disable-next-line no-sparse-arrays + expect(lamb.skipIf(lamb.not(lamb.isUndefined))([1, , 3, void 0])).toStrictEqual({ 3: void 0 }); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + expect(persons.map(lamb.skipIf(isNameKey2))).toEqual(agesAndCities); + }); + + it("should throw an exception if the predicate isn't a function or if is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.skipIf(value)({ a: 2 }); }).toThrow(); + }); + + expect(function () { lamb.skipIf()({ a: 2 }); }).toThrow(); + }); + + it("should throw an exception if the `source` is `null` or `undefined` or if is missing", function () { + expect(lamb.skipIf(isNumber)).toThrow(); + expect(function () { lamb.skipIf(isNumber)(null); }).toThrow(); + expect(function () { lamb.skipIf(isNumber)(void 0); }).toThrow(); + }); + + it("should convert to object every other value received as `source`", function () { + wannabeEmptyObjects.forEach(function (v) { + expect(lamb.skipIf(isNumber)(v)).toEqual({}); + }); + }); +}); diff --git a/src/object/__tests__/tear.spec.js b/src/object/__tests__/tear.spec.js new file mode 100644 index 0000000..583838a --- /dev/null +++ b/src/object/__tests__/tear.spec.js @@ -0,0 +1,64 @@ +import * as lamb from "../.."; +import { wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("tear / tearOwn", function () { + var baseFoo = Object.create({ a: 1 }, { b: { value: 2 } }); + var foo = Object.create(baseFoo, { + c: { value: 3 }, + d: { value: 4, enumerable: true } + }); + + it("should transform an object in two lists, one containing its keys, the other containing the corresponding values", function () { + expect(lamb.tear({ a: 1, b: 2, c: 3 })).toEqual([["a", "b", "c"], [1, 2, 3]]); + expect(lamb.tearOwn({ a: 1, b: 2, c: 3 })).toEqual([["a", "b", "c"], [1, 2, 3]]); + }); + + it("should work with array-like objects", function () { + var r1 = [["0", "1", "2"], [1, 2, 3]]; + var r2 = [["0", "1", "2"], ["a", "b", "c"]]; + + expect(lamb.tear([1, 2, 3])).toEqual(r1); + expect(lamb.tear("abc")).toEqual(r2); + expect(lamb.tearOwn([1, 2, 3])).toEqual(r1); + expect(lamb.tearOwn("abc")).toEqual(r2); + }); + + it("should keep `undefined` values in the result", function () { + var source = { a: null, b: void 0 }; + var result = [["a", "b"], [null, void 0]]; + + expect(lamb.tear(source)).toStrictEqual(result); + expect(lamb.tearOwn(source)).toStrictEqual(result); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.tear).toThrow(); + expect(lamb.tearOwn).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.tear(null); }).toThrow(); + expect(function () { lamb.tear(void 0); }).toThrow(); + expect(function () { lamb.tearOwn(null); }).toThrow(); + expect(function () { lamb.tearOwn(void 0); }).toThrow(); + }); + + it("should consider other values as empty objects", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.tear(value)).toEqual([[], []]); + expect(lamb.tearOwn(value)).toEqual([[], []]); + }); + }); + + describe("tear", function () { + it("should use all the enumerable properties of the source object, inherited or not", function () { + expect(lamb.tear(foo)).toEqual([["d", "a"], [4, 1]]); + }); + }); + + describe("tearOwn", function () { + it("should use only the own enumerable properties of the source object", function () { + expect(lamb.tearOwn(foo)).toEqual([["d"], [4]]); + }); + }); +}); diff --git a/src/object/__tests__/updateIn.spec.js b/src/object/__tests__/updateIn.spec.js new file mode 100644 index 0000000..b936ec3 --- /dev/null +++ b/src/object/__tests__/updateIn.spec.js @@ -0,0 +1,149 @@ +import * as lamb from "../.."; +import { + nonFunctions, + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("updateIn / updateKey", function () { + var arr = [1, 2, 3]; + var sparseArr = [, 5, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays + var sparseArrCopy = sparseArr.slice(); + var baseFoo = Object.create({ a: arr }, { b: { value: 2, enumerable: true }, z: { value: 5 } }); + var foo = Object.create(baseFoo, { c: { value: 3, enumerable: true } }); + + var fooEquivalent = { a: [1, 2, 3], b: 2, c: 3, z: 5 }; + var fooEnumerables = { a: [1, 2, 3], b: 2, c: 3 }; + + // The "toEqual" matcher would have checked only own enumerable properties + afterEach(function () { + for (var key in fooEquivalent) { + expect(fooEquivalent[key]).toEqual(foo[key]); + } + + expect(foo.a).toBe(arr); + expect(sparseArr).toStrictArrayEqual(sparseArrCopy); + }); + + it("should build a copy of the source object with all its enumerable properties and the desired key updated according to the received function", function () { + var makeDoubles = jest.fn(lamb.mapWith(function (n) { return n * 2; })); + var newObjA = lamb.updateIn(foo, "a", makeDoubles); + var newObjB = lamb.updateKey("a", makeDoubles)(foo); + var result = { a: [2, 4, 6], b: 2, c: 3 }; + + expect(newObjA).toEqual(result); + expect(newObjB).toEqual(result); + expect(makeDoubles).toHaveBeenCalledTimes(2); + expect(makeDoubles.mock.calls[0].length).toBe(1); + expect(makeDoubles.mock.calls[0][0]).toBe(arr); + expect(makeDoubles.mock.calls[1].length).toBe(1); + expect(makeDoubles.mock.calls[1][0]).toBe(arr); + + var o = { a: 1, b: void 0 }; + + expect(lamb.updateIn(o, "b", lamb.always(99))).toEqual({ a: 1, b: 99 }); + expect(lamb.updateKey("b", lamb.always(99))(o)).toEqual({ a: 1, b: 99 }); + }); + + it("should transform array-like objects in objects with numbered string as properties", function () { + var fn = lamb.always(99); + + expect(lamb.updateIn([1, 2], "1", fn)).toEqual({ 0: 1, 1: 99 }); + expect(lamb.updateKey("1", fn)([1, 2])).toEqual({ 0: 1, 1: 99 }); + expect(lamb.updateIn("foo", "1", fn)).toEqual({ 0: "f", 1: 99, 2: "o" }); + expect(lamb.updateKey("1", fn)("foo")).toEqual({ 0: "f", 1: 99, 2: "o" }); + }); + + it("should not add a new property if the given key doesn't exist on the source, and return a copy of the source instead", function () { + var newObjA = lamb.updateIn(foo, "xyz", lamb.always(99)); + var newObjB = lamb.updateKey("xyz", lamb.always(99))(foo); + var newObjC = lamb.updateKey("xyz", lamb.always(99))([1]); + + expect(newObjA).toEqual(fooEnumerables); + expect(newObjA).not.toBe(foo); + expect(newObjB).toEqual(fooEnumerables); + expect(newObjB).not.toBe(foo); + expect(newObjC).toEqual({ 0: 1 }); + }); + + it("should not allow to update a non-enumerable property, and return a copy of the source instead, as with non-existent keys", function () { + var newObjA = lamb.updateIn(foo, "z", lamb.always(99)); + var newObjB = lamb.updateKey("z", lamb.always(99))(foo); + + expect(newObjA).toEqual(fooEnumerables); + expect(newObjA).not.toBe(foo); + expect(newObjB).toEqual(fooEnumerables); + expect(newObjB).not.toBe(foo); + }); + + it("should check the validity of the destination key before trying to apply the received function to it", function () { + var o = { a: 1 }; + var toUpperCase = lamb.invoker("toUpperCase"); + + expect(lamb.updateIn(o, "z", toUpperCase)).toEqual({ a: 1 }); + expect(lamb.updateKey("z", toUpperCase)(o)).toEqual({ a: 1 }); + }); + + it("should accept integers as keys", function () { + var inc = function (n) { return ++n; }; + + expect(lamb.updateIn([1, 2], 1, inc)).toEqual({ 0: 1, 1: 3 }); + expect(lamb.updateKey(1, inc)([1, 2])).toEqual({ 0: 1, 1: 3 }); + }); + + it("should use only defined keys in sparse arrays", function () { + var fn99 = lamb.always(99); + var r1 = { 1: 99 }; + var r2 = { 1: 5 }; + + expect(lamb.updateIn(sparseArr, 1, fn99)).toStrictEqual(r1); + expect(lamb.updateIn(sparseArr, 2, fn99)).toStrictEqual(r2); + expect(lamb.updateKey(1, fn99)(sparseArr)).toStrictEqual(r1); + expect(lamb.updateKey(2, fn99)(sparseArr)).toStrictEqual(r2); + }); + + it("should convert other values for the `key` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + var expected = lamb.merge(testObj, {}); + + expected[String(key)] = 99; + + expect(lamb.updateIn(testObj, key, lamb.always(99))).toEqual(expected); + expect(lamb.updateKey(key, lamb.always(99))(testObj)).toEqual(expected); + }); + }); + + it("should throw an exception if the `updater` isn't a function or if is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.updateIn({ a: 2 }, "a", value); }).toThrow(); + expect(function () { lamb.updateKey("a", value)({ a: 2 }); }).toThrow(); + }); + + expect(function () { lamb.updateIn({ a: 2 }, "a"); }).toThrow(); + expect(function () { lamb.updateKey("a")({ a: 2 }); }).toThrow(); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.updateIn).toThrow(); + expect(lamb.updateKey()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.updateIn(null, "a", lamb.always(99)); }).toThrow(); + expect(function () { lamb.updateIn(void 0, "a", lamb.always(99)); }).toThrow(); + expect(function () { lamb.updateKey("a", lamb.always(99))(null); }).toThrow(); + expect(function () { lamb.updateKey("a", lamb.always(99))(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.updateIn(value, "a", lamb.always(99))).toEqual({}); + expect(lamb.updateKey("a", lamb.always(99))(value)).toEqual({}); + }); + }); +}); diff --git a/src/object/__tests__/updatePathIn.spec.js b/src/object/__tests__/updatePathIn.spec.js new file mode 100644 index 0000000..840584f --- /dev/null +++ b/src/object/__tests__/updatePathIn.spec.js @@ -0,0 +1,422 @@ +import * as lamb from "../.."; +import { + nonFunctions, + nonStrings, + nonStringsAsStrings, + wannabeEmptyObjects +} from "../../__tests__/commons"; +import "../../__tests__/custom_matchers"; + +describe("updatePath / updatePathIn", function () { + var obj = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo" + }, + "c.d": { "e.f": 6 } + }; + + var sparseArr = Object.freeze([, 55, ,]); // eslint-disable-line comma-spacing, no-sparse-arrays + + obj.b.d = sparseArr; + + Object.defineProperty(obj.b, "w", { + value: { + x: 22, + y: { z: 33 } + } + }); + + var objCopy = JSON.parse(JSON.stringify(obj)); + + objCopy.b.d = sparseArr; + + var double = lamb.multiplyBy(2); + var inc = lamb.add(1); + var toUpperCase = lamb.invoker("toUpperCase"); + + afterEach(function () { + expect(obj).toEqual(objCopy); + expect(obj.b.d).toStrictArrayEqual(objCopy.b.d); + }); + + it("should allow to update a nested property in a copy of the given object using the provided function", function () { + var makeDoubles = jest.fn(lamb.mapWith(double)); + var newObjA = lamb.updatePathIn(obj, "b.b", makeDoubles, "."); + var newObjB = lamb.updatePath("b.b", makeDoubles, ".")(obj); + var r1 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [8, 10], + c: "foo", + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + + expect(newObjA).toEqual(r1); + expect(newObjB).toEqual(r1); + expect(makeDoubles).toHaveBeenCalledTimes(2); + expect(makeDoubles.mock.calls[0].length).toBe(1); + expect(makeDoubles.mock.calls[0][0]).toBe(obj.b.b); + expect(makeDoubles.mock.calls[1].length).toBe(1); + expect(makeDoubles.mock.calls[1][0]).toBe(obj.b.b); + + expect(newObjA.b.a).toBe(obj.b.a); + expect(newObjA["c.d"]).toBe(obj["c.d"]); + + expect(newObjA.b.d).toStrictArrayEqual(r1.b.d); + expect(newObjB.b.d).toStrictArrayEqual(r1.b.d); + + var r2 = { + a: 3, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.updatePathIn(obj, "a", inc, ".")).toEqual(r2); + expect(lamb.updatePath("a", inc, ".")(obj)).toEqual(r2); + }); + + it("should use the dot as the default separator", function () { + var r = lamb.updatePath("b.a.g", double)(obj); + + expect(r).toEqual({ + a: 2, + b: { + a: { g: 20, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }); + expect(r.b.b).toBe(obj.b.b); + expect(r["c.d"]).toBe(obj["c.d"]); + expect(lamb.updatePathIn(obj, "b.a.g", double)).toEqual(r); + }); + + it("should ignore extra arguments passed to the built function in its partially applied form", function () { + var r = lamb.updatePath("b.a.h", double)(obj, {}); + + expect(r).toEqual({ + a: 2, + b: { + a: { g: 10, h: 22 }, + b: [4, 5], + c: "foo", + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }); + }); + + it("should allow custom separators", function () { + var r = lamb.updatePath("c.d->e.f", double, "->")(obj); + + expect(r).toEqual({ + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 12 } + }); + + expect(r.b).toBe(obj.b); + expect(lamb.updatePathIn(obj, "c.d->e.f", double, "->")).toEqual(r); + }); + + it("should be possible to use a path with a single key", function () { + var arr = [1, 2, 3]; + var o = { a: 1, b: 2 }; + + expect(lamb.updatePathIn(arr, "1", inc)).toEqual([1, 3, 3]); + expect(lamb.updatePath("-1", inc)(arr)).toEqual([1, 2, 4]); + expect(lamb.updatePathIn(o, "b", inc)).toEqual({ a: 1, b: 3 }); + expect(lamb.updatePath("a", inc)(o)).toEqual({ a: 2, b: 2 }); + }); + + it("should replace indexes when an array is found and the key is a string containing an integer", function () { + var r = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 10], + c: "foo", + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.updatePath("b.b.1", double)(obj)).toEqual(r); + expect(lamb.updatePathIn(obj, "b.b.1", double)).toEqual(r); + expect(lamb.updatePath("1", double)([1, 2, 3])).toEqual([1, 4, 3]); + expect(lamb.updatePathIn([1, 2, 3], "1", double)).toEqual([1, 4, 3]); + }); + + it("should allow using negative array indexes in path parts", function () { + var arr = [1, 2, 3]; + var r1 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [8, 5], + c: "foo", + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + var r2 = [1, 4, 3]; + + expect(lamb.updatePath("b.b.-2", double)(obj)).toEqual(r1); + expect(lamb.updatePath("b.b.-3", double)(obj)).toEqual(obj); + expect(lamb.updatePath("-2", double)(arr)).toEqual(r2); + expect(lamb.updatePath("-4", double)(arr)).toEqual(arr); + expect(lamb.updatePathIn(obj, "b.b.-2", double)).toEqual(r1); + expect(lamb.updatePathIn(obj, "b.b.-3", double)).toEqual(obj); + expect(lamb.updatePathIn(arr, "-2", double)).toEqual(r2); + expect(lamb.updatePathIn(arr, "-4", double)).toEqual(arr); + }); + + it("should allow to change values nested in an array", function () { + var o = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ] + }; + var r = { + data: [ + { id: 1, value: 10 }, + { id: 2, value: 21 }, + { id: 3, value: 30 } + ] + }; + + expect(lamb.updatePath("data.1.value", inc)(o)).toEqual(r); + expect(lamb.updatePathIn(o, "data.1.value", inc)).toEqual(r); + expect(lamb.updatePath("data.-2.value", inc)(o)).toEqual(r); + expect(lamb.updatePathIn(o, "data.-2.value", inc)).toEqual(r); + }); + + it("should build dense arrays when the path target is a sparse array index", function () { + var expected1 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [void 0, 99, void 0] + }, + "c.d": { "e.f": 6 } + }; + var expected2 = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: "foo", + d: [void 0, 55, 99] + }, + "c.d": { "e.f": 6 } + }; + var fn99 = lamb.always(99); + + ["b.d.1", "b.d.-2", "b.d.-1", "b.d.2"].forEach(function (path, idx) { + var r1 = lamb.updatePathIn(obj, path, fn99); + var r2 = lamb.updatePath(path, fn99)(obj); + var expected = idx < 2 ? expected1 : expected2; + + expect(r1).toEqual(expected); + expect(r1.b.d).toStrictArrayEqual(expected.b.d); + + expect(r2).toEqual(expected); + expect(r2.b.d).toStrictArrayEqual(expected.b.d); + }); + }); + + it("should build an object with numbered keys when an array-like object is found", function () { + var r = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: [4, 5], + c: { 0: "m", 1: "o", 2: "o" }, + d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + + expect(lamb.updatePath("b.c.0", lamb.always("m"))(obj)).toEqual(r); + expect(lamb.updatePathIn(obj, "b.c.0", lamb.always("m"))).toEqual(r); + }); + + it("should build an object with numbered keys when an array is found and the key is not a string containing an integer", function () { + var r = { + a: 2, + b: { + a: { g: 10, h: 11 }, + b: { 0: 4, 1: 5, z: 99 }, + c: "foo", d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays + }, + "c.d": { "e.f": 6 } + }; + + obj.b.b.z = 1; + + expect(lamb.updatePath("b.b.z", lamb.always(99))(obj)).toEqual(r); + expect(lamb.updatePathIn(obj, "b.b.z", lamb.always(99))).toEqual(r); + delete obj.b.b.z; + }); + + it("should not add a new property if the given path doesn't exist on the source, and return a copy of the source instead", function () { + var arr = [1]; + var newObjA = lamb.updatePathIn(obj, "b.a.z", lamb.always(99)); + var newObjB = lamb.updatePathIn(obj, "b.b.1.z", lamb.always(99)); + var newObjC = lamb.updatePath("xyz", lamb.always(99))(obj); + var newObjD = lamb.updatePathIn(obj, "b.b.-10", lamb.always(99)); + var newObjE = lamb.updatePath("xyz", lamb.always(99))(arr); + var newObjF = lamb.updatePath("x.y.z", lamb.always(99))(arr); + var newObjG = lamb.updatePath("1", lamb.always(99))(arr); + var newObjH = lamb.updatePath("-10", lamb.always(99))(arr); + + expect(newObjA).toEqual(obj); + expect(newObjA).not.toBe(obj); + expect(newObjB).toEqual(obj); + expect(newObjB).not.toBe(obj); + expect(newObjC).toEqual(obj); + expect(newObjC).not.toBe(obj); + expect(newObjD).toEqual(obj); + expect(newObjD).not.toBe(obj); + expect(newObjE).toEqual(arr); + expect(newObjE).not.toBe(arr); + expect(newObjF).toEqual(arr); + expect(newObjF).not.toBe(arr); + expect(newObjG).toEqual(arr); + expect(newObjG).not.toBe(arr); + expect(newObjH).toEqual(arr); + expect(newObjH).not.toBe(arr); + + var o = { a: null }; + + var newObjI = lamb.updatePath("a.b.c", lamb.always(99))(o); + var newObjJ = lamb.updatePathIn(o, "a.b.c", lamb.always(99)); + + expect(newObjI).toEqual(o); + expect(newObjI).not.toBe(o); + expect(newObjJ).toEqual(o); + expect(newObjJ).not.toBe(o); + }); + + it("should not see a non-existing path when the target is undefined", function () { + var fooObj = { a: { b: { c: 2, d: void 0 } } }; + var r = { a: { b: { c: 2, d: 99 } } }; + var fn99 = lamb.always(99); + + expect(lamb.updatePathIn(fooObj, "a.b.d", fn99)).toStrictEqual(r); + expect(lamb.updatePath("a.b.d", fn99)(fooObj)).toStrictEqual(r); + }); + + it("should return a copy of the source object when a non enumerable property is part of the path or its target", function () { + var fooObj = Object.create({}, { + a: { enumerable: true, value: 1 }, + b: { value: { c: 2, d: { e: 3 } } } + }); + + expect(lamb.updatePathIn(fooObj, "b", lamb.setKey("c", 99))).toEqual({ a: 1 }); + expect(lamb.updatePath("b", lamb.setKey("c", 99))(fooObj)).toEqual({ a: 1 }); + expect(lamb.updatePathIn(fooObj, "b.c", lamb.always(99))).toEqual({ a: 1 }); + expect(lamb.updatePath("b.d.e", lamb.always(99))(fooObj)).toEqual({ a: 1 }); + }); + + it("should accept integers as paths containing a single key", function () { + expect(lamb.updatePath(1, lamb.always(99))([1, 2, 3])).toEqual([1, 99, 3]); + expect(lamb.updatePathIn([1, 2, 3], -1, lamb.always(99))).toEqual([1, 2, 99]); + expect(lamb.updatePath(2, lamb.always(99))([1, 2])).toEqual([1, 2]); + }); + + it("should give priority to object keys over array indexes when a negative index is encountered", function () { + var o = { a: ["abc", "def", "ghi"] }; + + o.a["-1"] = "foo"; + + var r = { a: { 0: "abc", 1: "def", 2: "ghi", "-1": 99 } }; + + expect(lamb.updatePath("a.-1", lamb.always(99))(o)).toEqual(r); + expect(lamb.updatePathIn(o, "a.-1", lamb.always(99))).toEqual(r); + }); + + it("should consider a negative integer to be an index if the property exists but it's not enumerable", function () { + var o = { a: ["abc", "def", "ghi"] }; + + Object.defineProperty(o.a, "-1", { value: 99 }); + + var r = { a: ["abc", "def", "GHI"] }; + + expect(lamb.updatePath("a.-1", toUpperCase)(o)).toEqual(r); + expect(lamb.updatePathIn(o, "a.-1", toUpperCase)).toEqual(r); + }); + + it("should convert other values for the `path` parameter to string", function () { + var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var testObj = lamb.make(nonStringsAsStrings, values); + + nonStrings.forEach(function (key) { + var expected = lamb.merge(testObj, {}); + + expected[String(key)] = 99; + + expect(lamb.updatePathIn(testObj, key, lamb.always(99), "_")).toEqual(expected); + expect(lamb.updatePath(key, lamb.always(99), "_")(testObj)).toEqual(expected); + }); + + var fooObj = { a: 2, 1: { 5: 3 }, undefined: 4 }; + var r = { a: 2, 1: { 5: 99 }, undefined: 4 }; + + expect(lamb.updatePathIn(fooObj, 1.5, lamb.always(99))).toEqual(r); + expect(lamb.updatePath(1.5, lamb.always(99))(fooObj)).toEqual(r); + }); + + it("should throw an exception if the `updater` isn't a function or if is missing", function () { + nonFunctions.forEach(function (value) { + expect(function () { lamb.updatePathIn({ a: 2 }, "a", value); }).toThrow(); + expect(function () { lamb.updatePath("a", value)({ a: 2 }); }).toThrow(); + }); + + expect(function () { lamb.updatePathIn({ a: 2 }, "a"); }).toThrow(); + expect(function () { lamb.updatePath("a")({ a: 2 }); }).toThrow(); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.updatePathIn).toThrow(); + expect(lamb.updatePath()).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { + expect(function () { lamb.updatePathIn(null, "a", lamb.always(99)); }).toThrow(); + expect(function () { lamb.updatePathIn(void 0, "a", lamb.always(99)); }).toThrow(); + expect(function () { lamb.updatePath("a", lamb.always(99))(null); }).toThrow(); + expect(function () { lamb.updatePath("a", lamb.always(99))(void 0); }).toThrow(); + }); + + it("should convert to object every other value", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.updatePathIn(value, "a", lamb.always(99))).toEqual({}); + expect(lamb.updatePath("a", lamb.always(99))(value)).toEqual({}); + expect(lamb.updatePathIn(value, "a.b.c", lamb.always(99))).toEqual({}); + expect(lamb.updatePath("a.b.c", lamb.always(99))(value)).toEqual({}); + }); + }); +}); diff --git a/src/object/__tests__/validation.spec.js b/src/object/__tests__/validation.spec.js new file mode 100644 index 0000000..9364d60 --- /dev/null +++ b/src/object/__tests__/validation.spec.js @@ -0,0 +1,111 @@ +import * as lamb from "../.."; + +describe("Object validation", function () { + var persons = [ + { name: "Jane", surname: "Doe", age: 12, city: "New York", email: "jane@doe" }, + { name: "John", surname: "Doe", age: 40, city: "London", email: "john@doe" }, + { name: "Mario", surname: "Rossi", age: 18, city: "Rome", email: "mario@rossi.it" }, + { name: "Paolo", surname: "Bianchi", age: 15, city: "Amsterdam", email: "paolo@bianchi.nl" } + ]; + + persons[0].login = { "user.name": "", password: "jane", passwordConfirm: "janE" }; + + var isAdult = function (age) { return age >= 18; }; + var isRequired = function (v) { return v.length > 0; }; + var isValidMail = function (mail) { + // eslint-disable-next-line max-len + return /^[A-Za-z0-9](([_.-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([.-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$/.test(mail); + }; + var isValidPassword = function (pwd) { return pwd.length > 5; }; + + var mailCheck = lamb.checker(isValidMail, "Must have a valid mail", ["email"]); + var ageCheck = lamb.checker(isAdult, "Must be at least 18 years old", ["age"]); + var pwdCheck = lamb.checker( + isValidPassword, + "Passwords must have at least six characters", + ["login.password"] + ); + var userNameCheck = lamb.checker( + isRequired, + "The username is a required field", + ["login/user.name"], + "/" + ); + var pwdConfirmCheck = lamb.checker( + lamb.areSame, + "Passwords don't match", + ["login.password", "login.passwordConfirm"] + ); + + describe("checker", function () { + it("should build a function to validate the given properties of an object", function () { + expect(mailCheck(persons[0])).toEqual(["Must have a valid mail", ["email"]]); + expect(mailCheck(persons[2])).toEqual([]); + }); + + it("should accept string paths as property names", function () { + expect(pwdCheck(persons[0])).toEqual( + ["Passwords must have at least six characters", ["login.password"]] + ); + expect(userNameCheck(persons[0])).toEqual( + ["The username is a required field", ["login/user.name"]] + ); + }); + + it("should be possible to make a checker involving more than one property", function () { + expect(pwdConfirmCheck(persons[0])).toEqual( + ["Passwords don't match", ["login.password", "login.passwordConfirm"]] + ); + }); + + it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { + var isEven = function (n) { return n % 2 === 0; }; + var hasEvens = function (array) { return ~lamb.findIndex(array, isEven); }; + var isVowel = function (char) { return ~"aeiouAEIOU".indexOf(char); }; + var o = { a: [1, 3, 5, 6, 7], b: [1, 3, 5, 7], c: "a", d: "b" }; + + var checker1 = lamb.checker(hasEvens, "error", ["a"]); + var checker2 = lamb.checker(hasEvens, "error", ["b"]); + var checker3 = lamb.checker(isVowel, "error", ["c"]); + var checker4 = lamb.checker(isVowel, "error", ["d"]); + + expect(checker1(o)).toEqual([]); + expect(checker2(o)).toEqual(["error", ["b"]]); + expect(checker3(o)).toEqual([]); + expect(checker4(o)).toEqual(["error", ["d"]]); + }); + }); + + describe("validate", function () { + it("should validate an object with the given set of checkers", function () { + expect(lamb.validate(persons[0], [mailCheck, ageCheck])).toEqual([ + ["Must have a valid mail", ["email"]], + ["Must be at least 18 years old", ["age"]] + ]); + expect(lamb.validate(persons[1], [mailCheck, ageCheck])).toEqual([ + ["Must have a valid mail", ["email"]] + ]); + expect(lamb.validate(persons[2], [mailCheck, ageCheck])).toEqual([]); + }); + }); + + describe("validateWith", function () { + it("should build a validator to be reused with different objects", function () { + var personValidator = lamb.validateWith([mailCheck, ageCheck]); + + expect(persons.map(personValidator)).toEqual([ + [ + ["Must have a valid mail", ["email"]], + ["Must be at least 18 years old", ["age"]] + ], + [ + ["Must have a valid mail", ["email"]] + ], + [], + [ + ["Must be at least 18 years old", ["age"]] + ] + ]); + }); + }); +}); diff --git a/src/object/__tests__/values.spec.js b/src/object/__tests__/values.spec.js new file mode 100644 index 0000000..38eab72 --- /dev/null +++ b/src/object/__tests__/values.spec.js @@ -0,0 +1,64 @@ +import * as lamb from "../.."; +import { wannabeEmptyObjects } from "../../__tests__/commons"; + +describe("ownValues / values", function () { + var baseFoo = Object.create({ a: 1 }, { b: { value: 2 } }); + var foo = Object.create(baseFoo, { + c: { value: 3 }, + d: { value: 4, enumerable: true } + }); + + it("should return an array of values of the given object properties", function () { + var source = { a: 1, b: 2, c: 3 }; + var result = [1, 2, 3]; + + expect(lamb.ownValues(source)).toEqual(result); + expect(lamb.values(source)).toEqual(result); + }); + + it("should accept array-like objects, returning an array copy of their values", function () { + expect(lamb.ownValues([1, 2, 3])).toEqual([1, 2, 3]); + expect(lamb.ownValues("abc")).toEqual(["a", "b", "c"]); + expect(lamb.values([1, 2, 3])).toEqual([1, 2, 3]); + expect(lamb.values("abc")).toEqual(["a", "b", "c"]); + }); + + it("should keep `undefined` values in the result", function () { + var source = { a: null, b: void 0 }; + var result = [null, void 0]; + + expect(lamb.ownValues(source)).toStrictEqual(result); + expect(lamb.values(source)).toStrictEqual(result); + }); + + it("should throw an exception if called without arguments", function () { + expect(lamb.ownValues).toThrow(); + expect(lamb.values).toThrow(); + }); + + it("should throw an exception if supplied with `null` or `undefined`", function () { + expect(function () { lamb.ownValues(null); }).toThrow(); + expect(function () { lamb.ownValues(void 0); }).toThrow(); + expect(function () { lamb.values(null); }).toThrow(); + expect(function () { lamb.values(void 0); }).toThrow(); + }); + + it("should consider other values as empty objects", function () { + wannabeEmptyObjects.forEach(function (value) { + expect(lamb.ownValues(value)).toEqual([]); + expect(lamb.values(value)).toEqual([]); + }); + }); + + describe("ownValues", function () { + it("should pick only from the own enumerable properties of the given object", function () { + expect(lamb.ownValues(foo)).toEqual([4]); + }); + }); + + describe("values", function () { + it("should pick all the enumerable properties of the given object", function () { + expect(lamb.values(foo)).toEqual([4, 1]); + }); + }); +}); diff --git a/src/object/checker.js b/src/object/checker.js new file mode 100644 index 0000000..689bd44 --- /dev/null +++ b/src/object/checker.js @@ -0,0 +1,55 @@ +import __ from "../core/__"; +import map from "../core/map"; +import partial from "../core/partial"; +import getPathIn from "./getPathIn"; + +/** + * Builds a checker function meant to be used with + * {@link module:lamb.validate|validate}.
+ * Note that the function accepts multiple keyPaths as a means to + * compare their values. In other words all the received keyPaths will be + * passed as arguments to the predicate to run the test.
+ * If you want to run the same single property check with multiple properties, you should build + * multiple checkers and combine them with {@link module:lamb.validate|validate}. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * login: { + * username: "jdoe", + * password: "abc123", + * passwordConfirm: "abc123" + * } + * }; + * var pwdMatch = _.checker( + * _.areSame, + * "Passwords don't match", + * ["login.password", "login.passwordConfirm"] + * ); + * + * pwdMatch(user) // => [] + * + * var newUser = _.setPathIn(user, "login.passwordConfirm", "avc123"); + * + * pwdMatch(newUser) // => ["Passwords don't match", ["login.password", "login.passwordConfirm"]] + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.validate|validate}, {@link module:lamb.validateWith|validateWith} + * @since 0.1.0 + * @param {Function} predicate - The predicate to test the object properties + * @param {String} message - The error message + * @param {String[]} keyPaths - The array of keys, or {@link module:lamb.getPathIn|paths}, to test. + * @param {String} [pathSeparator="."] + * @returns {Function} A checker function which returns an error in the form + * ["message", ["propertyA", "propertyB"]] or an empty array. + */ +function checker (predicate, message, keyPaths, pathSeparator) { + return function (obj) { + var getValues = partial(getPathIn, [obj, __, pathSeparator]); + + return predicate.apply(obj, map(keyPaths, getValues)) ? [] : [message, keyPaths]; + }; +} + +export default checker; diff --git a/src/object/enumerables.js b/src/object/enumerables.js new file mode 100644 index 0000000..dc73d9b --- /dev/null +++ b/src/object/enumerables.js @@ -0,0 +1,26 @@ +import _safeEnumerables from "../privates/_safeEnumerables"; +import _unsafeKeyListFrom from "../privates/_unsafeKeyListFrom"; + +/** + * Creates an array with all the enumerable properties of the given object. + * @example Showing the difference with {@link module:lamb.keys|keys}: + * var baseFoo = Object.create({a: 1}, {b: {value: 2}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3}, + * d: {value: 4, enumerable: true} + * }); + * + * _.keys(foo) // => ["d"] + * _.enumerables(foo) // => ["d", "a"] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.keys|keys} + * @since 0.12.0 + * @param {Object} obj + * @returns {String[]} + */ +var enumerables = _unsafeKeyListFrom(_safeEnumerables); + +export default enumerables; diff --git a/src/object/fromPairs.js b/src/object/fromPairs.js new file mode 100644 index 0000000..affe633 --- /dev/null +++ b/src/object/fromPairs.js @@ -0,0 +1,29 @@ +import forEach from "../core/forEach"; + +/** + * Builds an object from a list of key / value pairs like the one + * returned by {@link module:lamb.pairs|pairs} or {@link module:lamb.ownPairs|ownPairs}.
+ * In case of duplicate keys the last key / value pair is used. + * @example + * _.fromPairs([["a", 1], ["b", 2], ["c", 3]]) // => {"a": 1, "b": 2, "c": 3} + * _.fromPairs([["a", 1], ["b", 2], ["a", 3]]) // => {"a": 3, "b": 2} + * _.fromPairs([[1], [void 0, 2], [null, 3]]) // => {"1": undefined, "undefined": 2, "null": 3} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.ownPairs|ownPairs}, {@link module:lamb.pairs|pairs} + * @since 0.8.0 + * @param {Array>} pairsList + * @returns {Object} + */ +function fromPairs (pairsList) { + var result = {}; + + forEach(pairsList, function (pair) { + result[pair[0]] = pair[1]; + }); + + return result; +} + +export default fromPairs; diff --git a/src/object/getIn.js b/src/object/getIn.js new file mode 100644 index 0000000..9d37236 --- /dev/null +++ b/src/object/getIn.js @@ -0,0 +1,22 @@ +/** + * Returns the value of the object property with the given key. + * @example + * var user = {name: "John"}; + * + * _.getIn(user, "name") // => "John"; + * _.getIn(user, "surname") // => undefined + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.getKey|getKey} + * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} + * @since 0.18.0 + * @param {Object} obj + * @param {String} key + * @returns {*} + */ +function getIn (obj, key) { + return obj[key]; +} + +export default getIn; diff --git a/src/object/getKey.js b/src/object/getKey.js new file mode 100644 index 0000000..e8b8fbc --- /dev/null +++ b/src/object/getKey.js @@ -0,0 +1,27 @@ +import _curry2 from "../privates/_curry2"; +import getIn from "./getIn"; + +/** + * A curried version of {@link module:lamb.getIn|getIn}.
+ * Receives a property name and builds a function expecting the object from which we want to retrieve + * the property. + * @example + * var user1 = {name: "john"}; + * var user2 = {name: "jane"}; + * var getName = _.getKey("name"); + * + * getName(user1) // => "john" + * getName(user2) // => "jane" + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.getIn|getIn} + * @see {@link module:lamb.getPath|getPath}, {@link module:lamb.getPathIn|getPathIn} + * @since 0.1.0 + * @param {String} key + * @returns {Function} + */ +var getKey = _curry2(getIn, true); + +export default getKey; diff --git a/src/object/getPath.js b/src/object/getPath.js new file mode 100644 index 0000000..8bde61a --- /dev/null +++ b/src/object/getPath.js @@ -0,0 +1,35 @@ +import _makePartial3 from "../privates/_makePartial3"; +import getPathIn from "./getPathIn"; + +/** + * Builds a partial application of {@link module:lamb.getPathIn|getPathIn} with the given + * path and separator, expecting the object to act upon.
+ * @example + * var user = { + * name: "John", + * surname: "Doe", + * login: { + * "user.name": "jdoe", + * password: "abc123" + * } + * }; + * + * var getPwd = _.getPath("login.password"); + * var getUsername = _.getPath("login/user.name", "/"); + * + * getPwd(user) // => "abc123"; + * getUsername(user) // => "jdoe" + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.getPathIn|getPathIn} + * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} + * @since 0.19.0 + * @param {String} path + * @param {String} [separator="."] + * @returns {Function} + */ +var getPath = _makePartial3(getPathIn); + +export default getPath; diff --git a/src/object/getPathIn.js b/src/object/getPathIn.js new file mode 100644 index 0000000..81caa44 --- /dev/null +++ b/src/object/getPathIn.js @@ -0,0 +1,59 @@ +import _getPathInfo from "../privates/_getPathInfo"; +import _toPathParts from "../privates/_toPathParts"; + +/** + * Gets a nested property value from an object using the given path.
+ * The path is a string with property names separated by dots by default, but + * it can be customised with the optional third parameter.
+ * You can use integers in the path, even negative ones, to refer to array-like + * object indexes, but the priority will be given to existing object keys: + * the last example explains this particular case. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * login: { + * "user.name": "jdoe", + * password: "abc123" + * }, + * scores: [ + * {id: 1, value: 10}, + * {id: 2, value: 20}, + * {id: 3, value: 30} + * ] + * }; + * + * _.getPathIn(user, "name") // => "John" + * _.getPathIn(user, "login.password") // => "abc123"; + * _.getPathIn(user, "login/user.name", "/") // => "jdoe" + * _.getPathIn(user, "name.foo") // => undefined + * _.getPathIn(user, "name.foo.bar") // => undefined + * + * @example Accessing array-like objects indexes: + * _.getPathIn(user, "login.password.1") // => "b" + * _.getPathIn(user, "scores.0") // => {id: 1, value: 10} + * _.getPathIn(user, "scores.-1.value") // => 30 + * + * @example Priority will be given to existing object keys over indexes: + * _.getPathIn(user, "scores.-1") // => {id: 3, value: 30} + * + * // let's do something funny + * user.scores["-1"] = "foo bar"; + * + * _.getPathIn(user, "scores.-1") // => "foo bar"; + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.getPath|getPath} + * @see {@link module:lamb.getIn|getIn}, {@link module:lamb.getKey|getKey} + * @since 0.19.0 + * @param {Object|ArrayLike} obj + * @param {String} path + * @param {String} [separator="."] + * @returns {*} + */ +function getPathIn (obj, path, separator) { + return _getPathInfo(obj, _toPathParts(path, separator), true).target; +} + +export default getPathIn; diff --git a/src/object/has.js b/src/object/has.js new file mode 100644 index 0000000..f0ce74e --- /dev/null +++ b/src/object/has.js @@ -0,0 +1,35 @@ +import isUndefined from "../core/isUndefined"; + +/** + * Verifies the existence of a property in an object. + * @example + * var user1 = {name: "john"}; + * + * _.has(user1, "name") // => true + * _.has(user1, "surname") // => false + * _.has(user1, "toString") // => true + * + * var user2 = Object.create(null); + * + * // not inherited through the prototype chain + * _.has(user2, "toString") // => false + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.hasKey|hasKey} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @since 0.1.0 + * @param {Object} obj + * @param {String} key + * @returns {Boolean} + */ +function has (obj, key) { + if (typeof obj !== "object" && !isUndefined(obj)) { + obj = Object(obj); + } + + return key in obj; +} + +export default has; diff --git a/src/object/hasKey.js b/src/object/hasKey.js new file mode 100644 index 0000000..5f07e00 --- /dev/null +++ b/src/object/hasKey.js @@ -0,0 +1,27 @@ +import _curry2 from "../privates/_curry2"; +import has from "./has"; + +/** + * Curried version of {@link module:lamb.has|has}.
+ * Returns a function expecting the object to check against the given key. + * @example + * var user1 = {name: "john"}; + * var user2 = {}; + * var hasName = _.hasKey("name"); + * + * hasName(user1) // => true + * hasName(user2) // => false + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.has|has} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @since 0.1.0 + * @param {String} key + * @returns {Function} + */ +var hasKey = _curry2(has, true); + +export default hasKey; diff --git a/src/object/hasKeyValue.js b/src/object/hasKeyValue.js new file mode 100644 index 0000000..19fe382 --- /dev/null +++ b/src/object/hasKeyValue.js @@ -0,0 +1,28 @@ +import areSVZ from "../core/areSVZ"; +import isUndefined from "../core/isUndefined"; +import has from "./has"; + +/** + * Builds a predicate expecting an object to check against the given key / value pair.
+ * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. + * @example + * var hasTheCorrectAnswer = _.hasKeyValue("answer", 42); + * + * hasTheCorrectAnswer({answer: 2}) // false + * hasTheCorrectAnswer({answer: 42}) // true + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.hasPathValue|hasPathValue} + * @since 0.1.0 + * @param {String} key + * @param {*} value + * @returns {Function} + */ +function hasKeyValue (key, value) { + return function (obj) { + return isUndefined(value) ? has(obj, key) && obj[key] === value : areSVZ(value, obj[key]); + }; +} + +export default hasKeyValue; diff --git a/src/object/hasOwn.js b/src/object/hasOwn.js new file mode 100644 index 0000000..36e2b7e --- /dev/null +++ b/src/object/hasOwn.js @@ -0,0 +1,30 @@ +import generic from "../core/generic"; + +/** + * Verifies if an object has the specified property and that the property isn't inherited through + * the prototype chain.
+ * @example Comparison with has: + * var user = {name: "john"}; + * + * _.has(user, "name") // => true + * _.has(user, "surname") // => false + * _.has(user, "toString") // => true + * + * _.hasOwn(user, "name") // => true + * _.hasOwn(user, "surname") // => false + * _.hasOwn(user, "toString") // => false + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @since 0.1.0 + * @param {Object} obj + * @param {String} key + * @returns {Boolean} + */ +var hasOwn = generic(Object.prototype.hasOwnProperty); + +export default hasOwn; diff --git a/src/object/hasOwnKey.js b/src/object/hasOwnKey.js new file mode 100644 index 0000000..d8f98b2 --- /dev/null +++ b/src/object/hasOwnKey.js @@ -0,0 +1,27 @@ +import _curry2 from "../privates/_curry2"; +import hasOwn from "./hasOwn"; + +/** + * Curried version of {@link module:lamb.hasOwn|hasOwn}.
+ * Returns a function expecting the object to check against the given key. + * @example + * var user = {name: "john"}; + * var hasOwnName = _.hasOwnKey("name"); + * var hasOwnToString = _.hasOwnToString("toString"); + * + * hasOwnName(user) // => true + * hasOwnToString(user) // => false + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.hasOwn|hasOwn} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} + * @since 0.1.0 + * @param {String} key + * @returns {Function} + */ +var hasOwnKey = _curry2(hasOwn, true); + +export default hasOwnKey; diff --git a/src/object/hasPathValue.js b/src/object/hasPathValue.js new file mode 100644 index 0000000..f872243 --- /dev/null +++ b/src/object/hasPathValue.js @@ -0,0 +1,49 @@ +import areSVZ from "../core/areSVZ"; +import _getPathInfo from "../privates/_getPathInfo"; +import _toPathParts from "../privates/_toPathParts"; + +/** + * Builds a predicate to check if the given path exists in an object and holds the desired value.
+ * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
+ * Note that the function will check even non-enumerable properties. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * personal: { + * age: 25, + * gender: "M" + * }, + * scores: [ + * {id: 1, value: 10, passed: false}, + * {id: 2, value: 20, passed: false}, + * {id: 3, value: 30, passed: true} + * ] + * }; + * + * var isMale = _.hasPathValue("personal.gender", "M"); + * var hasPassedFirstTest = _.hasPathValue("scores.0.passed", true); + * var hasPassedLastTest = _.hasPathValue("scores.-1.passed", true); + * + * isMale(user) // => true + * hasPassedFirstTest(user) // => false + * hasPassedLastTest(user) // => true + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.hasKeyValue|hasKeyValue} + * @since 0.41.0 + * @param {String} path + * @param {*} value + * @param {String} [separator="."] + * @returns {Function} + */ +function hasPathValue (path, value, separator) { + return function (obj) { + var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); + + return pathInfo.isValid && areSVZ(pathInfo.target, value); + }; +} + +export default hasPathValue; diff --git a/src/object/immutable.js b/src/object/immutable.js new file mode 100644 index 0000000..2189d33 --- /dev/null +++ b/src/object/immutable.js @@ -0,0 +1,37 @@ +import _immutable from "../privates/_immutable"; + +/** + * Makes an object immutable by recursively calling [Object.freeze]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze} + * on its members.
+ * Any attempt to extend or modify the object can throw a TypeError or fail silently, + * depending on the environment and the [strict mode]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode} directive. + * @example + * var user = _.immutable({ + * name: "John", + * surname: "Doe", + * login: { + * username: "jdoe", + * password: "abc123" + * }, + * luckyNumbers: [13, 17] + * }); + * + * // All of these statements will fail and possibly + * // throw a TypeError (see the function description) + * user.name = "Joe"; + * delete user.name; + * user.newProperty = []; + * user.login.password = "foo"; + * user.luckyNumbers.push(-13); + * + * @memberof module:lamb + * @category Object + * @since 0.8.0 + * @param {Object} obj + * @returns {Object} + */ +function immutable (obj) { + return _immutable(obj, []); +} + +export default immutable; diff --git a/src/object/keySatisfies.js b/src/object/keySatisfies.js new file mode 100644 index 0000000..119c998 --- /dev/null +++ b/src/object/keySatisfies.js @@ -0,0 +1,28 @@ +/** + * Builds a predicate to check if the given key satisfies the desired condition + * on an object. + * @example + * var users = [ + * {name: "John", age: 25}, + * {name: "Jane", age: 15}, + * ]; + * var isAdult = _.keySatisfies(_.isGTE(18), "age"); + * + * isAdult(users[0]) // => true + * isAdult(users[1]) // => false + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.pathSatisfies|pathSatisfies} + * @since 0.45.0 + * @param {Function} predicate + * @param {String} key + * @returns {Function} + */ +function keySatisfies (predicate, key) { + return function (obj) { + return predicate.call(this, obj[key]); + }; +} + +export default keySatisfies; diff --git a/src/object/keys.js b/src/object/keys.js new file mode 100644 index 0000000..0415f81 --- /dev/null +++ b/src/object/keys.js @@ -0,0 +1,31 @@ +import _safeKeys from "../privates/_safeKeys"; +import _unsafeKeyListFrom from "../privates/_unsafeKeyListFrom"; + +/** + * Retrieves the list of the own enumerable properties of an object.
+ * Although [Object.keys]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys} + * is already present in ECMAScript 5, its behaviour changed in the subsequent specifications + * of the standard.
+ * This function shims the ECMAScript 6 version, by forcing a conversion to + * object for any value but null and undefined. + * @example Showing the difference with {@link module:lamb.enumerables|enumerables}: + * var baseFoo = Object.create({a: 1}, {b: {value: 2}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3}, + * d: {value: 4, enumerable: true} + * }); + * + * _.enumerables(foo) // => ["d", "a"] + * _.keys(foo) // => ["d"] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.enumerables|enumerables} + * @since 0.25.1 + * @param {Object} obj + * @returns {String[]} + */ +var keys = _unsafeKeyListFrom(_safeKeys); + +export default keys; diff --git a/src/object/make.js b/src/object/make.js new file mode 100644 index 0000000..8728e69 --- /dev/null +++ b/src/object/make.js @@ -0,0 +1,32 @@ +/** + * Builds an object from the two given lists, using the first one as keys and the last + * one as values.
+ * If the list of keys is longer than the values one, the keys will be created with + * undefined values.
+ * If more values than keys are supplied, the extra values will be ignored. + * @example + * _.make(["a", "b", "c"], [1, 2, 3]) // => {a: 1, b: 2, c: 3} + * _.make(["a", "b", "c"], [1, 2]) // => {a: 1, b: 2, c: undefined} + * _.make(["a", "b"], [1, 2, 3]) // => {a: 1, b: 2} + * _.make([null, void 0, 2], [1, 2, 3]) // => {"null": 1, "undefined": 2, "2": 3} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.tear|tear}, {@link module:lamb.tearOwn|tearOwn} for the reverse operation + * @since 0.8.0 + * @param {String[]} names + * @param {ArrayLike} values + * @returns {Object} + */ +function make (names, values) { + var result = {}; + var valuesLen = values.length; + + for (var i = 0, len = names.length; i < len; i++) { + result[names[i]] = i < valuesLen ? values[i] : void 0; + } + + return result; +} + +export default make; diff --git a/src/object/mapValues.js b/src/object/mapValues.js new file mode 100644 index 0000000..714b5ee --- /dev/null +++ b/src/object/mapValues.js @@ -0,0 +1,37 @@ +import isNil from "../core/isNil"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; + +/** + * Creates a new object by applying the given function + * to all enumerable properties of the source one. + * @example + * var weights = { + * john: "72.5 Kg", + * jane: "52.3 Kg" + * }; + * + * _.mapValues(weights, parseFloat) // => {john: 72.5, jane: 52.3} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.mapValuesWith|mapValuesWith} + * @since 0.54.0 + * @param {Object} source + * @param {ObjectIteratorCallback} fn + * @returns {Object} + */ +function mapValues (source, fn) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "object"); + } + + var result = {}; + + for (var key in source) { + result[key] = fn(source[key], key, source); + } + + return result; +} + +export default mapValues; diff --git a/src/object/mapValuesWith.js b/src/object/mapValuesWith.js new file mode 100644 index 0000000..bb8ffd6 --- /dev/null +++ b/src/object/mapValuesWith.js @@ -0,0 +1,28 @@ +import _curry2 from "../privates/_curry2"; +import mapValues from "./mapValues"; + +/** + * A curried version of {@link module:lamb.mapValues|mapValues}.
+ * Expects a mapping function to build a new function waiting for the + * object to act upon. + * @example + * var incValues = _.mapValuesWith(_.add(1)); + * var results = { + * first: 10, + * second: 5, + * third: 3 + * }; + * + * incValues(results) // => {first: 11, second: 6, third: 4} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.mapValues|mapValues} + * @since 0.54.0 + * @function + * @param {ObjectIteratorCallback} fn + * @returns {Function} + */ +var mapValuesWith = _curry2(mapValues, true); + +export default mapValuesWith; diff --git a/src/object/merge.js b/src/object/merge.js new file mode 100644 index 0000000..55a03f9 --- /dev/null +++ b/src/object/merge.js @@ -0,0 +1,30 @@ +import _merge from "../privates/_merge"; +import partial from "../core/partial"; +import enumerables from "./enumerables"; + +/** + * Merges the enumerable properties of the provided sources into a new object.
+ * In case of key homonymy the last source has precedence over the first. + * @example + * _.merge({a: 1, b: 3}, {b: 5, c: 4}) // => {a: 1, b: 5, c: 4} + * + * @example Array-like objects will be transformed to objects with numbers as keys: + * _.merge([1, 2], {a: 2}) // => {"0": 1, "1": 2, a: 2} + * _.merge("foo", {a: 2}) // => {"0": "f", "1": "o", "2": "o", a: 2} + * + * @example Every other non-nil value will be treated as an empty object: + * _.merge({a: 2}, 99) // => {a: 2} + * _.merge({a: 2}, NaN) // => {a: 2} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.mergeOwn|mergeOwn} to merge own properties only + * @since 0.10.0 + * @function + * @param {Object} a + * @param {Object} b + * @returns {Object} + */ +var merge = partial(_merge, [enumerables]); + +export default merge; diff --git a/src/object/mergeOwn.js b/src/object/mergeOwn.js new file mode 100644 index 0000000..494639a --- /dev/null +++ b/src/object/mergeOwn.js @@ -0,0 +1,38 @@ +import _merge from "../privates/_merge"; +import partial from "../core/partial"; +import keys from "./keys"; + +/** + * Same as {@link module:lamb.merge|merge}, but only the own properties of the + * sources are taken into account. + * @example Showing the difference with merge: + * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3, enumerable: true} + * }); + * + * var bar = {d: 4}; + * + * _.merge(foo, bar) // => {a: 1, b: 2, c: 3, d: 4} + * _.mergeOwn(foo, bar) // => {c: 3, d: 4} + * + * @example Array-like objects will be transformed to objects with numbers as keys: + * _.mergeOwn([1, 2], {a: 2}) // => {"0": 1, "1": 2, a: 2} + * _.mergeOwn("foo", {a: 2}) // => {"0": "f", "1": "o", "2": "o", a: 2} + * + * @example Every other non-nil value will be treated as an empty object: + * _.mergeOwn({a: 2}, 99) // => {a: 2} + * _.mergeOwn({a: 2}, NaN) // => {a: 2} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.merge|merge} to merge all enumerable properties + * @since 0.12.0 + * @function + * @param {Object} a + * @param {Object} b + * @returns {Object} + */ +var mergeOwn = partial(_merge, [keys]); + +export default mergeOwn; diff --git a/src/object/ownPairs.js b/src/object/ownPairs.js new file mode 100644 index 0000000..23be87c --- /dev/null +++ b/src/object/ownPairs.js @@ -0,0 +1,28 @@ +import _pairsFrom from "../privates/_pairsFrom"; +import keys from "./keys"; + +/** + * Same as {@link module:lamb.pairs|pairs}, but only the own enumerable properties of the object are + * taken into account.
+ * See also {@link module:lamb.fromPairs|fromPairs} for the reverse operation. + * @example Showing the difference with pairs: + * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3, enumerable: true} + * }); + * + * _.pairs(foo) // => [["c", 3], ["b", 2], ["a", 1]] + * _.ownPairs(foo) // => [["c", 3]] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.pairs|pairs} + * @see {@link module:lamb.fromPairs|fromPairs} + * @since 0.12.0 + * @param {Object} obj + * @returns {Array>} + */ +var ownPairs = _pairsFrom(keys); + +export default ownPairs; diff --git a/src/object/ownValues.js b/src/object/ownValues.js new file mode 100644 index 0000000..c357c12 --- /dev/null +++ b/src/object/ownValues.js @@ -0,0 +1,26 @@ +import _valuesFrom from "../privates/_valuesFrom"; +import keys from "./keys"; + +/** + * Same as {@link module:lamb.values|values}, but only the own enumerable properties of the object are + * taken into account.
+ * @example Showing the difference with values: + * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3, enumerable: true} + * }); + * + * _.values(foo) // => [3, 2, 1] + * _.ownValues(foo) // => [3] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.values|values} + * @since 0.12.0 + * @param {Object} obj + * @returns {Array} + */ +var ownValues = _valuesFrom(keys); + +export default ownValues; diff --git a/src/object/pairs.js b/src/object/pairs.js new file mode 100644 index 0000000..bb77074 --- /dev/null +++ b/src/object/pairs.js @@ -0,0 +1,22 @@ +import _pairsFrom from "../privates/_pairsFrom"; +import enumerables from "./enumerables"; + +/** + * Converts an object into an array of key / value pairs of its enumerable properties.
+ * See also {@link module:lamb.ownPairs|ownPairs} for picking only the own enumerable + * properties and {@link module:lamb.fromPairs|fromPairs} for the reverse operation. + * @example + * _.pairs({a: 1, b: 2, c: 3}) // => [["a", 1], ["b", 2], ["c", 3]] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.ownPairs|ownPairs} + * @see {@link module:lamb.fromPairs|fromPairs} + * @since 0.8.0 + * @param {Object} obj + * @returns {Array>} + */ +var pairs = _pairsFrom(enumerables); + +export default pairs; diff --git a/src/object/pathExists.js b/src/object/pathExists.js new file mode 100644 index 0000000..e390c4c --- /dev/null +++ b/src/object/pathExists.js @@ -0,0 +1,39 @@ +import _makePartial3 from "../privates/_makePartial3"; +import pathExistsIn from "./pathExistsIn"; + +/** + * Builds a partial application of {@link module:lamb.pathExistsIn|pathExistsIn} using the given + * path and the optional separator. The resulting function expects the object to check.
+ * Note that the function will check even non-enumerable properties. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * address: { + * city: "New York" + * }, + * scores: [10, 20, 15] + * }; + * + * var hasCity = _.pathExists("address.city"); + * var hasCountry = _.pathExists("address.country"); + * var hasAtLeastThreeScores = _.pathExists("scores.2"); + * + * hasCity(user) // => true + * hasCountry(user) // => false + * hasAtLeastThreeScores(user) // => true + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.pathExistsIn|pathExistsIn} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @since 0.43.0 + * @param {String} path + * @param {String} [separator="."] + * @returns {Function} + */ +var pathExists = _makePartial3(pathExistsIn); + +export default pathExists; diff --git a/src/object/pathExistsIn.js b/src/object/pathExistsIn.js new file mode 100644 index 0000000..3c06635 --- /dev/null +++ b/src/object/pathExistsIn.js @@ -0,0 +1,36 @@ +import _getPathInfo from "../privates/_getPathInfo"; +import _toPathParts from "../privates/_toPathParts"; + +/** + * Checks if the provided path exists in the given object.
+ * Note that the function will check even non-enumerable properties. + * @example + * var user = { + * name: "John", + * surname: "Doe", + * address: { + * city: "New York" + * }, + * scores: [10, 20, 15] + * }; + * + * _.pathExistsIn(user, "address.city") // => true + * _.pathExistsIn(user, "address.country") // => false + * _.pathExistsIn(user, "scores.1") // => true + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.pathExists|pathExists} + * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} + * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} + * @since 0.43.0 + * @param {Object} obj + * @param {String} path + * @param {String} [separator="."] + * @returns {Boolean} + */ +function pathExistsIn (obj, path, separator) { + return _getPathInfo(obj, _toPathParts(path, separator), true).isValid; +} + +export default pathExistsIn; diff --git a/src/object/pathSatisfies.js b/src/object/pathSatisfies.js new file mode 100644 index 0000000..3798c57 --- /dev/null +++ b/src/object/pathSatisfies.js @@ -0,0 +1,41 @@ +import _getPathInfo from "../privates/_getPathInfo"; +import _toPathParts from "../privates/_toPathParts"; + +/** + * Builds a predicate that verifies if a condition is satisfied for the given + * path in an object.
+ * Like the other "path functions" you can use integers in the path, even + * negative ones, to refer to array-like object indexes, but the priority will + * be given to existing object keys. + * @example + * var user = { + * name: "John", + * performance: { + * scores: [1, 5, 10] + * } + * }; + * + * var gotAnHighScore = _.pathSatisfies(_.contains(10), "performance.scores"); + * var hadAGoodStart = _.pathSatisfies(_.isGT(6), "performance.scores.0"); + * + * gotAnHighScore(user) // => true + * hadAGoodStart(user) // => false + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.keySatisfies|keySatisfies} + * @since 0.45.0 + * @param {Function} predicate + * @param {String} path + * @param {String} [separator="."] + * @returns {Function} + */ +function pathSatisfies (predicate, path, separator) { + return function (obj) { + var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); + + return predicate.call(this, pathInfo.target); + }; +} + +export default pathSatisfies; diff --git a/src/object/pick.js b/src/object/pick.js new file mode 100644 index 0000000..75a3a43 --- /dev/null +++ b/src/object/pick.js @@ -0,0 +1,35 @@ +import has from "./has"; + +/** + * Returns an object containing only the specified properties of the given object.
+ * Non existent properties will be ignored. + * @example + * var user = {name: "john", surname: "doe", age: 30}; + * + * _.pick(user, ["name", "age"]) // => {"name": "john", "age": 30}; + * _.pick(user, ["name", "email"]) // => {"name": "john"} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.pickIf|pickIf}, {@link module:lamb.pickKeys|pickKeys} + * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf} + * @since 0.1.0 + * @param {Object} source + * @param {String[]} whitelist + * @returns {Object} + */ +function pick (source, whitelist) { + var result = {}; + + for (var i = 0, len = whitelist.length, key; i < len; i++) { + key = whitelist[i]; + + if (has(source, key)) { + result[key] = source[key]; + } + } + + return result; +} + +export default pick; diff --git a/src/object/pickIf.js b/src/object/pickIf.js new file mode 100644 index 0000000..b9bafee --- /dev/null +++ b/src/object/pickIf.js @@ -0,0 +1,41 @@ +import isNil from "../core/isNil"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; + +/** + * Builds a function expecting an object whose enumerable properties will be checked + * against the given predicate.
+ * The properties satisfying the predicate will be included in the resulting object. + * @example + * var user = {name: "john", surname: "doe", age: 30}; + * var pickIfIsString = _.pickIf(_.isType("String")); + * + * pickIfIsString(user) // => {name: "john", surname: "doe"} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys} + * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys}, + * {@link module:lamb.skipIf|skipIf} + * @since 0.1.0 + * @param {ObjectIteratorCallback} predicate + * @returns {Function} + */ +function pickIf (predicate) { + return function (source) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "object"); + } + + var result = {}; + + for (var key in source) { + if (predicate(source[key], key, source)) { + result[key] = source[key]; + } + } + + return result; + }; +} + +export default pickIf; diff --git a/src/object/pickKeys.js b/src/object/pickKeys.js new file mode 100644 index 0000000..581ffcb --- /dev/null +++ b/src/object/pickKeys.js @@ -0,0 +1,43 @@ +import _curry2 from "../privates/_curry2"; +import pick from "./pick"; + +/** + * A curried version of {@link module:lamb.pick|pick}, expecting a whitelist of keys to build + * a function waiting for the object to act upon. + * @example + * var user = {id: 1, name: "Jane", surname: "Doe", active: false}; + * var getUserInfo = _.pickKeys(["id", "active"]); + * + * getUserInfo(user) // => {id: 1, active: false} + * + * @example A useful composition with mapWith: + * var users = [ + * {id: 1, name: "Jane", surname: "Doe", active: false}, + * {id: 2, name: "John", surname: "Doe", active: true}, + * {id: 3, name: "Mario", surname: "Rossi", active: true}, + * {id: 4, name: "Paolo", surname: "Bianchi", active: false} + * ]; + * var select = _.compose(_.mapWith, _.pickKeys); + * var selectUserInfo = select(["id", "active"]); + * + * selectUserInfo(users) // => + * // [ + * // {id: 1, active: false}, + * // {id: 2, active: true}, + * // {id: 3, active: true}, + * // {id: 4, active: false} + * // ] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickIf|pickIf} + * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys}, + * {@link module:lamb.skipIf|skipIf} + * @since 0.35.0 + * @param {String[]} whitelist + * @returns {Function} + */ +var pickKeys = _curry2(pick, true); + +export default pickKeys; diff --git a/src/object/rename.js b/src/object/rename.js new file mode 100644 index 0000000..8924dc8 --- /dev/null +++ b/src/object/rename.js @@ -0,0 +1,47 @@ +import enumerables from "./enumerables"; + +/** + * Creates a copy of the given object with its enumerable keys renamed as + * indicated in the provided lookup table. + * @example + * var person = {"firstName": "John", "lastName": "Doe"}; + * var keysMap = {"firstName": "name", "lastName": "surname"}; + * + * _.rename(person, keysMap) // => {"name": "John", "surname": "Doe"} + * + * @example It's safe using it to swap keys: + * var keysMap = {"firstName": "lastName", "lastName": "firstName"}; + * + * _.rename(person, keysMap) // => {"lastName": "John", "firstName": "Doe"} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.renameKeys|renameKeys}, {@link module:lamb.renameWith|renameWith} + * @since 0.26.0 + * @param {Object} source + * @param {Object} keysMap + * @returns {Object} + */ +function rename (source, keysMap) { + keysMap = Object(keysMap); + var result = {}; + var oldKeys = enumerables(source); + + for (var prop in keysMap) { + if (~oldKeys.indexOf(prop)) { + result[keysMap[prop]] = source[prop]; + } + } + + for (var i = 0, len = oldKeys.length, key; i < len; i++) { + key = oldKeys[i]; + + if (!(key in keysMap || key in result)) { + result[key] = source[key]; + } + } + + return result; +} + +export default rename; diff --git a/src/object/renameKeys.js b/src/object/renameKeys.js new file mode 100644 index 0000000..dc075c0 --- /dev/null +++ b/src/object/renameKeys.js @@ -0,0 +1,35 @@ +import _curry2 from "../privates/_curry2"; +import rename from "./rename"; + +/** + * A curried version of {@link module:lamb.rename|rename} expecting a + * keysMap to build a function waiting for the object to act upon. + * @example + * var persons = [ + * {"firstName": "John", "lastName": "Doe"}, + * {"first_name": "Mario", "last_name": "Rossi"}, + * ]; + * var normalizeKeys = _.renameKeys({ + * "firstName": "name", + * "first_name": "name", + * "lastName": "surname", + * "last_name": "surname" + * }); + * + * _.map(persons, normalizeKeys) // => + * // [ + * // {"name": "John", "surname": "Doe"}, + * // {"name": "Mario", "surname": "Rossi"} + * // ] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameWith|renameWith} + * @since 0.26.0 + * @param {Object} keysMap + * @returns {Function} + */ +var renameKeys = _curry2(rename, true); + +export default renameKeys; diff --git a/src/object/renameWith.js b/src/object/renameWith.js new file mode 100644 index 0000000..dc70aa9 --- /dev/null +++ b/src/object/renameWith.js @@ -0,0 +1,31 @@ +import rename from "./rename"; + +/** + * Uses the provided function as a keysMap generator and returns + * a function expecting the object whose keys we want to {@link module:lamb.rename|rename}. + * @example + * var person = {"NAME": "John", "SURNAME": "Doe"}; + * var arrayToLower = _.mapWith(_.invoker("toLowerCase")); + * var makeLowerKeysMap = function (source) { + * var sourceKeys = _.keys(source); + * + * return _.make(sourceKeys, arrayToLower(sourceKeys)); + * }; + * var lowerKeysFor = _.renameWith(makeLowerKeysMap); + * + * lowerKeysFor(person) // => {"name": "John", "surname": "doe"}; + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.rename|rename}, {@link module:lamb.renameKeys|renameKeys} + * @since 0.26.0 + * @param {Function} fn + * @returns {Function} + */ +function renameWith (fn) { + return function (source) { + return rename(source, fn(source)); + }; +} + +export default renameWith; diff --git a/src/object/setIn.js b/src/object/setIn.js new file mode 100644 index 0000000..4565926 --- /dev/null +++ b/src/object/setIn.js @@ -0,0 +1,42 @@ +import isNil from "../core/isNil"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; +import _setIn from "../privates/_setIn"; + +/** + * Sets the specified key to the given value in a copy of the provided object.
+ * All the remaining enumerable keys of the source object will be simply copied in the + * result object without breaking references.
+ * If the specified key is not part of the source object, it will be added to the + * result.
+ * The main purpose of the function is to work on simple plain objects used as + * data structures, such as JSON objects, and makes no effort to play nice with + * objects created from an OOP perspective (it's not worth it).
+ * For example the prototype of the result will be Object's regardless + * of the source's one. + * @example + * var user = {name: "John", surname: "Doe", age: 30}; + * + * _.setIn(user, "name", "Jane") // => {name: "Jane", surname: "Doe", age: 30} + * _.setIn(user, "gender", "male") // => {name: "John", surname: "Doe", age: 30, gender: "male"} + * + * // `user` still is {name: "John", surname: "Doe", age: 30} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.setKey|setKey} + * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} + * @since 0.18.0 + * @param {Object} source + * @param {String} key + * @param {*} value + * @returns {Object} + */ +function setIn (source, key, value) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "object"); + } + + return _setIn(source, key, value); +} + +export default setIn; diff --git a/src/object/setKey.js b/src/object/setKey.js new file mode 100644 index 0000000..5ab84e5 --- /dev/null +++ b/src/object/setKey.js @@ -0,0 +1,30 @@ +import _makePartial3 from "../privates/_makePartial3"; +import setIn from "./setIn"; + +/** + * Builds a partial application of {@link module:lamb.setIn|setIn} with the provided + * key and value.
+ * The resulting function expects the object to act upon.
+ * Please refer to {@link module:lamb.setIn|setIn}'s description for explanations about + * how the copy of the source object is made. + * @example + * var user = {name: "John", surname: "Doe", age: 30}; + * var setAgeTo40 = _.setKey("age", 40); + * + * setAgeTo40(user) // => {name: "john", surname: "doe", age: 40} + * + * // `user` still is {name: "John", surname: "Doe", age: 30} + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.setIn|setIn} + * @see {@link module:lamb.setPath|setPath}, {@link module:lamb.setPathIn|setPathIn} + * @since 0.18.0 + * @param {String} key + * @param {*} value + * @returns {Function} + */ +var setKey = _makePartial3(setIn); + +export default setKey; diff --git a/src/object/setPath.js b/src/object/setPath.js new file mode 100644 index 0000000..17745ad --- /dev/null +++ b/src/object/setPath.js @@ -0,0 +1,29 @@ +import setPathIn from "./setPathIn"; + +/** + * Builds a partial application of {@link module:lamb.setPathIn|setPathIn} expecting the + * object to act upon.
+ * See {@link module:lamb.setPathIn|setPathIn} for more details and examples. + * @example + * var user = {id: 1, status: {active: false}}; + * var activate = _.setPath("status.active", true); + * + * activate(user) // => {id: 1, status: {active: true}} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.setPathIn|setPathIn} + * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} + * @since 0.20.0 + * @param {String} path + * @param {*} value + * @param {String} [separator="."] + * @returns {Function} + */ +function setPath (path, value, separator) { + return function (source) { + return setPathIn(source, path, value, separator); + }; +} + +export default setPath; diff --git a/src/object/setPathIn.js b/src/object/setPathIn.js new file mode 100644 index 0000000..7aa308e --- /dev/null +++ b/src/object/setPathIn.js @@ -0,0 +1,67 @@ +import isNil from "../core/isNil"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; +import _setPathIn from "../privates/_setPathIn"; +import _toPathParts from "../privates/_toPathParts"; + +/** + * Allows to change a nested value in a copy of the provided object.
+ * The function will delegate the "set action" to {@link module:lamb.setIn|setIn} or + * {@link module:lamb.setAt|setAt} depending on the value encountered in the path, + * so please refer to the documentation of those functions for specifics about the + * implementation.
+ * Note anyway that the distinction will be between Arrays, delegated + * to {@link module:lamb.setAt|setAt}, and everything else (including array-like objects), + * which will be delegated to {@link module:lamb.setIn|setIn}.
+ * As a result of that, array-like objects will be converted to objects having numbers as keys + * and paths targeting non-object values will be converted to empty objects.
+ * You can anyway target array elements using integers in the path, even negative ones, but + * the priority will be given to existing, and enumerable, object keys.
+ * Non-enumerable properties encountered in the path will be considered as non-existent properties.
+ * Like {@link module:lamb.getPathIn|getPathIn} or {@link module:lamb.getPath|getPath} you can + * use custom path separators. + * @example + * var user = {id: 1, status: {active : false, scores: [2, 4, 6]}}; + * + * _.setPathIn(user, "status.active", true) // => {id: 1, status: {active : true, scores: [2, 4, 6]}} + * + * @example Targeting arrays: + * _.setPathIn(user, "status.scores.0", 8) // => {id: 1, status: {active : false, scores: [8, 4, 6]}} + * + * // you can use negative indexes as well + * _.setPathIn(user, "status.scores.-1", 8) // => {id: 1, status: {active : false, scores: [2, 4, 8]}} + * + * @example Arrays can also be part of the path and not necessarily its target: + * var user = {id: 1, scores: [ + * {value: 2, year: "2000"}, + * {value: 4, year: "2001"}, + * {value: 6, year: "2002"} + * ]}; + * + * var newUser = _.setPathIn(user, "scores.0.value", 8); + * // "newUser" holds: + * // {id: 1, scores: [ + * // {value: 8, year: "2000"}, + * // {value: 4, year: "2001"}, + * // {value: 6, year: "2002"} + * // ]} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.setPath|setPath} + * @see {@link module:lamb.setIn|setIn}, {@link module:lamb.setKey|setKey} + * @since 0.20.0 + * @param {Object|Array} source + * @param {String} path + * @param {*} value + * @param {String} [separator="."] + * @returns {Object|Array} + */ +function setPathIn (source, path, value, separator) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "object"); + } + + return _setPathIn(source, _toPathParts(path, separator), value); +} + +export default setPathIn; diff --git a/src/object/skip.js b/src/object/skip.js new file mode 100644 index 0000000..1a7e418 --- /dev/null +++ b/src/object/skip.js @@ -0,0 +1,40 @@ +import isNil from "../core/isNil"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; +import make from "./make"; + +/** + * Returns a copy of the source object without the specified properties. + * @example + * var user = {name: "john", surname: "doe", age: 30}; + * + * _.skip(user, ["name", "age"]) // => {surname: "doe"}; + * _.skip(user, ["name", "email"]) // => {surname: "doe", age: 30}; + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.skipKeys|skipKeys}, {@link module:lamb.skipIf|skipIf} + * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, + * {@link module:lamb.pickIf|pickIf} + * @since 0.1.0 + * @param {Object} source + * @param {String[]} blacklist + * @returns {Object} + */ +function skip (source, blacklist) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "object"); + } + + var result = {}; + var props = make(blacklist, []); + + for (var key in source) { + if (!(key in props)) { + result[key] = source[key]; + } + } + + return result; +} + +export default skip; diff --git a/src/object/skipIf.js b/src/object/skipIf.js new file mode 100644 index 0000000..28794aa --- /dev/null +++ b/src/object/skipIf.js @@ -0,0 +1,27 @@ +import compose from "../core/compose"; +import not from "../logic/not"; +import pickIf from "./pickIf"; + +/** + * Builds a function expecting an object whose enumerable properties will be checked + * against the given predicate.
+ * The properties satisfying the predicate will be omitted in the resulting object. + * @example + * var user = {name: "john", surname: "doe", age: 30}; + * var skipIfIstring = _.skipIf(_.isType("String")); + * + * skipIfIstring(user) // => {age: 30} + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipKeys|skipKeys} + * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, + * {@link module:lamb.pickIf|pickIf} + * @since 0.1.0 + * @param {ObjectIteratorCallback} predicate + * @returns {Function} + */ +var skipIf = compose(pickIf, not); + +export default skipIf; diff --git a/src/object/skipKeys.js b/src/object/skipKeys.js new file mode 100644 index 0000000..dcd9e89 --- /dev/null +++ b/src/object/skipKeys.js @@ -0,0 +1,43 @@ +import _curry2 from "../privates/_curry2"; +import skip from "./skip"; + +/** + * A curried version of {@link module:lamb.skip|skip}, expecting a blacklist of keys to build + * a function waiting for the object to act upon. + * @example + * var user = {id: 1, name: "Jane", surname: "Doe", active: false}; + * var getUserInfo = _.skipKeys(["name", "surname"]); + * + * getUserInfo(user) // => {id: 1, active: false} + * + * @example A useful composition with mapWith: + * var users = [ + * {id: 1, name: "Jane", surname: "Doe", active: false}, + * {id: 2, name: "John", surname: "Doe", active: true}, + * {id: 3, name: "Mario", surname: "Rossi", active: true}, + * {id: 4, name: "Paolo", surname: "Bianchi", active: false} + * ]; + * var discard = _.compose(_.mapWith, _.skipKeys); + * var discardNames = discard(["name", "surname"]); + * + * discardNames(users) // => + * // [ + * // {id: 1, active: false}, + * // {id: 2, active: true}, + * // {id: 3, active: true}, + * // {id: 4, active: false} + * // ] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.skip|skip}, {@link module:lamb.skipIf|skipIf} + * @see {@link module:lamb.pick|pick}, {@link module:lamb.pickKeys|pickKeys}, + * {@link module:lamb.pickIf|pickIf} + * @since 0.35.0 + * @param {String[]} blacklist + * @returns {Function} + */ +var skipKeys = _curry2(skip, true); + +export default skipKeys; diff --git a/src/object/tear.js b/src/object/tear.js new file mode 100644 index 0000000..1dff9ef --- /dev/null +++ b/src/object/tear.js @@ -0,0 +1,23 @@ +import _tearFrom from "../privates/_tearFrom"; +import enumerables from "./enumerables"; + +/** + * Tears an object apart by transforming it in an array of two lists: one containing + * its enumerable keys, the other containing the corresponding values.
+ * Although this "tearing apart" may sound as a rather violent process, the source + * object will be unharmed. + * @example + * _.tear({a: 1, b: 2, c: 3}) // => [["a", "b", "c"], [1, 2, 3]] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.tearOwn|tearOwn} + * @see {@link module:lamb.make|make} for the reverse operation + * @since 0.8.0 + * @param {Object} obj + * @returns {Array} + */ +var tear = _tearFrom(enumerables); + +export default tear; diff --git a/src/object/tearOwn.js b/src/object/tearOwn.js new file mode 100644 index 0000000..1979957 --- /dev/null +++ b/src/object/tearOwn.js @@ -0,0 +1,27 @@ +import _tearFrom from "../privates/_tearFrom"; +import keys from "./keys"; + +/** + * Same as {@link module:lamb.tear|tear}, but only the own properties of the object are + * taken into account. + * @example Showing the difference with tear: + * var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); + * var foo = Object.create(baseFoo, { + * c: {value: 3, enumerable: true} + * }); + * + * _.tear(foo) // => [["c", "b", "a"], [3, 2, 1]] + * _.tearOwn(foo) // => [["c"], [3]] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.tear|tear} + * @see {@link module:lamb.make|make} for the reverse operation + * @since 0.12.0 + * @param {Object} obj + * @returns {Array} + */ +var tearOwn = _tearFrom(keys); + +export default tearOwn; diff --git a/src/object/updateIn.js b/src/object/updateIn.js new file mode 100644 index 0000000..5006096 --- /dev/null +++ b/src/object/updateIn.js @@ -0,0 +1,40 @@ +import _isEnumerable from "../privates/_isEnumerable"; +import _merge from "../privates/_merge"; +import _setIn from "../privates/_setIn"; +import enumerables from "./enumerables"; + +/** + * Creates a copy of the given object having the desired key value updated by applying + * the provided function to it.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the + * source is returned otherwise. + * @example + * var user = {name: "John", visits: 2}; + * var toUpperCase = _.invoker("toUpperCase"); + * + * _.updateIn(user, "name", toUpperCase) // => {name: "JOHN", visits: 2} + * _.updateIn(user, "surname", toUpperCase) // => {name: "John", visits: 2} + * + * @example Non-enumerable properties will be treated as non-existent: + * var user = Object.create({name: "John"}, {visits: {value: 2}}); + * + * _.updateIn(user, "visits", _.add(1)) // => {name: "John", visits: 2} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.updateKey|updateKey} + * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} + * @since 0.22.0 + * @param {Object} source + * @param {String} key + * @param {Function} updater + * @returns {Object} + */ +function updateIn (source, key, updater) { + return _isEnumerable(source, key) ? + _setIn(source, key, updater(source[key])) : + _merge(enumerables, source, {}); +} + +export default updateIn; diff --git a/src/object/updateKey.js b/src/object/updateKey.js new file mode 100644 index 0000000..2a54afe --- /dev/null +++ b/src/object/updateKey.js @@ -0,0 +1,28 @@ +import _makePartial3 from "../privates/_makePartial3"; +import updateIn from "./updateIn"; + +/** + * Builds a partial application of {@link module:lamb.updateIn|updateIn} with the provided + * key and updater, expecting the object to act upon.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setIn|setIn}; a copy of the + * source is returned otherwise. + * @example + * var user = {name: "John", visits: 2}; + * var incrementVisits = _.updateKey("visits", _.add(1)); + * + * incrementVisits(user) // => {name: "John", visits: 3} + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.updateIn|updateIn} + * @see {@link module:lamb.updatePath|updatePath}, {@link module:lamb.updatePathIn|updatePathIn} + * @since 0.22.0 + * @param {String} key + * @param {Function} updater + * @returns {Function} + */ +var updateKey = _makePartial3(updateIn); + +export default updateKey; diff --git a/src/object/updatePath.js b/src/object/updatePath.js new file mode 100644 index 0000000..ab9a924 --- /dev/null +++ b/src/object/updatePath.js @@ -0,0 +1,33 @@ +import updatePathIn from "./updatePathIn"; + +/** + * Builds a partial application of {@link module:lamb.updatePathIn|updatePathIn} + * expecting the object to act upon.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the + * source is returned otherwise.
+ * Like the other "path" functions, negative indexes can be used to access array elements, but + * the priority will be given to existing, and enumerable, object keys. + * @example + * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; + * var incrementScores = _.updatePath("status.scores", _.mapWith(_.add(1))) + * + * incrementScores(user) // => {id: 1, status: {scores: [3, 5, 7], visits: 0}} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.updatePathIn|updatePathIn} + * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} + * @since 0.24.0 + * @param {String} path + * @param {Function} updater + * @param {String} [separator="."] + * @returns {Function} + */ +function updatePath (path, updater, separator) { + return function (source) { + return updatePathIn(source, path, updater, separator); + }; +} + +export default updatePath; diff --git a/src/object/updatePathIn.js b/src/object/updatePathIn.js new file mode 100644 index 0000000..b7c2254 --- /dev/null +++ b/src/object/updatePathIn.js @@ -0,0 +1,65 @@ +import _getPathInfo from "../privates/_getPathInfo"; +import _merge from "../privates/_merge"; +import _toPathParts from "../privates/_toPathParts"; +import _setPathIn from "../privates/_setPathIn"; +import slice from "../core/slice"; +import enumerables from "./enumerables"; + +/** + * Allows to change a nested value in a copy of the given object by applying the provided + * function to it.
+ * This function is meant for updating existing enumerable properties, and for those it + * will delegate the "set action" to {@link module:lamb.setPathIn|setPathIn}; a copy of the + * source is returned otherwise.
+ * Like the other "path" functions, negative indexes can be used to access array elements, but + * the priority will be given to existing, and enumerable, object keys. + * @example + * var user = {id: 1, status: {scores: [2, 4, 6], visits: 0}}; + * var inc = _.add(1); + * + * _.updatePathIn(user, "status.visits", inc) // => {id: 1, status: {scores: [2, 4, 6]}, visits: 1} + * + * @example Targeting arrays: + * _.updatePathIn(user, "status.scores.0", inc) // => {id: 1, status: {scores: [3, 4, 6], visits: 0}} + * + * // you can use negative indexes as well + * _.updatePathIn(user, "status.scores.-1", inc) // => {id: 1, status: {scores: [2, 4, 7], visits: 0}} + * + * @example Arrays can also be part of the path and not necessarily its target: + * var user = {id: 1, scores: [ + * {value: 2, year: "2000"}, + * {value: 4, year: "2001"}, + * {value: 6, year: "2002"} + * ]}; + * + * var newUser = _.updatePathIn(user, "scores.0.value", inc); + * // "newUser" holds: + * // {id: 1, scores: [ + * // {value: 3, year: "2000"}, + * // {value: 4, year: "2001"}, + * // {value: 6, year: "2002"} + * // ]} + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.updatePath|updatePath} + * @see {@link module:lamb.updateIn|updateIn}, {@link module:lamb.updateKey|updateKey} + * @since 0.24.0 + * @param {Object|Array} source + * @param {String} path + * @param {Function} updater + * @param {String} [separator="."] + * @returns {Object|Array} + */ +function updatePathIn (source, path, updater, separator) { + var parts = _toPathParts(path, separator); + var pathInfo = _getPathInfo(source, parts, false); + + if (pathInfo.isValid) { + return _setPathIn(source, parts, updater(pathInfo.target)); + } else { + return Array.isArray(source) ? slice(source, 0, source.length) : _merge(enumerables, source, {}); + } +} + +export default updatePathIn; diff --git a/src/object/validate.js b/src/object/validate.js new file mode 100644 index 0000000..d63ef18 --- /dev/null +++ b/src/object/validate.js @@ -0,0 +1,43 @@ +import reduce from "../core/reduce"; + +/** + * Validates an object with the given list of {@link module:lamb.checker|checker} functions. + * @example + * var hasContent = function (s) { return s.trim().length > 0; }; + * var userCheckers = [ + * _.checker(hasContent, "Name is required", ["name"]), + * _.checker(hasContent, "Surname is required", ["surname"]), + * _.checker(_.isGTE(18), "Must be at least 18 years old", ["age"]) + * ]; + * + * var user1 = {name: "john", surname: "doe", age: 30}; + * var user2 = {name: "jane", surname: "", age: 15}; + * + * _.validate(user1, userCheckers) // => [] + * _.validate(user2, userCheckers) // => + * // [ + * // ["Surname is required", ["surname"]], + * // ["Must be at least 18 years old", ["age"]] + * // ] + * + * @memberof module:lamb + * @category Object + * @see {@link module:lamb.validateWith|validateWith} + * @see {@link module:lamb.checker|checker} + * @since 0.1.0 + * @param {Object} obj + * @param {Function[]} checkers + * @returns {Array>} An array of errors in the form returned by + * {@link module:lamb.checker|checker}, or an empty array. + */ +function validate (obj, checkers) { + return reduce(checkers, function (errors, _checker) { + var result = _checker(obj); + + result.length && errors.push(result); + + return errors; + }, []); +} + +export default validate; diff --git a/src/object/validateWith.js b/src/object/validateWith.js new file mode 100644 index 0000000..35ea447 --- /dev/null +++ b/src/object/validateWith.js @@ -0,0 +1,37 @@ +import _curry2 from "../privates/_curry2"; +import validate from "./validate"; + +/** + * A curried version of {@link module:lamb.validate|validate} accepting a list of + * {@link module:lamb.checker|checkers} and returning a function expecting the object to validate. + * @example + * var hasContent = function (s) { return s.trim().length > 0; }; + * var userCheckers = [ + * _.checker(hasContent, "Name is required", ["name"]), + * _.checker(hasContent, "Surname is required", ["surname"]), + * _.checker(_.isGTE(18), "Must be at least 18 years old", ["age"]) + * ]; + * var validateUser = _.validateWith(userCheckers); + * + * var user1 = {name: "john", surname: "doe", age: 30}; + * var user2 = {name: "jane", surname: "", age: 15}; + * + * validateUser(user1) // => [] + * validateUser(user2) // => + * // [ + * // ["Surname is required", ["surname"]], + * // ["Must be at least 18 years old", ["age"]] + * // ] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.validate|validate} + * @see {@link module:lamb.checker|checker} + * @since 0.1.0 + * @param {Function[]} checkers + * @returns {Function} + */ +var validateWith = _curry2(validate, true); + +export default validateWith; diff --git a/src/object/values.js b/src/object/values.js new file mode 100644 index 0000000..6b68238 --- /dev/null +++ b/src/object/values.js @@ -0,0 +1,22 @@ +import _valuesFrom from "../privates/_valuesFrom"; +import enumerables from "./enumerables"; + +/** + * Generates an array with the values of the enumerable properties of the given object.
+ * See also {@link module:lamb.ownValues|ownValues} to pick only from the own properties of the object. + * @example + * var user = {name: "john", surname: "doe", age: 30}; + * + * _.values(user) // => ["john", "doe", 30] + * + * @memberof module:lamb + * @category Object + * @function + * @see {@link module:lamb.ownValues|ownValues} + * @since 0.1.0 + * @param {Object} obj + * @returns {Array} + */ +var values = _valuesFrom(enumerables); + +export default values; diff --git a/src/object_checking.js b/src/object_checking.js deleted file mode 100644 index ece8aef..0000000 --- a/src/object_checking.js +++ /dev/null @@ -1,438 +0,0 @@ -/** - * Builds a checker function meant to be used with - * {@link module:lamb.validate|validate}.
- * Note that the function accepts multiple keyPaths as a means to - * compare their values. In other words all the received keyPaths will be - * passed as arguments to the predicate to run the test.
- * If you want to run the same single property check with multiple properties, you should build - * multiple checkers and combine them with {@link module:lamb.validate|validate}. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * login: { - * username: "jdoe", - * password: "abc123", - * passwordConfirm: "abc123" - * } - * }; - * var pwdMatch = _.checker( - * _.areSame, - * "Passwords don't match", - * ["login.password", "login.passwordConfirm"] - * ); - * - * pwdMatch(user) // => [] - * - * var newUser = _.setPathIn(user, "login.passwordConfirm", "avc123"); - * - * pwdMatch(newUser) // => ["Passwords don't match", ["login.password", "login.passwordConfirm"]] - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.validate|validate}, {@link module:lamb.validateWith|validateWith} - * @since 0.1.0 - * @param {Function} predicate - The predicate to test the object properties - * @param {String} message - The error message - * @param {String[]} keyPaths - The array of keys, or {@link module:lamb.getPathIn|paths}, to test. - * @param {String} [pathSeparator="."] - * @returns {Function} A checker function which returns an error in the form - * ["message", ["propertyA", "propertyB"]] or an empty array. - */ -function checker (predicate, message, keyPaths, pathSeparator) { - return function (obj) { - var getValues = partial(getPathIn, [obj, _, pathSeparator]); - - return predicate.apply(obj, map(keyPaths, getValues)) ? [] : [message, keyPaths]; - }; -} - -/** - * Verifies the existence of a property in an object. - * @example - * var user1 = {name: "john"}; - * - * _.has(user1, "name") // => true - * _.has(user1, "surname") // => false - * _.has(user1, "toString") // => true - * - * var user2 = Object.create(null); - * - * // not inherited through the prototype chain - * _.has(user2, "toString") // => false - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.hasKey|hasKey} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} - * @since 0.1.0 - * @param {Object} obj - * @param {String} key - * @returns {Boolean} - */ -function has (obj, key) { - if (typeof obj !== "object" && !isUndefined(obj)) { - obj = Object(obj); - } - - return key in obj; -} - -/** - * Curried version of {@link module:lamb.has|has}.
- * Returns a function expecting the object to check against the given key. - * @example - * var user1 = {name: "john"}; - * var user2 = {}; - * var hasName = _.hasKey("name"); - * - * hasName(user1) // => true - * hasName(user2) // => false - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.has|has} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} - * @since 0.1.0 - * @param {String} key - * @returns {Function} - */ -var hasKey = _curry2(has, true); - -/** - * Builds a predicate expecting an object to check against the given key / value pair.
- * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}. - * @example - * var hasTheCorrectAnswer = _.hasKeyValue("answer", 42); - * - * hasTheCorrectAnswer({answer: 2}) // false - * hasTheCorrectAnswer({answer: 42}) // true - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.hasPathValue|hasPathValue} - * @since 0.1.0 - * @param {String} key - * @param {*} value - * @returns {Function} - */ -function hasKeyValue (key, value) { - return function (obj) { - return isUndefined(value) ? has(obj, key) && obj[key] === value : areSVZ(value, obj[key]); - }; -} - -/** - * Verifies if an object has the specified property and that the property isn't inherited through - * the prototype chain.
- * @example Comparison with has: - * var user = {name: "john"}; - * - * _.has(user, "name") // => true - * _.has(user, "surname") // => false - * _.has(user, "toString") // => true - * - * _.hasOwn(user, "name") // => true - * _.hasOwn(user, "surname") // => false - * _.hasOwn(user, "toString") // => false - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} - * @since 0.1.0 - * @param {Object} obj - * @param {String} key - * @returns {Boolean} - */ -var hasOwn = generic(_objectProto.hasOwnProperty); - -/** - * Curried version of {@link module:lamb.hasOwn|hasOwn}.
- * Returns a function expecting the object to check against the given key. - * @example - * var user = {name: "john"}; - * var hasOwnName = _.hasOwnKey("name"); - * var hasOwnToString = _.hasOwnToString("toString"); - * - * hasOwnName(user) // => true - * hasOwnToString(user) // => false - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.hasOwn|hasOwn} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @see {@link module:lamb.pathExistsIn|pathExistsIn}, {@link module:lamb.pathExists|pathExists} - * @since 0.1.0 - * @param {String} key - * @returns {Function} - */ -var hasOwnKey = _curry2(hasOwn, true); - -/** - * Builds a predicate to check if the given path exists in an object and holds the desired value.
- * The value check is made with the ["SameValueZero" comparison]{@link module:lamb.areSVZ|areSVZ}.
- * Note that the function will check even non-enumerable properties. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * personal: { - * age: 25, - * gender: "M" - * }, - * scores: [ - * {id: 1, value: 10, passed: false}, - * {id: 2, value: 20, passed: false}, - * {id: 3, value: 30, passed: true} - * ] - * }; - * - * var isMale = _.hasPathValue("personal.gender", "M"); - * var hasPassedFirstTest = _.hasPathValue("scores.0.passed", true); - * var hasPassedLastTest = _.hasPathValue("scores.-1.passed", true); - * - * isMale(user) // => true - * hasPassedFirstTest(user) // => false - * hasPassedLastTest(user) // => true - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.hasKeyValue|hasKeyValue} - * @since 0.41.0 - * @param {String} path - * @param {*} value - * @param {String} [separator="."] - * @returns {Function} - */ -function hasPathValue (path, value, separator) { - return function (obj) { - var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); - - return pathInfo.isValid && areSVZ(pathInfo.target, value); - }; -} - -/** - * Builds a predicate to check if the given key satisfies the desired condition - * on an object. - * @example - * var users = [ - * {name: "John", age: 25}, - * {name: "Jane", age: 15}, - * ]; - * var isAdult = _.keySatisfies(_.isGTE(18), "age"); - * - * isAdult(users[0]) // => true - * isAdult(users[1]) // => false - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.pathSatisfies|pathSatisfies} - * @since 0.45.0 - * @param {Function} predicate - * @param {String} key - * @returns {Function} - */ -function keySatisfies (predicate, key) { - return function (obj) { - return predicate.call(this, obj[key]); - }; -} - -/** - * Builds a partial application of {@link module:lamb.pathExistsIn|pathExistsIn} using the given - * path and the optional separator. The resulting function expects the object to check.
- * Note that the function will check even non-enumerable properties. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * address: { - * city: "New York" - * }, - * scores: [10, 20, 15] - * }; - * - * var hasCity = _.pathExists("address.city"); - * var hasCountry = _.pathExists("address.country"); - * var hasAtLeastThreeScores = _.pathExists("scores.2"); - * - * hasCity(user) // => true - * hasCountry(user) // => false - * hasAtLeastThreeScores(user) // => true - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.pathExistsIn|pathExistsIn} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @since 0.43.0 - * @param {String} path - * @param {String} [separator="."] - * @returns {Function} - */ -var pathExists = _makePartial3(pathExistsIn); - -/** - * Checks if the provided path exists in the given object.
- * Note that the function will check even non-enumerable properties. - * @example - * var user = { - * name: "John", - * surname: "Doe", - * address: { - * city: "New York" - * }, - * scores: [10, 20, 15] - * }; - * - * _.pathExistsIn(user, "address.city") // => true - * _.pathExistsIn(user, "address.country") // => false - * _.pathExistsIn(user, "scores.1") // => true - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.pathExists|pathExists} - * @see {@link module:lamb.hasOwn|hasOwn}, {@link module:lamb.hasOwnKey|hasOwnKey} - * @see {@link module:lamb.has|has}, {@link module:lamb.hasKey|hasKey} - * @since 0.43.0 - * @param {Object} obj - * @param {String} path - * @param {String} [separator="."] - * @returns {Boolean} - */ -function pathExistsIn (obj, path, separator) { - return _getPathInfo(obj, _toPathParts(path, separator), true).isValid; -} - -/** - * Builds a predicate that verifies if a condition is satisfied for the given - * path in an object.
- * Like the other "path functions" you can use integers in the path, even - * negative ones, to refer to array-like object indexes, but the priority will - * be given to existing object keys. - * @example - * var user = { - * name: "John", - * performance: { - * scores: [1, 5, 10] - * } - * }; - * - * var gotAnHighScore = _.pathSatisfies(_.contains(10), "performance.scores"); - * var hadAGoodStart = _.pathSatisfies(_.isGT(6), "performance.scores.0"); - * - * gotAnHighScore(user) // => true - * hadAGoodStart(user) // => false - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.keySatisfies|keySatisfies} - * @since 0.45.0 - * @param {Function} predicate - * @param {String} path - * @param {String} [separator="."] - * @returns {Function} - */ -function pathSatisfies (predicate, path, separator) { - return function (obj) { - var pathInfo = _getPathInfo(obj, _toPathParts(path, separator), true); - - return predicate.call(this, pathInfo.target); - }; -} - -/** - * Validates an object with the given list of {@link module:lamb.checker|checker} functions. - * @example - * var hasContent = function (s) { return s.trim().length > 0; }; - * var userCheckers = [ - * _.checker(hasContent, "Name is required", ["name"]), - * _.checker(hasContent, "Surname is required", ["surname"]), - * _.checker(_.isGTE(18), "Must be at least 18 years old", ["age"]) - * ]; - * - * var user1 = {name: "john", surname: "doe", age: 30}; - * var user2 = {name: "jane", surname: "", age: 15}; - * - * _.validate(user1, userCheckers) // => [] - * _.validate(user2, userCheckers) // => - * // [ - * // ["Surname is required", ["surname"]], - * // ["Must be at least 18 years old", ["age"]] - * // ] - * - * @memberof module:lamb - * @category Object - * @see {@link module:lamb.validateWith|validateWith} - * @see {@link module:lamb.checker|checker} - * @since 0.1.0 - * @param {Object} obj - * @param {Function[]} checkers - * @returns {Array>} An array of errors in the form returned by - * {@link module:lamb.checker|checker}, or an empty array. - */ -function validate (obj, checkers) { - return reduce(checkers, function (errors, _checker) { - var result = _checker(obj); - - result.length && errors.push(result); - - return errors; - }, []); -} - -/** - * A curried version of {@link module:lamb.validate|validate} accepting a list of - * {@link module:lamb.checker|checkers} and returning a function expecting the object to validate. - * @example - * var hasContent = function (s) { return s.trim().length > 0; }; - * var userCheckers = [ - * _.checker(hasContent, "Name is required", ["name"]), - * _.checker(hasContent, "Surname is required", ["surname"]), - * _.checker(_.isGTE(18), "Must be at least 18 years old", ["age"]) - * ]; - * var validateUser = _.validateWith(userCheckers); - * - * var user1 = {name: "john", surname: "doe", age: 30}; - * var user2 = {name: "jane", surname: "", age: 15}; - * - * validateUser(user1) // => [] - * validateUser(user2) // => - * // [ - * // ["Surname is required", ["surname"]], - * // ["Must be at least 18 years old", ["age"]] - * // ] - * - * @memberof module:lamb - * @category Object - * @function - * @see {@link module:lamb.validate|validate} - * @see {@link module:lamb.checker|checker} - * @since 0.1.0 - * @param {Function[]} checkers - * @returns {Function} - */ -var validateWith = _curry2(validate, true); - -lamb.checker = checker; -lamb.has = has; -lamb.hasKey = hasKey; -lamb.hasKeyValue = hasKeyValue; -lamb.hasOwn = hasOwn; -lamb.hasOwnKey = hasOwnKey; -lamb.hasPathValue = hasPathValue; -lamb.keySatisfies = keySatisfies; -lamb.pathExists = pathExists; -lamb.pathExistsIn = pathExistsIn; -lamb.pathSatisfies = pathSatisfies; -lamb.validate = validate; -lamb.validateWith = validateWith; diff --git a/src/privates.js b/src/privates.js deleted file mode 100644 index ae189c9..0000000 --- a/src/privates.js +++ /dev/null @@ -1,965 +0,0 @@ -/** - * Builds an array with the received arguments excluding the first one.
- * To be used with the arguments object, which needs to be passed to the apply - * method of this function. - * @private - * @function - * @param {...*} value - * @returns {Array} - */ -var _argsTail = _argsToArrayFrom(1); - -/** - * Builds helper functions to extract portions of the arguments - * object rather efficiently without having to write for loops - * manually for each case.
- * The arguments object needs to be passed to the apply method - * of the generated function. - * @private - * @param {Number} idx - * @returns {Function} - */ -function _argsToArrayFrom (idx) { - return function () { - var argsLen = arguments.length || idx; - var len = argsLen - idx; - var result = Array(len); - - for (var i = 0; i < len; i++) { - result[i] = arguments[i + idx]; - } - - return result; - }; -} - -/** - * Keeps building a partial application of the received function as long - * as it's called with placeholders; applies the original function to - * the collected parameters otherwise.
- * The function checks only the public placeholder to gain a little performance - * as no function in Lamb is built with {@link module:lamb.asPartial|asPartial}. - * @private - * @param {Function} fn - * @param {Array} argsHolder - * @returns {Function|*} - */ -function _asPartial (fn, argsHolder) { - return function () { - var argsLen = arguments.length; - var lastIdx = 0; - var newArgs = []; - - for (var i = 0, len = argsHolder.length, boundArg; i < len; i++) { - boundArg = argsHolder[i]; - newArgs[i] = boundArg === _placeholder && lastIdx < argsLen ? arguments[lastIdx++] : boundArg; - } - - while (lastIdx < argsLen) { - newArgs[i++] = arguments[lastIdx++]; - } - - for (i = 0; i < argsLen; i++) { - if (arguments[i] === _placeholder) { - return _asPartial(fn, newArgs); - } - } - - for (i = 0, len = newArgs.length; i < len; i++) { - if (newArgs[i] === _placeholder) { - newArgs[i] = void 0; - } - } - - return fn.apply(this, newArgs); - }; -} - -/** - * Creates a function to check the given predicates.
- * Used to build the {@link module:lamb.allOf|allOf} and the - * {@link module:lamb.anyOf|anyOf} functions. - * @private - * @param {Boolean} checkAll - * @returns {Function} - */ -function _checkPredicates (checkAll) { - return function (predicates) { - if (!Array.isArray(predicates)) { - throw _makeTypeErrorFor(predicates, "array"); - } - - return function () { - for (var i = 0, len = predicates.length, result; i < len; i++) { - result = predicates[i].apply(this, arguments); - - if (checkAll && !result) { - return false; - } else if (!checkAll && result) { - return true; - } - } - - return checkAll; - }; - }; -} - -/** - * The default comparer for sorting functions.
- * If the given values are of different types they - * will be both converted to strings.
- * Uses the SameValueZero comparison. - * @private - * @param {*} a - * @param {*} b - * @returns {Number} -1 | 0 | 1 - */ -function _comparer (a, b) { - var result = 0; - - if (typeof a !== typeof b) { - a = String(a); - b = String(b); - } - - /* eslint-disable no-self-compare */ - - if (!areSVZ(a, b)) { - if (a > b || a !== a) { - result = 1; - } else if (a < b || b !== b) { - result = -1; - } - } - - /* eslint-enable no-self-compare */ - - return result; -} - -/** - * Accepts a list of sorting criteria with at least one element - * and builds a function that compares two values with such criteria. - * @private - * @param {Sorter[]} criteria - * @returns {Function} - */ -function _compareWith (criteria) { - return function (a, b) { - var len = criteria.length; - var criterion = criteria[0]; - var result = criterion.compare(a.value, b.value); - - for (var i = 1; result === 0 && i < len; i++) { - criterion = criteria[i]; - result = criterion.compare(a.value, b.value); - } - - if (result === 0) { - result = a.index - b.index; - } - - return criterion.isDescending ? -result : result; - }; -} - -/** - * Used by curry functions to collect arguments until the arity is consumed, - * then applies the original function. - * @private - * @param {Function} fn - * @param {Number} arity - * @param {Boolean} isRightCurry - * @param {Boolean} isAutoCurry - * @param {Array} argsHolder - * @returns {Function} - */ -function _currier (fn, arity, isRightCurry, isAutoCurry, argsHolder) { - return function () { - var holderLen = argsHolder.length; - var argsLen = arguments.length; - var newArgsLen = holderLen + (argsLen > 1 && isAutoCurry ? argsLen : 1); - var newArgs = Array(newArgsLen); - - for (var i = 0; i < holderLen; i++) { - newArgs[i] = argsHolder[i]; - } - - for (; i < newArgsLen; i++) { - newArgs[i] = arguments[i - holderLen]; - } - - if (newArgsLen >= arity) { - return fn.apply(this, isRightCurry ? newArgs.reverse() : newArgs); - } else { - return _currier(fn, arity, isRightCurry, isAutoCurry, newArgs); - } - }; -} - -/** - * Prepares a function for currying. If it's not auto-currying and the arity - * is 2 or 3 returns optimized functions, otherwise delegates the currying - * to the _currier function.
- * If the desumed arity isn't greater than one, it will return the received - * function itself, instead. - * @private - * @param {Function} fn - * @param {Number} [arity=fn.length] - * @param {Boolean} [isRightCurry=false] - * @param {Boolean} [isAutoCurry=false] - * @returns {Function} - */ -function _curry (fn, arity, isRightCurry, isAutoCurry) { - if (arity >>> 0 !== arity) { - arity = fn.length; - } - - if (isAutoCurry && arity > 1 || arity > 3) { - return _currier(fn, arity, isRightCurry, isAutoCurry, []); - } else if (arity === 2) { - return _curry2(fn, isRightCurry); - } else if (arity === 3) { - return _curry3(fn, isRightCurry); - } else { - return fn; - } -} - -/** - * Curries a function of arity 2. - * @private - * @param {Function} fn - * @param {Boolean} [isRightCurry=false] - * @returns {Function} - */ -function _curry2 (fn, isRightCurry) { - return function (a) { - return function (b) { - return isRightCurry ? fn.call(this, b, a) : fn.call(this, a, b); - }; - }; -} - -/** - * Curries a function of arity 3. - * @private - * @param {Function} fn - * @param {Boolean} [isRightCurry=false] - * @returns {Function} - */ -function _curry3 (fn, isRightCurry) { - return function (a) { - return function (b) { - return function (c) { - return isRightCurry ? fn.call(this, c, b, a) : fn.call(this, a, b, c); - }; - }; - }; -} - -/** - * Flattens an array. - * @private - * @param {Array} array - The source array - * @param {Boolean} isDeep - Whether to perform a deep flattening or not - * @param {Array} output - An array to collect the result - * @param {Number} idx - The next index to be filled in the output - * @returns {Array} The output array filled with the results - */ -function _flatten (array, isDeep, output, idx) { - for (var i = 0, len = array.length, value, j, vLen; i < len; i++) { - value = array[i]; - - if (!Array.isArray(value)) { - output[idx++] = value; - } else if (isDeep) { - _flatten(value, true, output, idx); - idx = output.length; - } else { - vLen = value.length; - output.length += vLen; - - for (j = 0; j < vLen; j++) { - output[idx++] = value[j]; - } - } - } - - return output; -} - -/** - * Converts a value to a number and returns it if it's not NaN, otherwise - * returns zero. - * @private - * @param {*} value - * @returns {Number} - */ -function _forceToNumber (value) { - var n = +value; - - return n === n ? n : 0; // eslint-disable-line no-self-compare -} - -/** - * Establishes at which index an element should be inserted in a sorted array to respect - * the array order. Needs the comparer used to sort the array. - * @private - * @param {Array} array - * @param {*} element - * @param {Function} comparer - * @param {Number} start - * @param {Number} end - * @returns {Number} - */ -function _getInsertionIndex (array, element, comparer, start, end) { - if (array.length === 0) { - return 0; - } - - var pivot = (start + end) >> 1; - var result = comparer( - {value: element, index: pivot}, - {value: array[pivot], index: pivot} - ); - - if (end - start <= 1) { - return result < 0 ? pivot : pivot + 1; - } else if (result < 0) { - return _getInsertionIndex(array, element, comparer, start, pivot); - } else if (result === 0) { - return pivot + 1; - } else { - return _getInsertionIndex(array, element, comparer, pivot, end); - } -} - -/** - * Gets the number of consecutive elements satisfying a predicate in an array-like object. - * @private - * @param {ArrayLike} arrayLike - * @param {ListIteratorCallback} predicate - * @returns {Number} - */ -function _getNumConsecutiveHits (arrayLike, predicate) { - var idx = 0; - var len = arrayLike.length; - - while (idx < len && predicate(arrayLike[idx], idx, arrayLike)) { - idx++; - } - - return idx; -} - -/** - * Builds the prefix or suffix to be used when padding a string. - * @private - * @param {String} source - * @param {String} char - * @param {Number} len - * @returns {String} - */ -function _getPadding (source, char, len) { - if (!isNil(source) && type(source) !== "String") { - source = String(source); - } - - return _repeat(String(char)[0] || "", Math.ceil(len - source.length)); -} - -/** - * Checks if a path is valid in the given object and retrieves the path target. - * @private - * @param {Object} obj - * @param {String[]} parts - * @param {Boolean} walkNonEnumerables - * @returns {Object} - */ -function _getPathInfo (obj, parts, walkNonEnumerables) { - if (isNil(obj)) { - throw _makeTypeErrorFor(obj, "object"); - } - - var target = obj; - var i = -1; - var len = parts.length; - var key; - - while (++i < len) { - key = _getPathKey(target, parts[i], walkNonEnumerables); - - if (isUndefined(key)) { - break; - } - - target = target[key]; - } - - return i === len ? {isValid: true, target: target} : {isValid: false, target: void 0}; -} - -/** - * Helper to retrieve the correct key while evaluating a path. - * @private - * @param {Object} target - * @param {String} key - * @param {Boolean} includeNonEnumerables - * @returns {String|Number|Undefined} - */ -function _getPathKey (target, key, includeNonEnumerables) { - if (includeNonEnumerables && key in Object(target) || _isEnumerable(target, key)) { - return key; - } - - var n = +key; - var len = target && target.length; - - return n >= -len && n < len ? n < 0 ? n + len : n : void 0; -} - -/** - * Builds a "grouping function" for an array-like object. - * @private - * @param {Function} makeValue - * @returns {Function} - */ -function _groupWith (makeValue) { - return function (arrayLike, iteratee) { - var result = {}; - var len = arrayLike.length; - - for (var i = 0, element, key; i < len; i++) { - element = arrayLike[i]; - key = iteratee(element, i, arrayLike); - result[key] = makeValue(result[key], element); - } - - return result; - }; -} - -/** - * Makes an object immutable by recursively calling Object.freeze - * on its members. - * @private - * @param {Object} obj - * @param {Array} seen - * @returns {Object} The obj parameter itself, not a copy. - */ -function _immutable (obj, seen) { - if (seen.indexOf(obj) === -1) { - seen.push(Object.freeze(obj)); - - forEach(Object.getOwnPropertyNames(obj), function (key) { - var value = obj[key]; - - if (typeof value === "object" && !isNull(value)) { - _immutable(value, seen); - } - }); - } - - return obj; -} - -/** - * If a method with the given name exists on the target, applies it to the provided - * arguments and returns the result. Returns undefined otherwise.
- * The arguments for the method are built by concatenating the array of bound arguments, - * optionally received by {@link module:lamb.invoker|invoker}, with the final set of, also - * optional, args. - * @private - * @param {Array} boundArgs - * @param {String} methodName - * @param {Object} target - * @param {...*} [args] - * @returns {*} - */ -function _invoker (boundArgs, methodName, target) { - var method = target[methodName]; - - if (typeof method !== "function") { - return void 0; - } - - var boundArgsLen = boundArgs.length; - var ofs = 3 - boundArgsLen; - var len = arguments.length - ofs; - var args = Array(len); - - for (var i = 0; i < boundArgsLen; i++) { - args[i] = boundArgs[i]; - } - - for (; i < len; i++) { - args[i] = arguments[i + ofs]; - } - - return method.apply(target, args); -} - -/** - * Accepts a target object and a key name and verifies that the target is an array and that - * the key is an existing index. - * @private - * @param {Object} target - * @param {String|Number} key - * @returns {Boolean} - */ -function _isArrayIndex (target, key) { - var n = +key; - - return Array.isArray(target) && n % 1 === 0 && !(n < 0 && _isEnumerable(target, key)); -} - -/** - * Checks whether the specified key is an enumerable property of the given object or not. - * @private - * @param {Object} obj - * @param {String} key - * @returns {Boolean} - */ -function _isEnumerable (obj, key) { - return key in Object(obj) && (_isOwnEnumerable(obj, key) || ~_safeEnumerables(obj).indexOf(key)); -} - -/** - * Checks whether the specified key is a own enumerable property of the given object or not. - * @private - * @function - * @param {Object} obj - * @param {String} key - * @returns {Boolean} - */ -var _isOwnEnumerable = generic(_objectProto.propertyIsEnumerable); - -/** - * Checks whether the given value is the internal or the public placeholder. - * @private - * @param {*} value - * @returns {Boolean} - */ -function _isPlaceholder (value) { - return value === _ || value === _placeholder; -} - -/** - * Accepts an object and build a function expecting a key to create a "pair" with the key - * and its value. - * @private - * @function - * @param {Object} obj - * @returns {Function} - */ -var _keyToPairIn = _curry2(function (obj, key) { - return [key, obj[key]]; -}); - -/** - * Helper to build the {@link module:lamb.everyIn|everyIn} or the - * {@link module:lamb.someIn|someIn} function. - * @private - * @param {Boolean} defaultResult - * @returns {Function} - */ -function _makeArrayChecker (defaultResult) { - return function (arrayLike, predicate) { - for (var i = 0, len = arrayLike.length; i < len; i++) { - if (defaultResult ^ !!predicate(arrayLike[i], i, arrayLike)) { - return !defaultResult; - } - } - - return defaultResult; - }; -} - -/** - * Helper to build the {@link module:lamb.flatten|flatten} and - * {@link module:lamb.shallowFlatten|shallowFlatten} functions. - * @private - * @function - * @param {Boolean} isDeep - * @returns {Function} - */ -var _makeArrayFlattener = _curry2(function (isDeep, array) { - return Array.isArray(array) ? _flatten(array, isDeep, [], 0) : slice(array, 0, array.length); -}); - -/** - * Builds a list of sorting criteria from a list of sorter functions. Returns a list containing - * a single default sorting criterion if the sorter list is empty. - * @private - * @param {Function[]} sorters - * @returns {Sorter[]} - */ -function _makeCriteria (sorters) { - return sorters && sorters.length ? map(sorters, _makeCriterion) : [_sorter()]; -} - -/** - * Converts a sorting function to a sorting criterion if necessary. - * @private - * @param {Function} criterion - * @returns {Sorter} - */ -function _makeCriterion (criterion) { - return criterion && typeof criterion.compare === "function" ? criterion : _sorter(criterion); -} - -/** - * Builds a partial application of a ternary function so that its first parameter - * is expected as the last one.
- * The shouldAritize parameter is for the "reduce" functions, where - * the absence of the initialValue transforms a "fold" operation into a - * "reduce" one. - * @private - * @param {Function} fn - * @param {Boolean} shouldAritize - * @returns {Function} - */ -function _makePartial3 (fn, shouldAritize) { - return function (a, b) { - var f = shouldAritize && arguments.length !== 2 ? binary(fn) : fn; - - return partial(f, [_, a, b]); - }; -} - -/** - * Builds a reduce function. The step parameter must be 1 - * to build {@link module:lamb.reduce|reduce} and -1 to build - * {@link module:lamb.reduceRight|reduceRight}. - * @private - * @param {Number} step - * @returns {Function} - */ -function _makeReducer (step) { - return function (arrayLike, accumulator, initialValue) { - var len = _toArrayLength(arrayLike.length); - var idx = step === 1 ? 0 : len - 1; - var nCalls; - var result; - - if (arguments.length === 3) { - nCalls = len; - result = initialValue; - } else { - if (len === 0) { - throw new TypeError("Reduce of empty array-like with no initial value"); - } - - result = arrayLike[idx]; - idx += step; - nCalls = len - 1; - } - - for (; nCalls--; idx += step) { - result = accumulator(result, arrayLike[idx], idx, arrayLike); - } - - return result; - }; -} - -/** - * Builds a TypeError stating that it's not possible to convert the given value to the - * desired type. - * @private - * @param {*} value - * @param {String} desiredType - * @returns {TypeError} - */ -function _makeTypeErrorFor (value, desiredType) { - return new TypeError("Cannot convert " + type(value).toLowerCase() + " to " + desiredType); -} - -/** - * Merges the received objects using the provided function to retrieve their keys. - * @private - * @param {Function} getKeys - * @param {Object} a - * @param {Object} b - * @returns {Function} - */ -function _merge (getKeys, a, b) { - return reduce([a, b], function (result, source) { - forEach(getKeys(source), function (key) { - result[key] = source[key]; - }); - - return result; - }, {}); -} - -/** - * Using the provided function to retrieve the keys, builds a new function - * expecting an object to create a list of key / value pairs. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} - */ -var _pairsFrom = _curry2(function (getKeys, obj) { - return map(getKeys(obj), _keyToPairIn(obj)); -}); - -/** - * A null-safe function to repeat the source string the desired amount of times. - * @private - * @param {String} source - * @param {Number} times - * @returns {String} - */ -function _repeat (source, times) { - var result = ""; - - for (var i = 0; i < times; i++) { - result += source; - } - - return result; -} - -/** - * Builds a list of the enumerable properties of an object. - * The function is null-safe, unlike the public one. - * @private - * @param {Object} obj - * @returns {String[]} - */ -function _safeEnumerables (obj) { - var result = []; - - for (var key in obj) { - result.push(key); - } - - return result; -} - -/** - * A null-safe version of Object.keys. - * @private - * @function - * @param {Object} obj - * @returns {String[]} - */ -var _safeKeys = compose(Object.keys, Object); - -/** - * A generic version of String.prototype.search - * @private - * @function - * @param {String} s - * @param {RegExp} pattern - * @return {Number} - */ -var _search = generic(_stringProto.search); - -/** - * Sets, or creates, a property in a copy of the provided object to the desired value. - * @private - * @param {Object} source - * @param {String} key - * @param {*} value - * @returns {Object} - */ -function _setIn (source, key, value) { - var result = {}; - - for (var prop in source) { - result[prop] = source[prop]; - } - - result[key] = value; - - return result; -} - -/** - * Sets an index in an array-like object.
- * If provided with an updater function it will use it to update the current value, - * otherwise sets the index to the specified value. - * @private - * @param {ArrayLike} arrayLike - * @param {Number} idx - * @param {*} [value] - * @param {Function} [updater] - * @returns {Array} - */ -function _setIndex (arrayLike, idx, value, updater) { - var result = slice(arrayLike, 0, arrayLike.length); - var n = _toNaturalIndex(idx, result.length); - - if (n === n) { // eslint-disable-line no-self-compare - result[n] = arguments.length === 4 ? updater(arrayLike[n]) : value; - } - - return result; -} - -/** - * Sets the object's property targeted by the given path to the desired value.
- * Works with arrays and is able to set their indexes, even negative ones. - * @private - * @param {Object|Array} obj - * @param {String[]} parts - * @param {*} value - * @returns {Object|Array} - */ -function _setPathIn (obj, parts, value) { - var key = parts[0]; - var partsLen = parts.length; - var v; - - if (partsLen === 1) { - v = value; - } else { - var targetKey = _getPathKey(obj, key, false); - - v = _setPathIn( - isUndefined(targetKey) ? targetKey : obj[targetKey], - slice(parts, 1, partsLen), - value - ); - } - - return _isArrayIndex(obj, key) ? _setIndex(obj, key, v) : _setIn(obj, key, v); -} - -/** - * Builds a sorting criterion. If the comparer function is missing, the default - * comparer will be used instead. - * @private - * @param {Function} reader - * @param {Boolean} isDescending - * @param {Function} [comparer] - * @returns {Sorter} - */ -function _sorter (reader, isDescending, comparer) { - if (typeof reader !== "function" || reader === identity) { - reader = null; - } - - if (typeof comparer !== "function") { - comparer = _comparer; - } - - return { - isDescending: isDescending === true, - compare: function (a, b) { - if (reader) { - a = reader(a); - b = reader(b); - } - - return comparer(a, b); - } - }; -} - -/** - * Using the provided function to retrieve the keys of an object, builds - * a function expecting an object to create an array containing a list - * of the keys in its first index and the corresponding list of values - * in the second one. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} - */ -var _tearFrom = _curry2(function (getKeys, obj) { - return reduce(getKeys(obj), function (result, key) { - result[0].push(key); - result[1].push(obj[key]); - - return result; - }, [[], []]); -}); - -/** - * Converts a value to a valid array length, thus an integer within - * 0 and 232 - 1 (both included). - * @private - * @param {*} value - * @returns {Number} - */ -function _toArrayLength (value) { - return clamp(value, 0, MAX_ARRAY_LENGTH) >>> 0; -} - -/** - * Converts a value to an integer. - * @private - * @param {*} value - * @returns {Number} - */ -function _toInteger (value) { - var n = +value; - - if (n !== n) { // eslint-disable-line no-self-compare - return 0; - } else if (n % 1 === 0) { - return n; - } else { - return Math.floor(Math.abs(n)) * (n < 0 ? -1 : 1); - } -} - -/** - * Checks if the given number, even negative, represents an array-like index - * within the provided length. If so returns its natural number equivalent.
- * Returns NaN otherwise. - * @private - * @param {Number} idx - * @param {Number} len - * @returns {Number} - */ -function _toNaturalIndex (idx, len) { - idx = _toInteger(idx); - - return idx >= -len && idx < len ? idx < 0 ? idx + len : idx : NaN; -} - -/** - * Splits a sting path using the provided separator and returns an array - * of path parts. - * @private - * @param {String} path - * @param {String} separator - * @returns {String[]} - */ -function _toPathParts (path, separator) { - return String(path).split(separator || "."); -} - -/** - * Creates a non-null-safe version of the provided "getKeys" function. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} - */ -var _unsafeKeyListFrom = _curry2(function (getKeys, obj) { - if (isNil(obj)) { - throw _makeTypeErrorFor(obj, "object"); - } - - return getKeys(obj); -}); - -/** - * Using the provided function to retrieve the keys of an object, builds - * a function expecting an object to create the list of values for such keys. - * @private - * @function - * @param {Function} getKeys - * @returns {Function} - */ -var _valuesFrom = _curry2(function (getKeys, obj) { - return map(getKeys(obj), partial(getIn, [obj])); -}); diff --git a/src/privates/_argsTail.js b/src/privates/_argsTail.js new file mode 100644 index 0000000..7552d08 --- /dev/null +++ b/src/privates/_argsTail.js @@ -0,0 +1,14 @@ +import _argsToArrayFrom from "./_argsToArrayFrom"; + +/** + * Builds an array with the received arguments excluding the first one.
+ * To be used with the arguments object, which needs to be passed to the apply + * method of this function. + * @private + * @function + * @param {...*} value + * @returns {Array} + */ +var _argsTail = _argsToArrayFrom(1); + +export default _argsTail; diff --git a/src/privates/_argsToArrayFrom.js b/src/privates/_argsToArrayFrom.js new file mode 100644 index 0000000..42e7de0 --- /dev/null +++ b/src/privates/_argsToArrayFrom.js @@ -0,0 +1,25 @@ +/** + * Builds helper functions to extract portions of the arguments + * object rather efficiently without having to write for loops + * manually for each case.
+ * The arguments object needs to be passed to the apply method + * of the generated function. + * @private + * @param {Number} idx + * @returns {Function} + */ +function _argsToArrayFrom (idx) { + return function () { + var argsLen = arguments.length || idx; + var len = argsLen - idx; + var result = Array(len); + + for (var i = 0; i < len; i++) { + result[i] = arguments[i + idx]; + } + + return result; + }; +} + +export default _argsToArrayFrom; diff --git a/src/privates/_asPartial.js b/src/privates/_asPartial.js new file mode 100644 index 0000000..0a5c56a --- /dev/null +++ b/src/privates/_asPartial.js @@ -0,0 +1,45 @@ +import __ from "../core/__"; + +/** + * Keeps building a partial application of the received function as long + * as it's called with placeholders; applies the original function to + * the collected parameters otherwise.
+ * The function checks only the public placeholder to gain a little performance + * as no function in Lamb is built with {@link module:lamb.asPartial|asPartial}. + * @private + * @param {Function} fn + * @param {Array} argsHolder + * @returns {Function|*} + */ +function _asPartial (fn, argsHolder) { + return function () { + var argsLen = arguments.length; + var lastIdx = 0; + var newArgs = []; + + for (var i = 0, len = argsHolder.length, boundArg; i < len; i++) { + boundArg = argsHolder[i]; + newArgs[i] = boundArg === __ && lastIdx < argsLen ? arguments[lastIdx++] : boundArg; + } + + while (lastIdx < argsLen) { + newArgs[i++] = arguments[lastIdx++]; + } + + for (i = 0; i < argsLen; i++) { + if (arguments[i] === __) { + return _asPartial(fn, newArgs); + } + } + + for (i = 0, len = newArgs.length; i < len; i++) { + if (newArgs[i] === __) { + newArgs[i] = void 0; + } + } + + return fn.apply(this, newArgs); + }; +} + +export default _asPartial; diff --git a/src/privates/_checkPredicates.js b/src/privates/_checkPredicates.js new file mode 100644 index 0000000..11918d8 --- /dev/null +++ b/src/privates/_checkPredicates.js @@ -0,0 +1,33 @@ +import _makeTypeErrorFor from "./_makeTypeErrorFor"; + +/** + * Creates a function to check the given predicates.
+ * Used to build the {@link module:lamb.allOf|allOf} and the + * {@link module:lamb.anyOf|anyOf} functions. + * @private + * @param {Boolean} checkAll + * @returns {Function} + */ +function _checkPredicates (checkAll) { + return function (predicates) { + if (!Array.isArray(predicates)) { + throw _makeTypeErrorFor(predicates, "array"); + } + + return function () { + for (var i = 0, len = predicates.length, result; i < len; i++) { + result = predicates[i].apply(this, arguments); + + if (checkAll && !result) { + return false; + } else if (!checkAll && result) { + return true; + } + } + + return checkAll; + }; + }; +} + +export default _checkPredicates; diff --git a/src/privates/_compareWith.js b/src/privates/_compareWith.js new file mode 100644 index 0000000..9b671b3 --- /dev/null +++ b/src/privates/_compareWith.js @@ -0,0 +1,27 @@ +/** + * Accepts a list of sorting criteria with at least one element + * and builds a function that compares two values with such criteria. + * @private + * @param {Sorter[]} criteria + * @returns {Function} + */ +function _compareWith (criteria) { + return function (a, b) { + var len = criteria.length; + var criterion = criteria[0]; + var result = criterion.compare(a.value, b.value); + + for (var i = 1; result === 0 && i < len; i++) { + criterion = criteria[i]; + result = criterion.compare(a.value, b.value); + } + + if (result === 0) { + result = a.index - b.index; + } + + return criterion.isDescending ? -result : result; + }; +} + +export default _compareWith; diff --git a/src/privates/_comparer.js b/src/privates/_comparer.js new file mode 100644 index 0000000..e35bb23 --- /dev/null +++ b/src/privates/_comparer.js @@ -0,0 +1,29 @@ +import areSVZ from "../core/areSVZ"; + +/** + * The default comparer for sorting functions.
+ * If the given values are of different types they + * will be both converted to strings.
+ * Uses the SameValueZero comparison. + * @private + * @param {*} a + * @param {*} b + * @returns {Number} -1 | 0 | 1 + */ +function _comparer (a, b) { + var result = 0; + + if (typeof a !== typeof b) { + a = String(a); + b = String(b); + } + + if (!areSVZ(a, b)) { + // eslint-disable-next-line no-self-compare + result = a > b || a !== a ? 1 : -1; + } + + return result; +} + +export default _comparer; diff --git a/src/privates/_constants.js b/src/privates/_constants.js new file mode 100644 index 0000000..f1200e3 --- /dev/null +++ b/src/privates/_constants.js @@ -0,0 +1,2 @@ +export var MAX_ARRAY_LENGTH = 4294967295; +export var MAX_SAFE_INTEGER = 9007199254740991; diff --git a/src/privates/_currier.js b/src/privates/_currier.js new file mode 100644 index 0000000..2c5c7a4 --- /dev/null +++ b/src/privates/_currier.js @@ -0,0 +1,35 @@ +/** + * Used by curry functions to collect arguments until the arity is consumed, + * then applies the original function. + * @private + * @param {Function} fn + * @param {Number} arity + * @param {Boolean} isRightCurry + * @param {Boolean} isAutoCurry + * @param {Array} argsHolder + * @returns {Function} + */ +function _currier (fn, arity, isRightCurry, isAutoCurry, argsHolder) { + return function () { + var holderLen = argsHolder.length; + var argsLen = arguments.length; + var newArgsLen = holderLen + (argsLen > 1 && isAutoCurry ? argsLen : 1); + var newArgs = Array(newArgsLen); + + for (var i = 0; i < holderLen; i++) { + newArgs[i] = argsHolder[i]; + } + + for (; i < newArgsLen; i++) { + newArgs[i] = arguments[i - holderLen]; + } + + if (newArgsLen >= arity) { + return fn.apply(this, isRightCurry ? newArgs.reverse() : newArgs); + } else { + return _currier(fn, arity, isRightCurry, isAutoCurry, newArgs); + } + }; +} + +export default _currier; diff --git a/src/privates/_curry.js b/src/privates/_curry.js new file mode 100644 index 0000000..868d9f0 --- /dev/null +++ b/src/privates/_curry.js @@ -0,0 +1,34 @@ +import _currier from "./_currier"; +import _curry2 from "./_curry2"; +import _curry3 from "./_curry3"; + +/** + * Prepares a function for currying. If it's not auto-currying and the arity + * is 2 or 3 returns optimized functions, otherwise delegates the currying + * to the _currier function.
+ * If the desumed arity isn't greater than one, it will return the received + * function itself, instead. + * @private + * @param {Function} fn + * @param {Number} [arity=fn.length] + * @param {Boolean} [isRightCurry=false] + * @param {Boolean} [isAutoCurry=false] + * @returns {Function} + */ +function _curry (fn, arity, isRightCurry, isAutoCurry) { + if (arity >>> 0 !== arity) { + arity = fn.length; + } + + if (isAutoCurry && arity > 1 || arity > 3) { + return _currier(fn, arity, isRightCurry, isAutoCurry, []); + } else if (arity === 2) { + return _curry2(fn, isRightCurry); + } else if (arity === 3) { + return _curry3(fn, isRightCurry); + } else { + return fn; + } +} + +export default _curry; diff --git a/src/privates/_curry2.js b/src/privates/_curry2.js new file mode 100644 index 0000000..5100ff0 --- /dev/null +++ b/src/privates/_curry2.js @@ -0,0 +1,16 @@ +/** + * Curries a function of arity 2. + * @private + * @param {Function} fn + * @param {Boolean} [isRightCurry=false] + * @returns {Function} + */ +function _curry2 (fn, isRightCurry) { + return function (a) { + return function (b) { + return isRightCurry ? fn.call(this, b, a) : fn.call(this, a, b); + }; + }; +} + +export default _curry2; diff --git a/src/privates/_curry3.js b/src/privates/_curry3.js new file mode 100644 index 0000000..6fec6d8 --- /dev/null +++ b/src/privates/_curry3.js @@ -0,0 +1,18 @@ +/** + * Curries a function of arity 3. + * @private + * @param {Function} fn + * @param {Boolean} [isRightCurry=false] + * @returns {Function} + */ +function _curry3 (fn, isRightCurry) { + return function (a) { + return function (b) { + return function (c) { + return isRightCurry ? fn.call(this, c, b, a) : fn.call(this, a, b, c); + }; + }; + }; +} + +export default _curry3; diff --git a/src/privates/_flatten.js b/src/privates/_flatten.js new file mode 100644 index 0000000..e82b66b --- /dev/null +++ b/src/privates/_flatten.js @@ -0,0 +1,32 @@ +/** + * Flattens an array. + * @private + * @param {Array} array - The source array + * @param {Boolean} isDeep - Whether to perform a deep flattening or not + * @param {Array} output - An array to collect the result + * @param {Number} idx - The next index to be filled in the output + * @returns {Array} The output array filled with the results + */ +function _flatten (array, isDeep, output, idx) { + for (var i = 0, len = array.length, value, j, vLen; i < len; i++) { + value = array[i]; + + if (!Array.isArray(value)) { + output[idx++] = value; + } else if (isDeep) { + _flatten(value, true, output, idx); + idx = output.length; + } else { + vLen = value.length; + output.length += vLen; + + for (j = 0; j < vLen; j++) { + output[idx++] = value[j]; + } + } + } + + return output; +} + +export default _flatten; diff --git a/src/privates/_forceToNumber.js b/src/privates/_forceToNumber.js new file mode 100644 index 0000000..c5a5c44 --- /dev/null +++ b/src/privates/_forceToNumber.js @@ -0,0 +1,14 @@ +/** + * Converts a value to a number and returns it if it's not NaN, otherwise + * returns zero. + * @private + * @param {*} value + * @returns {Number} + */ +function _forceToNumber (value) { + var n = +value; + + return n === n ? n : 0; // eslint-disable-line no-self-compare +} + +export default _forceToNumber; diff --git a/src/privates/_getInsertionIndex.js b/src/privates/_getInsertionIndex.js new file mode 100644 index 0000000..e97acde --- /dev/null +++ b/src/privates/_getInsertionIndex.js @@ -0,0 +1,34 @@ +/** + * Establishes at which index an element should be inserted in a sorted array to respect + * the array order. Needs the comparer used to sort the array. + * @private + * @param {Array} array + * @param {*} element + * @param {Function} comparer + * @param {Number} start + * @param {Number} end + * @returns {Number} + */ +function _getInsertionIndex (array, element, comparer, start, end) { + if (array.length === 0) { + return 0; + } + + var pivot = (start + end) >> 1; + var result = comparer( + { value: element, index: pivot }, + { value: array[pivot], index: pivot } + ); + + if (end - start <= 1) { + return result < 0 ? pivot : pivot + 1; + } else if (result < 0) { + return _getInsertionIndex(array, element, comparer, start, pivot); + } else if (result === 0) { + return pivot + 1; + } else { + return _getInsertionIndex(array, element, comparer, pivot, end); + } +} + +export default _getInsertionIndex; diff --git a/src/privates/_getNumConsecutiveHits.js b/src/privates/_getNumConsecutiveHits.js new file mode 100644 index 0000000..bad77cf --- /dev/null +++ b/src/privates/_getNumConsecutiveHits.js @@ -0,0 +1,19 @@ +/** + * Gets the number of consecutive elements satisfying a predicate in an array-like object. + * @private + * @param {ArrayLike} arrayLike + * @param {ListIteratorCallback} predicate + * @returns {Number} + */ +function _getNumConsecutiveHits (arrayLike, predicate) { + var idx = 0; + var len = arrayLike.length; + + while (idx < len && predicate(arrayLike[idx], idx, arrayLike)) { + idx++; + } + + return idx; +} + +export default _getNumConsecutiveHits; diff --git a/src/privates/_getPadding.js b/src/privates/_getPadding.js new file mode 100644 index 0000000..08f2c23 --- /dev/null +++ b/src/privates/_getPadding.js @@ -0,0 +1,21 @@ +import isNil from "../core/isNil"; +import type from "../core/type"; +import _repeat from "./_repeat"; + +/** + * Builds the prefix or suffix to be used when padding a string. + * @private + * @param {String} source + * @param {String} char + * @param {Number} len + * @returns {String} + */ +function _getPadding (source, char, len) { + if (!isNil(source) && type(source) !== "String") { + source = String(source); + } + + return _repeat(String(char)[0] || "", Math.ceil(len - source.length)); +} + +export default _getPadding; diff --git a/src/privates/_getPathInfo.js b/src/privates/_getPathInfo.js new file mode 100644 index 0000000..8e529dc --- /dev/null +++ b/src/privates/_getPathInfo.js @@ -0,0 +1,37 @@ +import isNil from "../core/isNil"; +import isUndefined from "../core/isUndefined"; +import _getPathKey from "./_getPathKey"; +import _makeTypeErrorFor from "./_makeTypeErrorFor"; + +/** + * Checks if a path is valid in the given object and retrieves the path target. + * @private + * @param {Object} obj + * @param {String[]} parts + * @param {Boolean} walkNonEnumerables + * @returns {Object} + */ +function _getPathInfo (obj, parts, walkNonEnumerables) { + if (isNil(obj)) { + throw _makeTypeErrorFor(obj, "object"); + } + + var target = obj; + var i = -1; + var len = parts.length; + var key; + + while (++i < len) { + key = _getPathKey(target, parts[i], walkNonEnumerables); + + if (isUndefined(key)) { + break; + } + + target = target[key]; + } + + return i === len ? { isValid: true, target: target } : { isValid: false, target: void 0 }; +} + +export default _getPathInfo; diff --git a/src/privates/_getPathKey.js b/src/privates/_getPathKey.js new file mode 100644 index 0000000..efba917 --- /dev/null +++ b/src/privates/_getPathKey.js @@ -0,0 +1,22 @@ +import _isEnumerable from "./_isEnumerable"; + +/** + * Helper to retrieve the correct key while evaluating a path. + * @private + * @param {Object} target + * @param {String} key + * @param {Boolean} includeNonEnumerables + * @returns {String|Number|Undefined} + */ +function _getPathKey (target, key, includeNonEnumerables) { + if (includeNonEnumerables && key in Object(target) || _isEnumerable(target, key)) { + return key; + } + + var n = +key; + var len = target && target.length; + + return n >= -len && n < len ? n < 0 ? n + len : n : void 0; +} + +export default _getPathKey; diff --git a/src/privates/_groupWith.js b/src/privates/_groupWith.js new file mode 100644 index 0000000..2873989 --- /dev/null +++ b/src/privates/_groupWith.js @@ -0,0 +1,22 @@ +/** + * Builds a "grouping function" for an array-like object. + * @private + * @param {Function} makeValue + * @returns {Function} + */ +function _groupWith (makeValue) { + return function (arrayLike, iteratee) { + var result = {}; + var len = arrayLike.length; + + for (var i = 0, element, key; i < len; i++) { + element = arrayLike[i]; + key = iteratee(element, i, arrayLike); + result[key] = makeValue(result[key], element); + } + + return result; + }; +} + +export default _groupWith; diff --git a/src/privates/_immutable.js b/src/privates/_immutable.js new file mode 100644 index 0000000..778a61d --- /dev/null +++ b/src/privates/_immutable.js @@ -0,0 +1,28 @@ +import forEach from "../core/forEach"; +import isNull from "../core/isNull"; + +/** + * Makes an object immutable by recursively calling Object.freeze + * on its members. + * @private + * @param {Object} obj + * @param {Array} seen + * @returns {Object} The obj parameter itself, not a copy. + */ +function _immutable (obj, seen) { + if (seen.indexOf(obj) === -1) { + seen.push(Object.freeze(obj)); + + forEach(Object.getOwnPropertyNames(obj), function (key) { + var value = obj[key]; + + if (typeof value === "object" && !isNull(value)) { + _immutable(value, seen); + } + }); + } + + return obj; +} + +export default _immutable; diff --git a/src/privates/_invoker.js b/src/privates/_invoker.js new file mode 100644 index 0000000..0a969fc --- /dev/null +++ b/src/privates/_invoker.js @@ -0,0 +1,37 @@ +/** + * If a method with the given name exists on the target, applies it to the provided + * arguments and returns the result. Returns undefined otherwise.
+ * The arguments for the method are built by concatenating the array of bound arguments, + * optionally received by {@link module:lamb.invoker|invoker}, with the final set of, also + * optional, args. + * @private + * @param {Array} boundArgs + * @param {String} methodName + * @param {Object} target + * @param {...*} [args] + * @returns {*} + */ +function _invoker (boundArgs, methodName, target) { + var method = target[methodName]; + + if (typeof method !== "function") { + return void 0; + } + + var boundArgsLen = boundArgs.length; + var ofs = 3 - boundArgsLen; + var len = arguments.length - ofs; + var args = Array(len); + + for (var i = 0; i < boundArgsLen; i++) { + args[i] = boundArgs[i]; + } + + for (; i < len; i++) { + args[i] = arguments[i + ofs]; + } + + return method.apply(target, args); +} + +export default _invoker; diff --git a/src/privates/_isArrayIndex.js b/src/privates/_isArrayIndex.js new file mode 100644 index 0000000..1d41716 --- /dev/null +++ b/src/privates/_isArrayIndex.js @@ -0,0 +1,17 @@ +import _isEnumerable from "./_isEnumerable"; + +/** + * Accepts a target object and a key name and verifies that the target is an array and that + * the key is an existing index. + * @private + * @param {Object} target + * @param {String|Number} key + * @returns {Boolean} + */ +function _isArrayIndex (target, key) { + var n = +key; + + return Array.isArray(target) && n % 1 === 0 && !(n < 0 && _isEnumerable(target, key)); +} + +export default _isArrayIndex; diff --git a/src/privates/_isEnumerable.js b/src/privates/_isEnumerable.js new file mode 100644 index 0000000..321e22b --- /dev/null +++ b/src/privates/_isEnumerable.js @@ -0,0 +1,15 @@ +import _isOwnEnumerable from "./_isOwnEnumerable"; +import _safeEnumerables from "./_safeEnumerables"; + +/** + * Checks whether the specified key is an enumerable property of the given object or not. + * @private + * @param {Object} obj + * @param {String} key + * @returns {Boolean} + */ +function _isEnumerable (obj, key) { + return key in Object(obj) && (_isOwnEnumerable(obj, key) || ~_safeEnumerables(obj).indexOf(key)); +} + +export default _isEnumerable; diff --git a/src/privates/_isOwnEnumerable.js b/src/privates/_isOwnEnumerable.js new file mode 100644 index 0000000..c033306 --- /dev/null +++ b/src/privates/_isOwnEnumerable.js @@ -0,0 +1,13 @@ +import generic from "../core/generic"; + +/** + * Checks whether the specified key is a own enumerable property of the given object or not. + * @private + * @function + * @param {Object} obj + * @param {String} key + * @returns {Boolean} + */ +var _isOwnEnumerable = generic(Object.prototype.propertyIsEnumerable); + +export default _isOwnEnumerable; diff --git a/src/privates/_keyToPairIn.js b/src/privates/_keyToPairIn.js new file mode 100644 index 0000000..9bf6e66 --- /dev/null +++ b/src/privates/_keyToPairIn.js @@ -0,0 +1,15 @@ +import _curry2 from "./_curry2"; + +/** + * Accepts an object and build a function expecting a key to create a "pair" with the key + * and its value. + * @private + * @function + * @param {Object} obj + * @returns {Function} + */ +var _keyToPairIn = _curry2(function (obj, key) { + return [key, obj[key]]; +}); + +export default _keyToPairIn; diff --git a/src/privates/_makeArrayChecker.js b/src/privates/_makeArrayChecker.js new file mode 100644 index 0000000..bb28d35 --- /dev/null +++ b/src/privates/_makeArrayChecker.js @@ -0,0 +1,20 @@ +/** + * Helper to build the {@link module:lamb.everyIn|everyIn} or the + * {@link module:lamb.someIn|someIn} function. + * @private + * @param {Boolean} defaultResult + * @returns {Function} + */ +function _makeArrayChecker (defaultResult) { + return function (arrayLike, predicate) { + for (var i = 0, len = arrayLike.length; i < len; i++) { + if (defaultResult ^ !!predicate(arrayLike[i], i, arrayLike)) { + return !defaultResult; + } + } + + return defaultResult; + }; +} + +export default _makeArrayChecker; diff --git a/src/privates/_makeArrayFlattener.js b/src/privates/_makeArrayFlattener.js new file mode 100644 index 0000000..f85b2c3 --- /dev/null +++ b/src/privates/_makeArrayFlattener.js @@ -0,0 +1,17 @@ +import slice from "../core/slice"; +import _curry2 from "./_curry2"; +import _flatten from "./_flatten"; + +/** + * Helper to build the {@link module:lamb.flatten|flatten} and + * {@link module:lamb.shallowFlatten|shallowFlatten} functions. + * @private + * @function + * @param {Boolean} isDeep + * @returns {Function} + */ +var _makeArrayFlattener = _curry2(function (isDeep, array) { + return Array.isArray(array) ? _flatten(array, isDeep, [], 0) : slice(array, 0, array.length); +}); + +export default _makeArrayFlattener; diff --git a/src/privates/_makeCriteria.js b/src/privates/_makeCriteria.js new file mode 100644 index 0000000..eae24ec --- /dev/null +++ b/src/privates/_makeCriteria.js @@ -0,0 +1,16 @@ +import map from "../core/map"; +import _makeCriterion from "./_makeCriterion"; +import _sorter from "./_sorter"; + +/** + * Builds a list of sorting criteria from a list of sorter functions. Returns a list containing + * a single default sorting criterion if the sorter list is empty. + * @private + * @param {Function[]} sorters + * @returns {Sorter[]} + */ +function _makeCriteria (sorters) { + return sorters && sorters.length ? map(sorters, _makeCriterion) : [_sorter()]; +} + +export default _makeCriteria; diff --git a/src/privates/_makeCriterion.js b/src/privates/_makeCriterion.js new file mode 100644 index 0000000..9cb3292 --- /dev/null +++ b/src/privates/_makeCriterion.js @@ -0,0 +1,13 @@ +import _sorter from "./_sorter"; + +/** + * Converts a sorting function to a sorting criterion if necessary. + * @private + * @param {Function} criterion + * @returns {Sorter} + */ +function _makeCriterion (criterion) { + return criterion && typeof criterion.compare === "function" ? criterion : _sorter(criterion); +} + +export default _makeCriterion; diff --git a/src/privates/_makePartial3.js b/src/privates/_makePartial3.js new file mode 100644 index 0000000..000af63 --- /dev/null +++ b/src/privates/_makePartial3.js @@ -0,0 +1,24 @@ +import __ from "../core/__"; +import binary from "../core/binary"; +import partial from "../core/partial"; + +/** + * Builds a partial application of a ternary function so that its first parameter + * is expected as the last one.
+ * The shouldAritize parameter is for the "reduce" functions, where + * the absence of the initialValue transforms a "fold" operation into a + * "reduce" one. + * @private + * @param {Function} fn + * @param {Boolean} shouldAritize + * @returns {Function} + */ +function _makePartial3 (fn, shouldAritize) { + return function (a, b) { + var f = shouldAritize && arguments.length !== 2 ? binary(fn) : fn; + + return partial(f, [__, a, b]); + }; +} + +export default _makePartial3; diff --git a/src/privates/_makeReducer.js b/src/privates/_makeReducer.js new file mode 100644 index 0000000..7c3c65c --- /dev/null +++ b/src/privates/_makeReducer.js @@ -0,0 +1,39 @@ +import _toArrayLength from "./_toArrayLength"; + +/** + * Builds a reduce function. The step parameter must be 1 + * to build {@link module:lamb.reduce|reduce} and -1 to build + * {@link module:lamb.reduceRight|reduceRight}. + * @private + * @param {Number} step + * @returns {Function} + */ +function _makeReducer (step) { + return function (arrayLike, accumulator, initialValue) { + var len = _toArrayLength(arrayLike.length); + var idx = step === 1 ? 0 : len - 1; + var nCalls; + var result; + + if (arguments.length === 3) { + nCalls = len; + result = initialValue; + } else { + if (len === 0) { + throw new TypeError("Reduce of empty array-like with no initial value"); + } + + result = arrayLike[idx]; + idx += step; + nCalls = len - 1; + } + + for (; nCalls--; idx += step) { + result = accumulator(result, arrayLike[idx], idx, arrayLike); + } + + return result; + }; +} + +export default _makeReducer; diff --git a/src/privates/_makeTypeErrorFor.js b/src/privates/_makeTypeErrorFor.js new file mode 100644 index 0000000..81e2b48 --- /dev/null +++ b/src/privates/_makeTypeErrorFor.js @@ -0,0 +1,15 @@ +import type from "../core/type"; + +/** + * Builds a TypeError stating that it's not possible to convert the given value to the + * desired type. + * @private + * @param {*} value + * @param {String} desiredType + * @returns {TypeError} + */ +function _makeTypeErrorFor (value, desiredType) { + return new TypeError("Cannot convert " + type(value).toLowerCase() + " to " + desiredType); +} + +export default _makeTypeErrorFor; diff --git a/src/privates/_merge.js b/src/privates/_merge.js new file mode 100644 index 0000000..2d23cb8 --- /dev/null +++ b/src/privates/_merge.js @@ -0,0 +1,22 @@ +import forEach from "../core/forEach"; +import reduce from "../core/reduce"; + +/** + * Merges the received objects using the provided function to retrieve their keys. + * @private + * @param {Function} getKeys + * @param {Object} a + * @param {Object} b + * @returns {Function} + */ +function _merge (getKeys, a, b) { + return reduce([a, b], function (result, source) { + forEach(getKeys(source), function (key) { + result[key] = source[key]; + }); + + return result; + }, {}); +} + +export default _merge; diff --git a/src/privates/_pairsFrom.js b/src/privates/_pairsFrom.js new file mode 100644 index 0000000..85fc53c --- /dev/null +++ b/src/privates/_pairsFrom.js @@ -0,0 +1,17 @@ +import map from "../core/map"; +import _curry2 from "./_curry2"; +import _keyToPairIn from "./_keyToPairIn"; + +/** + * Using the provided function to retrieve the keys, builds a new function + * expecting an object to create a list of key / value pairs. + * @private + * @function + * @param {Function} getKeys + * @returns {Function} + */ +var _pairsFrom = _curry2(function (getKeys, obj) { + return map(getKeys(obj), _keyToPairIn(obj)); +}); + +export default _pairsFrom; diff --git a/src/privates/_repeat.js b/src/privates/_repeat.js new file mode 100644 index 0000000..bf0d6a9 --- /dev/null +++ b/src/privates/_repeat.js @@ -0,0 +1,18 @@ +/** + * A null-safe function to repeat the source string the desired amount of times. + * @private + * @param {String} source + * @param {Number} times + * @returns {String} + */ +function _repeat (source, times) { + var result = ""; + + for (var i = 0; i < times; i++) { + result += source; + } + + return result; +} + +export default _repeat; diff --git a/src/privates/_safeEnumerables.js b/src/privates/_safeEnumerables.js new file mode 100644 index 0000000..20d0687 --- /dev/null +++ b/src/privates/_safeEnumerables.js @@ -0,0 +1,18 @@ +/** + * Builds a list of the enumerable properties of an object. + * The function is null-safe, unlike the public one. + * @private + * @param {Object} obj + * @returns {String[]} + */ +function _safeEnumerables (obj) { + var result = []; + + for (var key in obj) { + result.push(key); + } + + return result; +} + +export default _safeEnumerables; diff --git a/src/privates/_safeKeys.js b/src/privates/_safeKeys.js new file mode 100644 index 0000000..9d79c79 --- /dev/null +++ b/src/privates/_safeKeys.js @@ -0,0 +1,12 @@ +import compose from "../core/compose"; + +/** + * A null-safe version of Object.keys. + * @private + * @function + * @param {Object} obj + * @returns {String[]} + */ +var _safeKeys = compose(Object.keys, Object); + +export default _safeKeys; diff --git a/src/privates/_search.js b/src/privates/_search.js new file mode 100644 index 0000000..9d612d1 --- /dev/null +++ b/src/privates/_search.js @@ -0,0 +1,13 @@ +import generic from "../core/generic"; + +/** + * A generic version of String.prototype.search + * @private + * @function + * @param {String} s + * @param {RegExp} pattern + * @return {Number} + */ +var _search = generic(String.prototype.search); + +export default _search; diff --git a/src/privates/_setIn.js b/src/privates/_setIn.js new file mode 100644 index 0000000..a68590a --- /dev/null +++ b/src/privates/_setIn.js @@ -0,0 +1,21 @@ +/** + * Sets, or creates, a property in a copy of the provided object to the desired value. + * @private + * @param {Object} source + * @param {String} key + * @param {*} value + * @returns {Object} + */ +function _setIn (source, key, value) { + var result = {}; + + for (var prop in source) { + result[prop] = source[prop]; + } + + result[key] = value; + + return result; +} + +export default _setIn; diff --git a/src/privates/_setIndex.js b/src/privates/_setIndex.js new file mode 100644 index 0000000..95d55f4 --- /dev/null +++ b/src/privates/_setIndex.js @@ -0,0 +1,26 @@ +import slice from "../core/slice"; +import _toNaturalIndex from "./_toNaturalIndex"; + +/** + * Sets an index in an array-like object.
+ * If provided with an updater function it will use it to update the current value, + * otherwise sets the index to the specified value. + * @private + * @param {ArrayLike} arrayLike + * @param {Number} idx + * @param {*} [value] + * @param {Function} [updater] + * @returns {Array} + */ +function _setIndex (arrayLike, idx, value, updater) { + var result = slice(arrayLike, 0, arrayLike.length); + var n = _toNaturalIndex(idx, result.length); + + if (n === n) { // eslint-disable-line no-self-compare + result[n] = arguments.length === 4 ? updater(arrayLike[n]) : value; + } + + return result; +} + +export default _setIndex; diff --git a/src/privates/_setPathIn.js b/src/privates/_setPathIn.js new file mode 100644 index 0000000..3703117 --- /dev/null +++ b/src/privates/_setPathIn.js @@ -0,0 +1,37 @@ +import isUndefined from "../core/isUndefined"; +import slice from "../core/slice"; +import _getPathKey from "./_getPathKey"; +import _isArrayIndex from "./_isArrayIndex"; +import _setIn from "./_setIn"; +import _setIndex from "./_setIndex"; + +/** + * Sets the object's property targeted by the given path to the desired value.
+ * Works with arrays and is able to set their indexes, even negative ones. + * @private + * @param {Object|Array} obj + * @param {String[]} parts + * @param {*} value + * @returns {Object|Array} + */ +function _setPathIn (obj, parts, value) { + var key = parts[0]; + var partsLen = parts.length; + var v; + + if (partsLen === 1) { + v = value; + } else { + var targetKey = _getPathKey(obj, key, false); + + v = _setPathIn( + isUndefined(targetKey) ? targetKey : obj[targetKey], + slice(parts, 1, partsLen), + value + ); + } + + return _isArrayIndex(obj, key) ? _setIndex(obj, key, v) : _setIn(obj, key, v); +} + +export default _setPathIn; diff --git a/src/privates/_sorter.js b/src/privates/_sorter.js new file mode 100644 index 0000000..958917c --- /dev/null +++ b/src/privates/_sorter.js @@ -0,0 +1,35 @@ +import identity from "../core/identity"; +import _comparer from "./_comparer"; + +/** + * Builds a sorting criterion. If the comparer function is missing, the default + * comparer will be used instead. + * @private + * @param {Function} reader + * @param {Boolean} isDescending + * @param {Function} [comparer] + * @returns {Sorter} + */ +function _sorter (reader, isDescending, comparer) { + if (typeof reader !== "function" || reader === identity) { + reader = null; + } + + if (typeof comparer !== "function") { + comparer = _comparer; + } + + return { + isDescending: isDescending === true, + compare: function (a, b) { + if (reader) { + a = reader(a); + b = reader(b); + } + + return comparer(a, b); + } + }; +} + +export default _sorter; diff --git a/src/privates/_tearFrom.js b/src/privates/_tearFrom.js new file mode 100644 index 0000000..5d46f3c --- /dev/null +++ b/src/privates/_tearFrom.js @@ -0,0 +1,23 @@ +import reduce from "../core/reduce"; +import _curry2 from "./_curry2"; + +/** + * Using the provided function to retrieve the keys of an object, builds + * a function expecting an object to create an array containing a list + * of the keys in its first index and the corresponding list of values + * in the second one. + * @private + * @function + * @param {Function} getKeys + * @returns {Function} + */ +var _tearFrom = _curry2(function (getKeys, obj) { + return reduce(getKeys(obj), function (result, key) { + result[0].push(key); + result[1].push(obj[key]); + + return result; + }, [[], []]); +}); + +export default _tearFrom; diff --git a/src/privates/_toArrayLength.js b/src/privates/_toArrayLength.js new file mode 100644 index 0000000..e974662 --- /dev/null +++ b/src/privates/_toArrayLength.js @@ -0,0 +1,15 @@ +import clamp from "../core/clamp"; +import { MAX_ARRAY_LENGTH } from "./_constants"; + +/** + * Converts a value to a valid array length, thus an integer within + * 0 and 232 - 1 (both included). + * @private + * @param {*} value + * @returns {Number} + */ +function _toArrayLength (value) { + return clamp(value, 0, MAX_ARRAY_LENGTH) >>> 0; +} + +export default _toArrayLength; diff --git a/src/privates/_toInteger.js b/src/privates/_toInteger.js new file mode 100644 index 0000000..f27c2d5 --- /dev/null +++ b/src/privates/_toInteger.js @@ -0,0 +1,19 @@ +/** + * Converts a value to an integer. + * @private + * @param {*} value + * @returns {Number} + */ +function _toInteger (value) { + var n = +value; + + if (n !== n) { // eslint-disable-line no-self-compare + return 0; + } else if (n % 1 === 0) { + return n; + } else { + return Math.floor(Math.abs(n)) * (n < 0 ? -1 : 1); + } +} + +export default _toInteger; diff --git a/src/privates/_toNaturalIndex.js b/src/privates/_toNaturalIndex.js new file mode 100644 index 0000000..d4fb1c5 --- /dev/null +++ b/src/privates/_toNaturalIndex.js @@ -0,0 +1,18 @@ +import _toInteger from "./_toInteger"; + +/** + * Checks if the given number, even negative, represents an array-like index + * within the provided length. If so returns its natural number equivalent.
+ * Returns NaN otherwise. + * @private + * @param {Number} idx + * @param {Number} len + * @returns {Number} + */ +function _toNaturalIndex (idx, len) { + idx = _toInteger(idx); + + return idx >= -len && idx < len ? idx < 0 ? idx + len : idx : NaN; +} + +export default _toNaturalIndex; diff --git a/src/privates/_toPathParts.js b/src/privates/_toPathParts.js new file mode 100644 index 0000000..64cb95b --- /dev/null +++ b/src/privates/_toPathParts.js @@ -0,0 +1,13 @@ +/** + * Splits a sting path using the provided separator and returns an array + * of path parts. + * @private + * @param {String} path + * @param {String} separator + * @returns {String[]} + */ +function _toPathParts (path, separator) { + return String(path).split(separator || "."); +} + +export default _toPathParts; diff --git a/src/privates/_unsafeKeyListFrom.js b/src/privates/_unsafeKeyListFrom.js new file mode 100644 index 0000000..b7d4c0f --- /dev/null +++ b/src/privates/_unsafeKeyListFrom.js @@ -0,0 +1,20 @@ +import _curry2 from "./_curry2"; +import isNil from "../core/isNil"; +import _makeTypeErrorFor from "./_makeTypeErrorFor"; + +/** + * Creates a non-null-safe version of the provided "getKeys" function. + * @private + * @function + * @param {Function} getKeys + * @returns {Function} + */ +var _unsafeKeyListFrom = _curry2(function (getKeys, obj) { + if (isNil(obj)) { + throw _makeTypeErrorFor(obj, "object"); + } + + return getKeys(obj); +}); + +export default _unsafeKeyListFrom; diff --git a/src/privates/_valuesFrom.js b/src/privates/_valuesFrom.js new file mode 100644 index 0000000..f14c947 --- /dev/null +++ b/src/privates/_valuesFrom.js @@ -0,0 +1,18 @@ +import map from "../core/map"; +import _curry2 from "./_curry2"; + +/** + * Using the provided function to retrieve the keys of an object, builds + * a function expecting an object to create the list of values for such keys. + * @private + * @function + * @param {Function} getKeys + * @returns {Function} + */ +var _valuesFrom = _curry2(function (getKeys, obj) { + return map(getKeys(obj), function (key) { + return obj[key]; + }); +}); + +export default _valuesFrom; diff --git a/src/sort.js b/src/sort.js deleted file mode 100644 index 265c508..0000000 --- a/src/sort.js +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Returns a [stably]{@link https://en.wikipedia.org/wiki/Sorting_algorithm#Stability} sorted - * copy of an array-like object using the given criteria.
- * Sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, but you - * can also pass simple "reader" functions and default ascending sorters will be built for you.
- * A "reader" is a function that evaluates the array element and supplies the value to be used - * in the comparison.
- * Please note that if the arguments received by the default comparer aren't of the same type, - * they will be compared as strings. - * - * @example Stable sort: - * var persons = [ - * {"name": "John", "surname" :"Doe"}, - * {"name": "Mario", "surname": "Rossi"}, - * {"name": "John", "surname" :"Moe"}, - * {"name": "Jane", "surname": "Foe"} - * ]; - * - * var personsByName = _.sort(persons, [_.getKey("name")]); - * - * // personsByName holds: - * // [ - * // {"name": "Jane", "surname": "Foe"}, - * // {"name": "John", "surname" :"Doe"}, - * // {"name": "John", "surname" :"Moe"}, - * // {"name": "Mario", "surname": "Rossi"} - * // ] - * - * @example Stable multi-sort: - * var personsByNameAscSurnameDesc = _.sort(persons, [ - * _.getKey("name"), - * _.sorterDesc(_.getKey("surname")) - * ]); - * - * // personsByNameAscSurnameDesc holds: - * // [ - * // {"name": "Jane", "surname": "Foe"}, - * // {"name": "John", "surname" :"Moe"}, - * // {"name": "John", "surname" :"Doe"}, - * // {"name": "Mario", "surname": "Rossi"} - * // ] - * - * @example Using custom comparers: - * var localeSorter = new Intl.Collator("it"); - * var chars = ["a", "è", "à", "é", "c", "b", "e"]; - * - * _.sort(chars, [localeSorter]) // => ["a", "à", "b", "c", "e", "é", "è"] - * - * var localeSorterDesc = _.sorterDesc(_.identity, localeSorter.compare); - * - * _.sort(chars, [localeSorterDesc]) // => ["è", "é", "e", "c", "b", "à", "a"] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} - * @since 0.15.0 - * @param {ArrayLike} arrayLike - * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - * @returns {Array} - */ -function sort (arrayLike, sorters) { - var criteria = _makeCriteria(sorters); - var len = _toArrayLength(arrayLike.length); - var result = Array(len); - - for (var i = 0; i < len; i++) { - result[i] = {value: arrayLike[i], index: i}; - } - - result.sort(_compareWith(criteria)); - - for (i = 0; i < len; i++) { - result[i] = result[i].value; - } - - return result; -} - -/** - * Inserts an element in a copy of a sorted array respecting the sort order. - * @example With simple values: - * _.sortedInsert([], 1) // => [1] - * _.sortedInsert([2, 4, 6], 5) // => [2, 4, 5, 6] - * _.sortedInsert([4, 2, 1], 3, _.sorterDesc()) // => [4, 3, 2, 1] - * - * @example With complex values: - * var persons = [ - * {"name": "jane", "surname": "doe"}, - * {"name": "John", "surname": "Doe"}, - * {"name": "Mario", "surname": "Rossi"} - * ]; - * - * var getLowerCaseName = _.compose( - * _.invoker("toLowerCase"), - * _.getKey("name") - * ); - * - * var result = _.sortedInsert( - * persons, - * {"name": "marco", "surname": "Rossi"}, - * getLowerCaseName - * ); - * - * // `result` holds: - * // [ - * // {"name": "jane", "surname": "doe"}, - * // {"name": "John", "surname": "Doe"}, - * // {"name": "marco", "surname": "Rossi"}, - * // {"name": "Mario", "surname": "Rossi"} - * // ] - * - * @memberof module:lamb - * @category Array - * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} - * @see {@link module:lamb.insert|insert}, {@link module:lamb.insertAt|insertAt} to insert the element - * at a specific index - * @since 0.27.0 - * @param {ArrayLike} arrayLike - * @param {*} element - * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - The sorting criteria - * used to sort the array. - * @returns {Array} - */ -function sortedInsert (arrayLike, element, sorters) { - var result = slice(arrayLike, 0, arrayLike.length); - - if (arguments.length === 1) { - return result; - } - - var criteria = _makeCriteria(sorters); - var idx = _getInsertionIndex(result, element, _compareWith(criteria), 0, result.length); - - result.splice(idx, 0, element); - - return result; -} - -/** - * Creates an ascending sort criterion with the provided reader and - * comparer.
- * See {@link module:lamb.sort|sort} for various examples. - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorterDesc|sorterDesc} - * @since 0.1.0 - * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a - * simple value from a complex one. The function should evaluate the array element and supply the - * value to be passed to the comparer. - * @param {Function} [comparer] An optional custom comparer function. - * @returns {Sorter} - */ -var sorter = partial(_sorter, [_, false, _]); - -/** - * Creates a descending sort criterion with the provided reader and - * comparer.
- * See {@link module:lamb.sort|sort} for various examples. - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.sortedInsert|sortedInsert} - * @see {@link module:lamb.sort|sort}, {@link module:lamb.sortWith|sortWith} - * @see {@link module:lamb.sorter|sorter} - * @since 0.15.0 - * @param {Function} [reader={@link module:lamb.identity|identity}] A function meant to generate a - * simple value from a complex one. The function should evaluate the array element and supply the - * value to be passed to the comparer. - * @param {Function} [comparer] An optional custom comparer function. - * @returns {Sorter} - */ -var sorterDesc = partial(_sorter, [_, true, _]); - -/** - * Builds a partial application of {@link module:lamb.sort|sort} using the provided criteria. - * The returned function expects the array-like object to sort. - * As usual, sorting criteria are built using Lamb's {@link module:lamb.sorter|sorter} function, - * but you can also pass simple "reader" functions and default ascending sorters will be built.
- * A "reader" is a function that evaluates the array element and supplies the value to be used in - * the comparison.
- * See {@link module:lamb.sort|sort} for more examples. - * - * @example - * var sortAsNumbers = _.sortWith([parseFloat]); - * var weights = ["2 Kg", "10 Kg", "1 Kg", "7 Kg"]; - * - * sortAsNumbers(weights) // => ["1 Kg", "2 Kg", "7 Kg", "10 Kg"] - * - * @memberof module:lamb - * @category Array - * @function - * @see {@link module:lamb.sort|sort} - * @see {@link module:lamb.sorter|sorter}, {@link module:lamb.sorterDesc|sorterDesc} - * @since 0.15.0 - * @param {Sorter[]|Function[]} [sorters=[{@link module:lamb.sorter|sorter()}]] - * @returns {Function} - */ -var sortWith = _curry2(sort, true); - -lamb.sort = sort; -lamb.sortedInsert = sortedInsert; -lamb.sorter = sorter; -lamb.sorterDesc = sorterDesc; -lamb.sortWith = sortWith; diff --git a/src/string.js b/src/string.js deleted file mode 100644 index 782086e..0000000 --- a/src/string.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Pads a string to the desired length with the given char starting from the beginning of the string. - * @example - * _.padLeft("foo", "-", 0) // => "foo" - * _.padLeft("foo", "-", -1) // => "foo" - * _.padLeft("foo", "-", 5) // => "--foo" - * _.padLeft("foo", "-", 3) // => "foo" - * _.padLeft("foo", "ab", 7) // => "aaaafoo" - * _.padLeft("foo", "", 5) // => "foo" - * _.padLeft("", "-", 5) // => "-----" - * - * @memberof module:lamb - * @category String - * @see {@link module:lamb.padRight|padRight} - * @since 0.1.0 - * @param {String} source - * @param {String} char - The padding char. If a string is passed only the first char is used. - * @param {Number} len - * @returns {String} - */ -function padLeft (source, char, len) { - return _getPadding(source, char, len) + source; -} - -/** - * Pads a string to the desired length with the given char starting from the end of the string. - * @example - * _.padRight("foo", "-", 0) // => "foo" - * _.padRight("foo", "-", -1) // => "foo" - * _.padRight("foo", "-", 5) // => "foo--" - * _.padRight("foo", "-", 3) // => "foo" - * _.padRight("foo", "ab", 7) // => "fooaaaa" - * _.padRight("foo", "", 5) // => "foo" - * _.padRight("", "-", 5) // => "-----" - * - * @memberof module:lamb - * @category String - * @see {@link module:lamb.padLeft|padLeft} - * @since 0.1.0 - * @param {String} source - * @param {String} char - The padding char. If a string is passed only the first char is used. - * @param {Number} len - * @returns {String} - */ -function padRight (source, char, len) { - return source + _getPadding(source, char, len); -} - -/** - * Builds a new string by repeating the source string the desired amount of times.
- * Note that unlike the current ES6 proposal for - * [String.prototype.repeat]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat}, - * this function doesn't throw a RangeError if times is negative, - * but returns an empty string instead. - * @example - * _.repeat("Hello", -1) // => "" - * _.repeat("Hello", 1) // => "Hello" - * _.repeat("Hello", 3) // => "HelloHelloHello" - * - * @memberof module:lamb - * @category String - * @since 0.1.0 - * @param {String} source - * @param {Number} times - * @returns {String} - */ -function repeat (source, times) { - if (isNil(source)) { - throw _makeTypeErrorFor(source, "string"); - } - - return _repeat(source, Math.floor(times)); -} - -/** - * Builds a predicate expecting a string to test against the given regular expression pattern. - * @example - * var hasNumbersOnly = _.testWith(/^\d+$/); - * - * hasNumbersOnly("123") // => true - * hasNumbersOnly("123 Kg") // => false - * - * @memberof module:lamb - * @category String - * @since 0.1.0 - * @param {RegExp} pattern - * @returns {Function} - */ -function testWith (pattern) { - return function (s) { - return _search(s, pattern) !== -1; - }; -} - -lamb.padLeft = padLeft; -lamb.padRight = padRight; -lamb.repeat = repeat; -lamb.testWith = testWith; diff --git a/src/string/__tests__/padLeft.spec.js b/src/string/__tests__/padLeft.spec.js new file mode 100644 index 0000000..ed03e39 --- /dev/null +++ b/src/string/__tests__/padLeft.spec.js @@ -0,0 +1,83 @@ +import * as lamb from "../.."; +import { zeroesAsIntegers } from "../../__tests__/commons"; + +describe("padLeft / padRight", function () { + var source = "foo"; + + it("should pad a string to the given length with the provided char", function () { + expect(lamb.padLeft(source, "-", 5)).toBe("--foo"); + expect(lamb.padLeft("", "-", 5)).toBe("-----"); + expect(lamb.padRight(source, "-", 5)).toBe("foo--"); + expect(lamb.padRight("", "-", 5)).toBe("-----"); + }); + + it("should return a copy of the original string if the given length is lesser or equal to the string's length or if it's not a number", function () { + expect(lamb.padLeft(source, "-", 0)).toBe("foo"); + expect(lamb.padLeft(source, "-", -1)).toBe("foo"); + expect(lamb.padLeft(source, "-", 3)).toBe("foo"); + expect(lamb.padRight(source, "-", 0)).toBe("foo"); + expect(lamb.padRight(source, "-", -1)).toBe("foo"); + expect(lamb.padRight(source, "-", 3)).toBe("foo"); + + zeroesAsIntegers.forEach(function (value) { + expect(lamb.padLeft(source, "-", value)).toBe("foo"); + expect(lamb.padRight(source, "-", value)).toBe("foo"); + }); + }); + + it("should use only the first character of the string passed as padding char", function () { + expect(lamb.padLeft(source, "ab", 7)).toBe("aaaafoo"); + expect(lamb.padRight(source, "ab", 7)).toBe("fooaaaa"); + }); + + it("should not do any padding if the padding char is an empty string", function () { + expect(lamb.padLeft(source, "", 5)).toBe("foo"); + expect(lamb.padRight(source, "", 5)).toBe("foo"); + }); + + it("should convert to string values received as padding characters", function () { + var d = new Date(); + var dChar = String(d)[0]; + var values = [null, void 0, [7, 8], /foo/, 1, function () {}, NaN, true, d, { a: 2 }]; + var resultsA = [ + "nnfoo", "uufoo", "77foo", "//foo", "11foo", "fffoo", + "NNfoo", "ttfoo", dChar + dChar + "foo", "[[foo" + ]; + var resultsB = [ + "foonn", "foouu", "foo77", "foo//", "foo11", "fooff", + "fooNN", "foott", "foo" + dChar + dChar, "foo[[" + ]; + + values.forEach(function (value, idx) { + expect(lamb.padLeft(source, value, 5)).toBe(resultsA[idx]); + expect(lamb.padRight(source, value, 5)).toBe(resultsB[idx]); + }); + }); + + it("should throw an exception when the source is `null` or `undefined` and when it's called without arguments", function () { + expect(function () { lamb.padLeft(null, "-", 10); }).toThrow(); + expect(function () { lamb.padLeft(void 0, "-", 10); }).toThrow(); + expect(function () { lamb.padRight(null, "-", 10); }).toThrow(); + expect(function () { lamb.padRight(void 0, "-", 10); }).toThrow(); + expect(lamb.padLeft).toThrow(); + expect(lamb.padRight).toThrow(); + }); + + it("should convert to string every other value passed as source", function () { + var d = new Date(); + var values = [{ a: 2 }, [1, 2], /foo/, 1, function () {}, NaN, true, d]; + var resultsA = [ + "[object Object]", "----1,2", "--/foo/", "------1", + "function () {}", "----NaN", "---true", String(d) + ]; + var resultsB = [ + "[object Object]", "1,2----", "/foo/--", "1------", + "function () {}", "NaN----", "true---", String(d) + ]; + + values.forEach(function (value, idx) { + expect(lamb.padLeft(value, "-", 7)).toBe(resultsA[idx]); + expect(lamb.padRight(value, "-", 7)).toBe(resultsB[idx]); + }); + }); +}); diff --git a/src/string/__tests__/repeat.spec.js b/src/string/__tests__/repeat.spec.js new file mode 100644 index 0000000..04a4881 --- /dev/null +++ b/src/string/__tests__/repeat.spec.js @@ -0,0 +1,58 @@ +import * as lamb from "../.."; +import { zeroesAsIntegers } from "../../__tests__/commons"; + +describe("repeat", function () { + var source = "foo"; + + it("should build a new string by repeating the source string the desired amount of times", function () { + expect(lamb.repeat(source, 0)).toBe(""); + expect(lamb.repeat(source, 1)).toBe("foo"); + expect(lamb.repeat(source, 3)).toBe("foofoofoo"); + }); + + it("should consider negative values in the `times` parameter as zeroes", function () { + expect(lamb.repeat(source, -1)).toBe(""); + expect(lamb.repeat(source, -99)).toBe(""); + }); + + it("should floor floating point values passed as the `times` parameter", function () { + expect(lamb.repeat(source, 3.2)).toBe("foofoofoo"); + expect(lamb.repeat(source, 3.8)).toBe("foofoofoo"); + }); + + it("should convert to integer every other value passed as the `times` parameter", function () { + zeroesAsIntegers.forEach(function (value) { + expect(lamb.repeat("foo", value)).toBe(""); + }); + + expect(lamb.repeat("foo")).toBe(""); + expect(lamb.repeat("foo", true)).toBe("foo"); + expect(lamb.repeat("foo", "2.5")).toBe("foofoo"); + expect(lamb.repeat("foo", new Date(3))).toBe("foofoofoo"); + }); + + it("should throw an exception when the source is `null` or `undefined` and when it's called without arguments", function () { + expect(function () { lamb.repeat(null, 10); }).toThrow(); + expect(function () { lamb.repeat(void 0, 10); }).toThrow(); + expect(lamb.repeat).toThrow(); + }); + + it("should convert to string every other value passed as source", function () { + var d = new Date(); + var values = [{ a: 2 }, [1, 2], /foo/, 1, function () {}, NaN, true, d]; + var results = [ + "[object Object][object Object][object Object]", + "1,21,21,2", + "/foo//foo//foo/", + "111", + "function () {}function () {}function () {}", + "NaNNaNNaN", + "truetruetrue", + String(d) + String(d) + String(d) + ]; + + values.forEach(function (value, idx) { + expect(lamb.repeat(value, 3)).toBe(results[idx]); + }); + }); +}); diff --git a/src/string/__tests__/testWith.spec.js b/src/string/__tests__/testWith.spec.js new file mode 100644 index 0000000..1bdaf27 --- /dev/null +++ b/src/string/__tests__/testWith.spec.js @@ -0,0 +1,71 @@ +import * as lamb from "../.."; + +describe("testWith", function () { + it("should build a predicate accepting a string value to be tested against the pattern", function () { + var hasNumbersOnly = lamb.testWith(/^\d+$/); + var hasOnlyLowerCaseLetters = lamb.testWith(new RegExp("^[a-z]+$")); + + expect(hasNumbersOnly("123a")).toBe(false); + expect(hasNumbersOnly("123")).toBe(true); + expect(hasOnlyLowerCaseLetters("aBc")).toBe(false); + expect(hasOnlyLowerCaseLetters("abc")).toBe(true); + }); + + it("should be safe to reuse the function even when the \"global\" flag is used in the pattern", function () { + var re = /^\d+$/g; + var hasNumbersOnly = lamb.testWith(re); + + expect(hasNumbersOnly("123")).toBe(true); + expect(re.lastIndex).toBe(0); + expect(hasNumbersOnly("123")).toBe(true); + expect(re.lastIndex).toBe(0); + }); + + it("should convert every value passed as `pattern` to a RegExp", function () { + var d = new Date(); + var values = [null, void 0, { a: 2 }, [1, 2], 1, function () {}, NaN, true, d]; + var matches = [ + "null", + "", + "[object Object]", + "1,2", + "1", + "function {}", + "NaN", + "true", + RegExp(d).source.replace(/[+()]/g, "") + ]; + + values.forEach(function (value, idx) { + expect(lamb.testWith(value)(matches[idx])).toBe(true); + }); + + expect(lamb.testWith()("")).toBe(true); + }); + + it("should throw an exception when the source string is `nil` or is missing", function () { + expect(function () { lamb.testWith(/asd/)(null); }).toThrow(); + expect(function () { lamb.testWith(/asd/)(void 0); }).toThrow(); + expect(lamb.testWith(/asd/)).toThrow(); + expect(lamb.testWith()).toThrow(); + }); + + it("should convert to string every other value passed as `source`", function () { + var d = new Date(); + var values = [{ a: 2 }, [1, 2], /foo/, 1, function () {}, NaN, true, d]; + var patterns = [ + /\[object Object\]/, + /1,2/, + /foo/, + /1/, + /function \(\) \{\}/, + /NaN/, + /true/, + RegExp(String(d).replace(/([+()])/g, "\\$1")) + ]; + + values.forEach(function (value, idx) { + expect(lamb.testWith(patterns[idx])(value)).toBe(true); + }); + }); +}); diff --git a/src/string/padLeft.js b/src/string/padLeft.js new file mode 100644 index 0000000..07dcf4f --- /dev/null +++ b/src/string/padLeft.js @@ -0,0 +1,27 @@ +import _getPadding from "../privates/_getPadding"; + +/** + * Pads a string to the desired length with the given char starting from the beginning of the string. + * @example + * _.padLeft("foo", "-", 0) // => "foo" + * _.padLeft("foo", "-", -1) // => "foo" + * _.padLeft("foo", "-", 5) // => "--foo" + * _.padLeft("foo", "-", 3) // => "foo" + * _.padLeft("foo", "ab", 7) // => "aaaafoo" + * _.padLeft("foo", "", 5) // => "foo" + * _.padLeft("", "-", 5) // => "-----" + * + * @memberof module:lamb + * @category String + * @see {@link module:lamb.padRight|padRight} + * @since 0.1.0 + * @param {String} source + * @param {String} char - The padding char. If a string is passed only the first char is used. + * @param {Number} len + * @returns {String} + */ +function padLeft (source, char, len) { + return _getPadding(source, char, len) + source; +} + +export default padLeft; diff --git a/src/string/padRight.js b/src/string/padRight.js new file mode 100644 index 0000000..f3596f6 --- /dev/null +++ b/src/string/padRight.js @@ -0,0 +1,27 @@ +import _getPadding from "../privates/_getPadding"; + +/** + * Pads a string to the desired length with the given char starting from the end of the string. + * @example + * _.padRight("foo", "-", 0) // => "foo" + * _.padRight("foo", "-", -1) // => "foo" + * _.padRight("foo", "-", 5) // => "foo--" + * _.padRight("foo", "-", 3) // => "foo" + * _.padRight("foo", "ab", 7) // => "fooaaaa" + * _.padRight("foo", "", 5) // => "foo" + * _.padRight("", "-", 5) // => "-----" + * + * @memberof module:lamb + * @category String + * @see {@link module:lamb.padLeft|padLeft} + * @since 0.1.0 + * @param {String} source + * @param {String} char - The padding char. If a string is passed only the first char is used. + * @param {Number} len + * @returns {String} + */ +function padRight (source, char, len) { + return source + _getPadding(source, char, len); +} + +export default padRight; diff --git a/src/string/repeat.js b/src/string/repeat.js new file mode 100644 index 0000000..1aa594d --- /dev/null +++ b/src/string/repeat.js @@ -0,0 +1,31 @@ +import isNil from "../core/isNil"; +import _makeTypeErrorFor from "../privates/_makeTypeErrorFor"; +import _repeat from "../privates/_repeat"; + +/** + * Builds a new string by repeating the source string the desired amount of times.
+ * Note that unlike the current ES6 proposal for + * [String.prototype.repeat]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat}, + * this function doesn't throw a RangeError if times is negative, + * but returns an empty string instead. + * @example + * _.repeat("Hello", -1) // => "" + * _.repeat("Hello", 1) // => "Hello" + * _.repeat("Hello", 3) // => "HelloHelloHello" + * + * @memberof module:lamb + * @category String + * @since 0.1.0 + * @param {String} source + * @param {Number} times + * @returns {String} + */ +function repeat (source, times) { + if (isNil(source)) { + throw _makeTypeErrorFor(source, "string"); + } + + return _repeat(source, Math.floor(times)); +} + +export default repeat; diff --git a/src/string/testWith.js b/src/string/testWith.js new file mode 100644 index 0000000..9e107e2 --- /dev/null +++ b/src/string/testWith.js @@ -0,0 +1,23 @@ +import _search from "../privates/_search"; + +/** + * Builds a predicate expecting a string to test against the given regular expression pattern. + * @example + * var hasNumbersOnly = _.testWith(/^\d+$/); + * + * hasNumbersOnly("123") // => true + * hasNumbersOnly("123 Kg") // => false + * + * @memberof module:lamb + * @category String + * @since 0.1.0 + * @param {RegExp} pattern + * @returns {Function} + */ +function testWith (pattern) { + return function (s) { + return _search(s, pattern) !== -1; + }; +} + +export default testWith; diff --git a/src/type.js b/src/type.js deleted file mode 100644 index 54bf524..0000000 --- a/src/type.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Accepts a constructor and builds a predicate expecting an object, - * which will be tested to verify whether the prototype of the constructor - * is in its prototype chain.
- * Wraps in a convenient way the native - * [instanceof]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof} operator. - * @example - * function SomeObjA () {} - * - * var a = new SomeObjA(); - * var sObj = new String("foo"); - * var s = "foo"; - * - * _.isInstanceOf(Object)(a) // => true - * _.isInstanceOf(SomeObjA)(a) // => true - * - * _.isInstanceOf(Object)(sObj) // => true - * _.isInstanceOf(String)(sObj) // => true - * - * _.isInstanceOf(Object)(s) // => false - * _.isInstanceOf(String)(s) // => false - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isType|isType} - * @since 0.47.0 - * @param {*} constructor - * @returns {Function} - */ -function isInstanceOf (constructor) { - return function (obj) { - return obj instanceof constructor; - }; -} - -/** - * Verifies if a value is null or undefined. - * @example - * _.isNil(NaN) // => false - * _.isNil({}) // => false - * _.isNil(null) // => true - * _.isNil(void 0) // => true - * _.isNil() // => true - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isNull|isNull} - * @see {@link module:lamb.isUndefined|isUndefined} - * @since 0.1.0 - * @param {*} value - * @returns {Boolean} - */ -function isNil (value) { - return isNull(value) || isUndefined(value); -} - -/** - * Verifies if a value is null. - * @example - * _.isNull(null) // => true - * _.isNull(void 0) // => false - * _.isNull(false) // => false - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isNil|isNil} if you want to check for undefined too. - * @since 0.1.0 - * @param {*} value - * @returns {Boolean} - */ -function isNull (value) { - return value === null; -} - -/** - * Builds a predicate that expects a value to check against the specified type. - * @example - * var isString = _.isType("String"); - * - * isString("Hello") // => true - * isString(new String("Hi")) // => true - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.type|type} - * @since 0.1.0 - * @param {String} typeName - * @returns {Function} - */ -function isType (typeName) { - return function (value) { - return type(value) === typeName; - }; -} - -/** - * Verifies if a value is undefined. - * @example - * _.isUndefined(null) // => false - * _.isUndefined(void 0) // => true - * _.isUndefined(false) // => false - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isNil|isNil} if you want to check for null too. - * @since 0.1.0 - * @param {*} value - * @returns {Boolean} - */ -function isUndefined (value) { - return value === void 0; -} - -/** - * Retrieves the "type tag" from the given value. - * @example - * var x = 5; - * var y = new Number(5); - * - * typeof x // => "number" - * typeof y // => "object" - * _.type(x) // => "Number" - * _.type(y) // => "Number" - * - * _.type(Object.prototype.toString) // => "Function" - * _.type(/a/) // => "RegExp" - * - * @memberof module:lamb - * @category Type - * @see {@link module:lamb.isType|isType} - * @since 0.9.0 - * @param {*} value - * @returns {String} - */ -function type (value) { - return _objectProto.toString.call(value).slice(8, -1); -} - -lamb.isInstanceOf = isInstanceOf; -lamb.isNil = isNil; -lamb.isNull = isNull; -lamb.isType = isType; -lamb.isUndefined = isUndefined; -lamb.type = type; diff --git a/src/type/__tests__/isInstanceOf.spec.js b/src/type/__tests__/isInstanceOf.spec.js new file mode 100644 index 0000000..eaf03e1 --- /dev/null +++ b/src/type/__tests__/isInstanceOf.spec.js @@ -0,0 +1,39 @@ +import * as lamb from "../.."; +import { nonFunctions } from "../../__tests__/commons"; + +describe("isInstanceOf", function () { + it("should build a predicate to check if an object is an instance of the given constructor", function () { + function SomeObjA () {} + function SomeObjB () {} + + var a = new SomeObjA(); + var b = new SomeObjB(); + + expect(lamb.isInstanceOf(SomeObjA)(a)).toBe(true); + expect(lamb.isInstanceOf(Object)(a)).toBe(true); + expect(lamb.isInstanceOf(SomeObjB)(a)).toBe(false); + + expect(lamb.isInstanceOf(SomeObjA)(b)).toBe(false); + + SomeObjB.prototype = new SomeObjA(); + var c = new SomeObjB(); + + expect(lamb.isInstanceOf(SomeObjA)(b)).toBe(false); + expect(lamb.isInstanceOf(SomeObjA)(c)).toBe(true); + + expect(lamb.isInstanceOf(Object)({})).toBe(true); + expect(lamb.isInstanceOf(Object)(Object.create(null))).toBe(false); + expect(lamb.isInstanceOf(String)("foo")).toBe(false); + expect(lamb.isInstanceOf(Object)("foo")).toBe(false); + expect(lamb.isInstanceOf(String)(new String("foo"))).toBe(true); + expect(lamb.isInstanceOf(Object)(new String("foo"))).toBe(true); + }); + + it("should build a predicate throwing an exception if the constructor isn't a function or is missing", function () { + nonFunctions.forEach(function (value) { + expect(lamb.isInstanceOf(value)).toThrow(); + }); + + expect(lamb.isInstanceOf()).toThrow(); + }); +}); diff --git a/src/type/__tests__/isType.spec.js b/src/type/__tests__/isType.spec.js new file mode 100644 index 0000000..40dd1ff --- /dev/null +++ b/src/type/__tests__/isType.spec.js @@ -0,0 +1,20 @@ +import * as lamb from "../.."; +import { valuesList } from "../../__tests__/commons"; + +describe("isType", function () { + it("should build a predicate that expects a value to check against the specified type", function () { + var isArray = lamb.isType("Array"); + var isFunction = lamb.isType("Function"); + + expect(isArray([1, 2, 3])).toBe(true); + expect(isFunction(function () {})).toBe(true); + expect(isArray({})).toBe(false); + expect(isFunction(new Date())).toBe(false); + }); + + it("should build a function returning false for every value if called without arguments", function () { + valuesList.forEach(function (value) { + expect(lamb.isType()(value)).toBe(false); + }); + }); +}); diff --git a/src/type/isInstanceOf.js b/src/type/isInstanceOf.js new file mode 100644 index 0000000..30103ce --- /dev/null +++ b/src/type/isInstanceOf.js @@ -0,0 +1,36 @@ +/** + * Accepts a constructor and builds a predicate expecting an object, + * which will be tested to verify whether the prototype of the constructor + * is in its prototype chain.
+ * Wraps in a convenient way the native + * [instanceof]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof} operator. + * @example + * function SomeObjA () {} + * + * var a = new SomeObjA(); + * var sObj = new String("foo"); + * var s = "foo"; + * + * _.isInstanceOf(Object)(a) // => true + * _.isInstanceOf(SomeObjA)(a) // => true + * + * _.isInstanceOf(Object)(sObj) // => true + * _.isInstanceOf(String)(sObj) // => true + * + * _.isInstanceOf(Object)(s) // => false + * _.isInstanceOf(String)(s) // => false + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.isType|isType} + * @since 0.47.0 + * @param {*} constructor + * @returns {Function} + */ +function isInstanceOf (constructor) { + return function (obj) { + return obj instanceof constructor; + }; +} + +export default isInstanceOf; diff --git a/src/type/isType.js b/src/type/isType.js new file mode 100644 index 0000000..58361f2 --- /dev/null +++ b/src/type/isType.js @@ -0,0 +1,24 @@ +import type from "../core/type"; + +/** + * Builds a predicate that expects a value to check against the specified type. + * @example + * var isString = _.isType("String"); + * + * isString("Hello") // => true + * isString(new String("Hi")) // => true + * + * @memberof module:lamb + * @category Type + * @see {@link module:lamb.type|type} + * @since 0.1.0 + * @param {String} typeName + * @returns {Function} + */ +function isType (typeName) { + return function (value) { + return type(value) === typeName; + }; +} + +export default isType; diff --git a/test/custom_equalities.js b/test/custom_equalities.js deleted file mode 100644 index 43e7697..0000000 --- a/test/custom_equalities.js +++ /dev/null @@ -1,33 +0,0 @@ -"use strict"; - -function isSparseArray (array) { - return Array.isArray(array) && Object.keys(array).filter(function (v) { - return String(v >>> 0) === v; - }).length !== array.length; -} - -function isSparseArrayCheckNeeded (a, b) { - return isSparseArray(a) && Array.isArray(b) || isSparseArray(b) && Array.isArray(a); -} - -function sparseArrayEquality (a, b) { // eslint-disable-line consistent-return - if (isSparseArrayCheckNeeded(a, b)) { - var aLen = a.length; - - if (aLen !== b.length) { - return false; - } - - for (var i = 0; i < aLen; i++) { - if (i in a ^ i in b) { - return false; - } else if (a[i] !== b[i]) { - return false; - } - } - - return true; - } -} - -module.exports = {sparseArrayEquality: sparseArrayEquality}; diff --git a/test/spec/accessorsSpec.js b/test/spec/accessorsSpec.js deleted file mode 100644 index a567970..0000000 --- a/test/spec/accessorsSpec.js +++ /dev/null @@ -1,1566 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; -var sparseArrayEquality = commons.equalities.sparseArrayEquality; - -var nonStrings = commons.vars.nonStrings; -var nonStringsAsStrings = commons.vars.nonStringsAsStrings; -var nonFunctions = commons.vars.nonFunctions; -var wannabeEmptyArrays = commons.vars.wannabeEmptyArrays; -var wannabeEmptyObjects = commons.vars.wannabeEmptyObjects; -var zeroesAsIntegers = commons.vars.zeroesAsIntegers; - -describe("lamb.accessors", function () { - beforeEach(function () { - jasmine.addCustomEqualityTester(sparseArrayEquality); - }); - - describe("Array accessors", function () { - var arr = [1, 2, 3, 4, 5]; - var arrCopy = arr.slice(); - var s = "abcde"; - var sparseArr = [, , 3, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays - var sparseArrCopy = sparseArr.slice(); - var sparseArrAsDense = [void 0, void 0, 3, void 0]; - - afterEach(function () { - expect(arr).toEqual(arrCopy); - expect(sparseArr).toEqual(sparseArrCopy); - }); - - describe("getIndex / getAt / head / last", function () { - it("should retrieve the element at the given index in an array-like object", function () { - var getThird = lamb.getAt(2); - - expect(lamb.getIndex(arr, 1)).toBe(2); - expect(lamb.getIndex(s, 1)).toBe("b"); - expect(getThird(arr)).toBe(3); - expect(getThird(s)).toBe("c"); - expect(lamb.head(arr)).toBe(1); - expect(lamb.head(s)).toBe("a"); - expect(lamb.last(arr)).toBe(5); - expect(lamb.last(s)).toBe("e"); - }); - - it("should allow negative indexes", function () { - expect(lamb.getIndex(arr, -2)).toBe(4); - expect(lamb.getIndex(s, -2)).toBe("d"); - expect(lamb.getAt(-2)(arr)).toBe(4); - expect(lamb.getAt(-2)(s)).toBe("d"); - }); - - it("should work with sparse arrays", function () { - expect(lamb.getIndex(sparseArr, 2)).toBe(3); - expect(lamb.getIndex(sparseArr, -2)).toBe(3); - expect(lamb.getAt(2)(sparseArr)).toBe(3); - expect(lamb.getAt(-2)(sparseArr)).toBe(3); - expect(lamb.head(sparseArr)).toBe(void 0); - expect(lamb.last(sparseArr)).toBe(void 0); - }); - - it("should return `undefined` if the index is out of bounds", function () { - expect(lamb.getIndex(arr, -6)).toBeUndefined(); - expect(lamb.getIndex(arr, 66)).toBeUndefined(); - - expect(lamb.getAt(-6)(arr)).toBeUndefined(); - expect(lamb.getAt(66)(arr)).toBeUndefined(); - - expect(lamb.head([])).toBeUndefined(); - expect(lamb.last([])).toBeUndefined(); - }); - - it("should convert the `index` parameter to integer", function () { - zeroesAsIntegers.forEach(function (value) { - expect(lamb.getIndex(arr, value)).toBe(arr[0]); - expect(lamb.getAt(value)(arr)).toBe(arr[0]); - }); - - [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { - expect(lamb.getIndex(arr, value)).toBe(arr[1]); - expect(lamb.getAt(value)(arr)).toBe(arr[1]); - }); - - expect(lamb.getIndex(arr, new Date())).toBeUndefined(); - expect(lamb.getAt(new Date())(arr)).toBeUndefined(); - - expect(lamb.getIndex(arr)).toBe(arr[0]); - expect(lamb.getAt()(arr)).toBe(arr[0]); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.getIndex).toThrow(); - expect(lamb.getAt(1)).toThrow(); - expect(lamb.head).toThrow(); - expect(lamb.last).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.getIndex(null, 2); }).toThrow(); - expect(function () { lamb.getIndex(void 0, 2); }).toThrow(); - expect(function () { lamb.getAt(2)(null); }).toThrow(); - expect(function () { lamb.getAt(2)(void 0); }).toThrow(); - expect(function () { lamb.head(null); }).toThrow(); - expect(function () { lamb.last(void 0); }).toThrow(); - }); - - it("should return `undefined` for any other value", function () { - wannabeEmptyArrays.forEach(function (v) { - expect(lamb.getIndex(v, 2)).toBeUndefined(); - expect(lamb.getAt(2)(v)).toBeUndefined(); - expect(lamb.head(v)).toBeUndefined(); - expect(lamb.last(v)).toBeUndefined(); - }); - }); - }); - - describe("setIndex / setAt", function () { - it("should allow to set a value in a copy of the given array-like object", function () { - var r1 = [1, 2, 99, 4, 5]; - var r2 = ["z", "b", "c", "d", "e"]; - - expect(lamb.setIndex(arr, 2, 99)).toEqual(r1); - expect(lamb.setAt(2, 99)(arr)).toEqual(r1); - expect(lamb.setIndex(s, 0, "z")).toEqual(r2); - expect(lamb.setAt(0, "z")(s)).toEqual(r2); - }); - - it("should allow negative indexes", function () { - var newArr = lamb.setIndex(arr, -1, 99); - var newArr2 = lamb.setAt(-5, 99)(arr); - - expect(newArr).toEqual([1, 2, 3, 4, 99]); - expect(newArr2).toEqual([99, 2, 3, 4, 5]); - }); - - it("should always return dense arrays", function () { - var r1 = [void 0, void 0, 99, void 0]; - var r2 = [void 0, void 0, 3, 99]; - - expect(lamb.setIndex(sparseArr, 2, 99)).toEqual(r1); - expect(lamb.setIndex(sparseArr, -2, 99)).toEqual(r1); - expect(lamb.setIndex(sparseArr, -1, 99)).toEqual(r2); - expect(lamb.setIndex(sparseArr, 3, 99)).toEqual(r2); - expect(lamb.setAt(2, 99)(sparseArr)).toEqual(r1); - expect(lamb.setAt(-2, 99)(sparseArr)).toEqual(r1); - expect(lamb.setAt(-1, 99)(sparseArr)).toEqual(r2); - expect(lamb.setAt(3, 99)(sparseArr)).toEqual(r2); - }); - - it("should return an array copy of the array-like if the index is out of bounds", function () { - var newArr = lamb.setIndex(arr, 5, 99); - var newArr2 = lamb.setAt(-6, 99)(arr); - var newS = lamb.setAt(10, 99)(s); - var newSparseArr = lamb.setIndex(sparseArr, 5, 99); - var newSparseArr2 = lamb.setAt(5, 99)(sparseArr); - - expect(newArr).toEqual([1, 2, 3, 4, 5]); - expect(newArr2).toEqual([1, 2, 3, 4, 5]); - expect(newArr).not.toBe(arr); - expect(newArr2).not.toBe(arr); - expect(newS).toEqual(["a", "b", "c", "d", "e"]); - expect(newSparseArr).toEqual(sparseArrAsDense); - expect(newSparseArr).not.toBe(sparseArr); - expect(newSparseArr2).toEqual(sparseArrAsDense); - expect(newSparseArr2).not.toBe(sparseArr); - }); - - it("should convert the `index` parameter to integer", function () { - var r1 = [99, 2, 3, 4, 5]; - var r2 = [1, 99, 3, 4, 5]; - var r3 = [void 0, 2, 3, 4, 5]; - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.setIndex(arr, value, 99)).toEqual(r1); - expect(lamb.setAt(value, 99)(arr)).toEqual(r1); - }); - - [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { - expect(lamb.setIndex(arr, value, 99)).toEqual(r2); - expect(lamb.setAt(value, 99)(arr)).toEqual(r2); - }); - - expect(lamb.setIndex(arr, new Date(), 99)).toEqual(arr); - expect(lamb.setAt(new Date(), 99)(arr)).toEqual(arr); - - expect(lamb.setIndex(arr)).toEqual(r3); - expect(lamb.setAt()(arr)).toEqual(r3); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.setIndex).toThrow(); - expect(lamb.setAt(1, 1)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.setAt(0, 99)(null); }).toThrow(); - expect(function () { lamb.setAt(0, 99)(void 0); }).toThrow(); - expect(function () { lamb.setIndex(null, 0, 99); }).toThrow(); - expect(function () { lamb.setIndex(void 0, 0, 99); }).toThrow(); - }); - - it("should return an empty array for every other value", function () { - wannabeEmptyArrays.forEach(function (v) { - expect(lamb.setIndex(v, 2, 99)).toEqual([]); - expect(lamb.setAt(2, 99)(v)).toEqual([]); - }); - }); - }); - - describe("updateIndex / updateAt", function () { - var inc = function (n) { return n + 1; }; - var incSpy = jasmine.createSpy("inc").and.callFake(inc); - var fn99 = lamb.always(99); - - afterEach(function () { - expect(arr).toEqual(arrCopy); - incSpy.calls.reset(); - }); - - it("should allow to update a value in a copy of the given array-like object with the provided function", function () { - expect(lamb.updateIndex(arr, 2, incSpy)).toEqual([1, 2, 4, 4, 5]); - expect(lamb.updateAt(2, incSpy)(arr)).toEqual([1, 2, 4, 4, 5]); - expect(incSpy.calls.count()).toBe(2); - expect(incSpy.calls.argsFor(0).length).toBe(1); - expect(incSpy.calls.argsFor(1).length).toBe(1); - expect(incSpy.calls.argsFor(0)[0]).toBe(3); - expect(incSpy.calls.argsFor(1)[0]).toBe(3); - expect(lamb.updateIndex(s, 0, lamb.always("z"))).toEqual(["z", "b", "c", "d", "e"]); - expect(lamb.updateAt(0, lamb.always("z"))(s)).toEqual(["z", "b", "c", "d", "e"]); - expect(lamb.updateAt(1, lamb.always(99))([1, void 0, 3])).toEqual([1, 99, 3]); - }); - - it("should allow negative indexes", function () { - var newArr = lamb.updateIndex(arr, -1, fn99); - var newArr2 = lamb.updateAt(-5, fn99)(arr); - - expect(newArr).toEqual([1, 2, 3, 4, 99]); - expect(newArr2).toEqual([99, 2, 3, 4, 5]); - }); - - it("should always return dense arrays", function () { - var r1 = [void 0, void 0, 99, void 0]; - var r2 = [void 0, void 0, 3, 99]; - - expect(lamb.updateIndex(sparseArr, 2, fn99)).toEqual(r1); - expect(lamb.updateIndex(sparseArr, -2, fn99)).toEqual(r1); - expect(lamb.updateIndex(sparseArr, -1, fn99)).toEqual(r2); - expect(lamb.updateIndex(sparseArr, 3, fn99)).toEqual(r2); - expect(lamb.updateAt(2, fn99)(sparseArr)).toEqual(r1); - expect(lamb.updateAt(-2, fn99)(sparseArr)).toEqual(r1); - expect(lamb.updateAt(-1, fn99)(sparseArr)).toEqual(r2); - expect(lamb.updateAt(3, fn99)(sparseArr)).toEqual(r2); - }); - - it("should return an array copy of the array-like if the index is out of bounds", function () { - var newArr = lamb.updateIndex(arr, 5, fn99); - var newArr2 = lamb.updateAt(-6, fn99)(arr); - var newS = lamb.updateAt(10, fn99)(s); - var newSparseArr = lamb.updateIndex(sparseArr, 5, fn99); - var newSparseArr2 = lamb.updateAt(5, fn99)(sparseArr); - - expect(newArr).toEqual([1, 2, 3, 4, 5]); - expect(newArr2).toEqual([1, 2, 3, 4, 5]); - expect(newArr).not.toBe(arr); - expect(newArr2).not.toBe(arr); - expect(newS).toEqual(["a", "b", "c", "d", "e"]); - expect(newSparseArr).toEqual(sparseArrAsDense); - expect(newSparseArr).not.toBe(sparseArr); - expect(newSparseArr2).toEqual(sparseArrAsDense); - expect(newSparseArr2).not.toBe(sparseArr); - }); - - it("should convert the `index` parameter to integer", function () { - var r1 = [99, 2, 3, 4, 5]; - var r2 = [1, 99, 3, 4, 5]; - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.updateIndex(arr, value, fn99)).toEqual(r1); - expect(lamb.updateAt(value, fn99)(arr)).toEqual(r1); - }); - - [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { - expect(lamb.updateIndex(arr, value, fn99)).toEqual(r2); - expect(lamb.updateAt(value, fn99)(arr)).toEqual(r2); - }); - - expect(lamb.updateIndex(arr, new Date(), fn99)).toEqual(arr); - expect(lamb.updateAt(new Date(), fn99)(arr)).toEqual(arr); - }); - - it("should throw an exception if the `updater` isn't a function or if is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.updateIndex(arr, 0, value); }).toThrow(); - expect(function () { lamb.updateAt(0, value)(arr); }).toThrow(); - }); - - expect(function () { lamb.updateIndex(arr, 0); }).toThrow(); - expect(function () { lamb.updateAt(0)(arr); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.updateIndex).toThrow(); - expect(lamb.updateAt(1, fn99)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.updateIndex(null, 0, fn99); }).toThrow(); - expect(function () { lamb.updateIndex(void 0, 0, fn99); }).toThrow(); - expect(function () { lamb.updateAt(0, fn99)(null); }).toThrow(); - expect(function () { lamb.updateAt(0, fn99)(void 0); }).toThrow(); - }); - - it("should return an empty array for every other value", function () { - wannabeEmptyArrays.forEach(function (v) { - expect(lamb.updateIndex(v, 2, fn99)).toEqual([]); - expect(lamb.updateAt(2, fn99)(v)).toEqual([]); - }); - }); - }); - }); - - describe("Object accessors", function () { - describe("getIn / getKey", function () { - var obj = {foo: 1, bar: 2, baz: 3}; - - Object.defineProperty(obj, "qux", {value: 4}); - - it("should return the value of the given object property", function () { - expect(lamb.getIn(obj, "bar")).toBe(2); - expect(lamb.getKey("foo")(obj)).toBe(1); - }); - - it("should return `undefined` for a non-existent property", function () { - expect(lamb.getIn(obj, "a")).toBeUndefined(); - expect(lamb.getKey("z")(obj)).toBeUndefined(); - }); - - it("should be able to retrieve non-enumerable properties", function () { - expect(lamb.getIn(obj, "qux")).toBe(4); - expect(lamb.getKey("qux")(obj)).toBe(4); - }); - - it("should accept integers as keys and accept array-like objects", function () { - var o = {1: "a", 2: "b"}; - var arr = [1, 2, 3, 4]; - var s = "abcd"; - - expect(lamb.getIn(o, 1)).toBe("a"); - expect(lamb.getKey(2)(o)).toBe("b"); - expect(lamb.getIn(arr, 1)).toBe(2); - expect(lamb.getKey(2)(arr)).toBe(3); - expect(lamb.getIn(s, 1)).toBe("b"); - expect(lamb.getKey(2)(s)).toBe("c"); - }); - - it("should work with sparse arrays", function () { - var sparseArr = Array(3); - - sparseArr[1] = 99; - - expect(lamb.getIn(sparseArr, 1)).toBe(99); - expect(lamb.getIn(sparseArr, 2)).toBeUndefined(); - expect(lamb.getKey(1)(sparseArr)).toBe(99); - expect(lamb.getKey(2)(sparseArr)).toBeUndefined(); - }); - - it("should convert other values for the `key` parameter to string", function () { - var values = lamb.range(0, nonStringsAsStrings.length, 1); - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - var value = values[nonStringsAsStrings.indexOf(String(key))]; - - expect(lamb.getIn(testObj, key)).toBe(value); - expect(lamb.getKey(key)(testObj)).toBe(value); - }); - - var idx = nonStringsAsStrings.indexOf("undefined"); - - expect(lamb.getIn(testObj)).toBe(idx); - expect(lamb.getKey()(testObj)).toBe(idx); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.getIn).toThrow(); - expect(lamb.getKey("a")).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.getIn(null, "a"); }).toThrow(); - expect(function () { lamb.getIn(void 0, "a"); }).toThrow(); - expect(function () { lamb.getKey("a")(null); }).toThrow(); - expect(function () { lamb.getKey("a")(void 0); }).toThrow(); - }); - - it("should return convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.getIn(v, "a")).toBeUndefined(); - expect(lamb.getKey("a")(v)).toBeUndefined(); - }); - - expect(lamb.getIn(/foo/, "lastIndex")).toBe(0); - expect(lamb.getKey("lastIndex")(/foo/)).toBe(0); - }); - }); - - describe("getPath / getPathIn", function () { - var obj = {a: 2, b: {a: 3, b: [4, 5], c: "foo"}, "c.d": {"e.f": 6}}; - - obj.b.d = Array(3); - obj.b.d[1] = 99; - - Object.defineProperty(obj, "e", {value: 10}); - obj.f = Object.create({}, {g: {value: 20}}); - - it("should retrieve a nested object property using the supplied path", function () { - expect(lamb.getPath("a")(obj)).toBe(2); - expect(lamb.getPath("b.a")(obj)).toBe(3); - expect(lamb.getPath("b.b")(obj)).toBe(obj.b.b); - expect(lamb.getPathIn(obj, "a")).toBe(2); - expect(lamb.getPathIn(obj, "b.a")).toBe(3); - expect(lamb.getPathIn(obj, "b.b")).toBe(obj.b.b); - }); - - it("should be able to access non-enumerable properties", function () { - expect(lamb.getPath("e")(obj)).toBe(10); - expect(lamb.getPathIn(obj, "e")).toBe(10); - expect(lamb.getPath("f.g")(obj)).toBe(20); - expect(lamb.getPathIn(obj, "f.g")).toBe(20); - }); - - it("should be able to retrieve values from arrays and array-like objects", function () { - expect(lamb.getPath("b.b.0")(obj)).toBe(4); - expect(lamb.getPath("b.c.0")(obj)).toBe("f"); - expect(lamb.getPathIn(obj, "b.b.0")).toBe(4); - expect(lamb.getPathIn(obj, "b.c.0")).toBe("f"); - }); - - it("should allow negative indexes", function () { - expect(lamb.getPath("b.b.-1")(obj)).toBe(5); - expect(lamb.getPathIn(obj, "b.b.-1")).toBe(5); - expect(lamb.getPath("b.c.-3")(obj)).toBe("f"); - expect(lamb.getPathIn(obj, "b.c.-3")).toBe("f"); - }); - - it("should work with sparse arrays", function () { - expect(lamb.getPathIn(obj, "b.d.1")).toBe(99); - expect(lamb.getPathIn(obj, "b.d.-2")).toBe(99); - expect(lamb.getPath("b.d.1")(obj)).toBe(99); - expect(lamb.getPath("b.d.-2")(obj)).toBe(99); - }); - - it("should be able to retrieve values nested in arrays", function () { - var o = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 20}, - {id: 3, value: 30} - ] - }; - - expect(lamb.getPath("data.1.value")(o)).toBe(20); - expect(lamb.getPathIn(o, "data.1.value")).toBe(20); - expect(lamb.getPath("data.-1.value")(o)).toBe(30); - expect(lamb.getPathIn(o, "data.-1.value")).toBe(30); - }); - - it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { - var o = {a: ["abc", new String("def"), "ghi"]}; - - o.a["-1"] = "foo"; - o.a[1]["-2"] = "bar"; - Object.defineProperty(o.a, "-2", {value: 99}); - - expect(lamb.getPath("a.-1")(o)).toBe("foo"); - expect(lamb.getPathIn(o, "a.-1")).toBe("foo"); - expect(lamb.getPath("a.1.-2")(o)).toBe("bar"); - expect(lamb.getPathIn(o, "a.1.-2")).toBe("bar"); - expect(lamb.getPath("a.-2")(o)).toBe(99); - expect(lamb.getPathIn(o, "a.-2")).toBe(99); - }); - - it("should accept a custom path separator", function () { - expect(lamb.getPath("b->b->0", "->")(obj)).toBe(4); - expect(lamb.getPath("c.d/e.f", "/")(obj)).toBe(6); - expect(lamb.getPathIn(obj, "b->b->0", "->")).toBe(4); - expect(lamb.getPathIn(obj, "c.d/e.f", "/")).toBe(6); - }); - - it("should return `undefined` for a non-existent path in a valid source", function () { - expect(lamb.getPath("b.a.z")(obj)).toBeUndefined(); - expect(lamb.getPathIn(obj, "b.a.z")).toBeUndefined(); - expect(lamb.getPath("b.z.a")(obj)).toBeUndefined(); - expect(lamb.getPathIn(obj, "b.z.a")).toBeUndefined(); - expect(lamb.getPath("b.b.10")(obj)).toBeUndefined(); - expect(lamb.getPathIn(obj, "b.b.10")).toBeUndefined(); - expect(lamb.getPath("b.b.10.z")(obj)).toBeUndefined(); - expect(lamb.getPathIn(obj, "b.b.10.z")).toBeUndefined(); - }); - - it("should accept integers as paths containing a single key", function () { - expect(lamb.getPathIn([1, 2], 1)).toBe(2); - expect(lamb.getPath(1)([1, 2])).toBe(2); - expect(lamb.getPathIn([1, 2], -1)).toBe(2); - expect(lamb.getPath(-1)([1, 2])).toBe(2); - expect(lamb.getPathIn({1: "a"}, 1)).toBe("a"); - expect(lamb.getPath(1)({1: "a"})).toBe("a"); - }); - - it("should convert other values for the `path` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - var value = values[nonStringsAsStrings.indexOf(String(key))]; - - expect(lamb.getPathIn(testObj, key, "_")).toBe(value); - expect(lamb.getPath(key, "_")(testObj)).toBe(value); - }); - - var fooObj = {a: 2, 1: {5: 3}, undefined: 4}; - - expect(lamb.getPathIn(fooObj, 1.5)).toBe(3); - expect(lamb.getPath(1.5)(fooObj)).toBe(3); - - expect(lamb.getPathIn(fooObj)).toBe(4); - expect(lamb.getPath()(fooObj)).toBe(4); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.getPathIn).toThrow(); - expect(lamb.getPath()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.getPathIn(null, "a"); }).toThrow(); - expect(function () { lamb.getPathIn(void 0, "a"); }).toThrow(); - expect(function () { lamb.getPath("a")(null); }).toThrow(); - expect(function () { lamb.getPath("a")(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.getPathIn(value, "a")).toBeUndefined(); - expect(lamb.getPath("a")(value)).toBeUndefined(); - }); - - expect(lamb.getPathIn(/foo/, "lastIndex")).toBe(0); - expect(lamb.getPath("lastIndex")(/foo/)).toBe(0); - }); - }); - - describe("Property setters", function () { - var arr = [1, 2, 3]; - var sparseArr = [, 5, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays - var sparseArrCopy = sparseArr.slice(); - var baseFoo = Object.create({a: arr}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - var foo = Object.create(baseFoo, {c: {value: 3, enumerable: true}}); - - var fooEquivalent = {a: [1, 2, 3], b: 2, c: 3, z: 5}; - var fooEnumerables = {a: [1, 2, 3], b: 2, c: 3}; - - // The "toEqual" matcher would have checked only own enumerable properties - afterEach(function () { - for (var key in fooEquivalent) { - expect(fooEquivalent[key]).toEqual(foo[key]); - } - - expect(foo.a).toBe(arr); - expect(sparseArr).toEqual(sparseArrCopy); - }); - - describe("setIn / setKey", function () { - it("should build a copy of the source object with all its enumerable properties and the desired key set to the provided value", function () { - var newObjA = lamb.setIn(foo, "c", 99); - var newObjB = lamb.setKey("a", ["a", "b"])(foo); - - expect(newObjA).toEqual({a: [1, 2, 3], b: 2, c: 99}); - expect(newObjA.a).toBe(arr); - expect(newObjA.z).toBeUndefined(); - expect(newObjB).toEqual({a: ["a", "b"], b: 2, c: 3}); - expect(newObjB.a).not.toBe(arr); - expect(newObjB.z).toBeUndefined(); - }); - - it("should add a new property if the given key doesn't exist on the source or if it isn't enumerable", function () { - var newObjA = lamb.setIn(foo, "z", 99); - var newObjB = lamb.setKey("z", 0)(foo); - - expect(newObjA).toEqual({a: [1, 2, 3], b: 2, c: 3, z: 99}); - expect(newObjB).toEqual({a: [1, 2, 3], b: 2, c: 3, z: 0}); - }); - - it("should transform array-like objects in objects with numbered string as properties", function () { - expect(lamb.setIn([1, 2], "a", 99)).toEqual({0: 1, 1: 2, a: 99}); - expect(lamb.setKey("a", 99)([1, 2])).toEqual({0: 1, 1: 2, a: 99}); - expect(lamb.setIn("foo", "a", 99)).toEqual({0: "f", 1: "o", 2: "o", a: 99}); - expect(lamb.setKey("a", 99)("foo")).toEqual({0: "f", 1: "o", 2: "o", a: 99}); - }); - - it("should accept integers as keys", function () { - expect(lamb.setIn([1, 2], 1, 3)).toEqual({0: 1, 1: 3}); - expect(lamb.setKey(1, 3)([1, 2])).toEqual({0: 1, 1: 3}); - }); - - it("should use only defined keys in sparse arrays", function () { - var r1 = {1: 99}; - var r2 = {1: 5, 2: 99}; - - expect(lamb.setIn(sparseArr, 1, 99)).toEqual(r1); - expect(lamb.setIn(sparseArr, 2, 99)).toEqual(r2); - expect(lamb.setKey(1, 99)(sparseArr)).toEqual(r1); - expect(lamb.setKey(2, 99)(sparseArr)).toEqual(r2); - }); - - it("should convert other values for the `key` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - var expected = lamb.merge(testObj, {}); - - expected[String(key)] = 99; - - expect(lamb.setIn(testObj, key, 99)).toEqual(expected); - expect(lamb.setKey(key, 99)(testObj)).toEqual(expected); - }); - - expect(lamb.setIn({a: 2})).toEqual({a: 2, undefined: void 0}); - expect(lamb.setKey()({a: 2})).toEqual({a: 2, undefined: void 0}); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.setIn).toThrow(); - expect(lamb.setKey()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.setIn(null, "a", 99); }).toThrow(); - expect(function () { lamb.setIn(void 0, "a", 99); }).toThrow(); - expect(function () { lamb.setKey("a", 99)(null); }).toThrow(); - expect(function () { lamb.setKey("a", 99)(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.setIn(value, "a", 99)).toEqual({a: 99}); - expect(lamb.setKey("a", 99)(value)).toEqual({a: 99}); - }); - }); - }); - - describe("updateIn / updateKey", function () { - it("should build a copy of the source object with all its enumerable properties and the desired key updated according to the received function", function () { - var makeDoubles = lamb.mapWith(function (n) { return n * 2; }); - var makeDoublesSpy = jasmine.createSpy("makeDoubles").and.callFake(makeDoubles); - var newObjA = lamb.updateIn(foo, "a", makeDoublesSpy); - var newObjB = lamb.updateKey("a", makeDoublesSpy)(foo); - var result = {a: [2, 4, 6], b: 2, c: 3}; - - expect(newObjA).toEqual(result); - expect(newObjB).toEqual(result); - expect(makeDoublesSpy.calls.count()).toBe(2); - expect(makeDoublesSpy.calls.argsFor(0).length).toBe(1); - expect(makeDoublesSpy.calls.argsFor(0)[0]).toBe(arr); - expect(makeDoublesSpy.calls.argsFor(1).length).toBe(1); - expect(makeDoublesSpy.calls.argsFor(1)[0]).toBe(arr); - - var o = {a: 1, b: void 0}; - - expect(lamb.updateIn(o, "b", lamb.always(99))).toEqual({a: 1, b: 99}); - expect(lamb.updateKey("b", lamb.always(99))(o)).toEqual({a: 1, b: 99}); - }); - - it("should transform array-like objects in objects with numbered string as properties", function () { - var fn = lamb.always(99); - - expect(lamb.updateIn([1, 2], "1", fn)).toEqual({0: 1, 1: 99}); - expect(lamb.updateKey("1", fn)([1, 2])).toEqual({0: 1, 1: 99}); - expect(lamb.updateIn("foo", "1", fn)).toEqual({0: "f", 1: 99, 2: "o"}); - expect(lamb.updateKey("1", fn)("foo")).toEqual({0: "f", 1: 99, 2: "o"}); - }); - - it("should not add a new property if the given key doesn't exist on the source, and return a copy of the source instead", function () { - var newObjA = lamb.updateIn(foo, "xyz", lamb.always(99)); - var newObjB = lamb.updateKey("xyz", lamb.always(99))(foo); - var newObjC = lamb.updateKey("xyz", lamb.always(99))([1]); - - expect(newObjA).toEqual(fooEnumerables); - expect(newObjA).not.toBe(foo); - expect(newObjB).toEqual(fooEnumerables); - expect(newObjB).not.toBe(foo); - expect(newObjC).toEqual({0: 1}); - }); - - it("should not allow to update a non-enumerable property, and return a copy of the source instead, as with non-existent keys", function () { - var newObjA = lamb.updateIn(foo, "z", lamb.always(99)); - var newObjB = lamb.updateKey("z", lamb.always(99))(foo); - - expect(newObjA).toEqual(fooEnumerables); - expect(newObjA).not.toBe(foo); - expect(newObjB).toEqual(fooEnumerables); - expect(newObjB).not.toBe(foo); - }); - - it("should check the validity of the destination key before trying to apply the received function to it", function () { - var o = {a: 1}; - var toUpperCase = lamb.invoker("toUpperCase"); - - expect(lamb.updateIn(o, "z", toUpperCase)).toEqual({a: 1}); - expect(lamb.updateKey("z", toUpperCase)(o)).toEqual({a: 1}); - }); - - it("should accept integers as keys", function () { - var inc = function (n) { return ++n; }; - - expect(lamb.updateIn([1, 2], 1, inc)).toEqual({0: 1, 1: 3}); - expect(lamb.updateKey(1, inc)([1, 2])).toEqual({0: 1, 1: 3}); - }); - - it("should use only defined keys in sparse arrays", function () { - var fn99 = lamb.always(99); - var r1 = {1: 99}; - var r2 = {1: 5}; - - expect(lamb.updateIn(sparseArr, 1, fn99)).toEqual(r1); - expect(lamb.updateIn(sparseArr, 2, fn99)).toEqual(r2); - expect(lamb.updateKey(1, fn99)(sparseArr)).toEqual(r1); - expect(lamb.updateKey(2, fn99)(sparseArr)).toEqual(r2); - }); - - it("should convert other values for the `key` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - var expected = lamb.merge(testObj, {}); - - expected[String(key)] = 99; - - expect(lamb.updateIn(testObj, key, lamb.always(99))).toEqual(expected); - expect(lamb.updateKey(key, lamb.always(99))(testObj)).toEqual(expected); - }); - }); - - it("should throw an exception if the `updater` isn't a function or if is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.updateIn({a: 2}, "a", value); }).toThrow(); - expect(function () { lamb.updateKey("a", value)({a: 2}); }).toThrow(); - }); - - expect(function () { lamb.updateIn({a: 2}, "a"); }).toThrow(); - expect(function () { lamb.updateKey("a")({a: 2}); }).toThrow(); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.updateIn).toThrow(); - expect(lamb.updateKey()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.updateIn(null, "a", lamb.always(99)); }).toThrow(); - expect(function () { lamb.updateIn(void 0, "a", lamb.always(99)); }).toThrow(); - expect(function () { lamb.updateKey("a", lamb.always(99))(null); }).toThrow(); - expect(function () { lamb.updateKey("a", lamb.always(99))(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.updateIn(value, "a", lamb.always(99))).toEqual({}); - expect(lamb.updateKey("a", lamb.always(99))(value)).toEqual({}); - }); - }); - }); - }); - - describe("Path setters", function () { - var obj = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo" - }, - "c.d": {"e.f": 6} - }; - - obj.b.d = [, 55, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays - - Object.defineProperty(obj.b, "w", { - value: { - x: 22, - y: {z: 33} - } - }); - - var objCopy = JSON.parse(JSON.stringify(obj)); - - objCopy.b.d = [, 55, ,]; // eslint-disable-line comma-spacing, no-sparse-arrays - - afterEach(function () { - expect(obj).toEqual(objCopy); - }); - - describe("setPath / setPathIn", function () { - it("should allow to set a nested property in a copy of the given object", function () { - var r = lamb.setPath("a", 99, ".")(obj); - - expect(r).toEqual({ - a: 99, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }); - - expect(r.b).toBe(obj.b); - expect(r["c.d"]).toBe(obj["c.d"]); - expect(lamb.setPathIn(obj, "a", 99, ".")).toEqual(r); - - var r1 = lamb.setPath("b.c", "bar", ".")(obj); - - expect(r1).toEqual({ - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "bar", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }); - - expect(r1.b.a).toBe(obj.b.a); - expect(r1.b.b).toBe(obj.b.b); - expect(r1["c.d"]).toBe(obj["c.d"]); - expect(lamb.setPathIn(obj, "b.c", "bar", ".")).toEqual(r1); - }); - - it("should use the dot as the default separator", function () { - var r = lamb.setPath("b.a.g", 99)(obj); - - expect(r).toEqual({ - a: 2, - b: { - a: {g: 99, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }); - expect(r.b.b).toBe(obj.b.b); - expect(r["c.d"]).toBe(obj["c.d"]); - expect(lamb.setPathIn(obj, "b.a.g", 99)).toEqual(r); - }); - - it("should ignore extra arguments passed to the built function in its partially applied form", function () { - var r = lamb.setPath("b.c", "bar")(obj, {}); - - expect(r).toEqual({ - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "bar", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }); - }); - - it("should allow custom separators", function () { - var r = lamb.setPath("c.d->e.f", 99, "->")(obj); - - expect(r).toEqual({ - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 99} - }); - expect(r.b).toBe(obj.b); - expect(lamb.setPathIn(obj, "c.d->e.f", 99, "->")).toEqual(r); - }); - - it("should add non-existent properties to existing objects", function () { - var r1 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, , ], // eslint-disable-line comma-spacing, no-sparse-arrays - z: 99 - }, - "c.d": {"e.f": 6} - }; - var r2 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6}, - z: {a: 99} - }; - var r3 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6}, - z: {a: {b: 99}} - }; - - expect(lamb.setPath("b.z", 99)(obj)).toEqual(r1); - expect(lamb.setPathIn(obj, "b.z", 99)).toEqual(r1); - expect(lamb.setPath("z.a", 99)(obj)).toEqual(r2); - expect(lamb.setPathIn(obj, "z.a", 99)).toEqual(r2); - expect(lamb.setPath("z.a.b", 99)(obj)).toEqual(r3); - expect(lamb.setPathIn(obj, "z.a.b", 99)).toEqual(r3); - - var o = {a: null}; - var r4 = {a: {b: {c: 99}}}; - - expect(lamb.setPath("a.b.c", 99)(o)).toEqual(r4); - expect(lamb.setPathIn(o, "a.b.c", 99)).toEqual(r4); - }); - - it("should treat non-enumerable properties encountered in a path as non-existent properties", function () { - var r1 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, , ], // eslint-disable-line comma-spacing, no-sparse-arrays - w: {z: 99} - }, - "c.d": {"e.f": 6} - }; - var r2 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, , ], // eslint-disable-line comma-spacing, no-sparse-arrays - w: {y: {z: 99}} - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.setPathIn(obj, "b.w.z", 99)).toEqual(r1); - expect(lamb.setPath("b.w.z", 99)(obj)).toEqual(r1); - expect(lamb.setPathIn(obj, "b.w.y.z", 99)).toEqual(r2); - expect(lamb.setPath("b.w.y.z", 99)(obj)).toEqual(r2); - }); - - it("should replace indexes when an array is found and the key is a string containing an integer", function () { - var r = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 99], - c: "foo", - d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.setPath("b.b.1", 99)(obj)).toEqual(r); - expect(lamb.setPathIn(obj, "b.b.1", 99)).toEqual(r); - expect(lamb.setPath("1", 99)([1, 2, 3])).toEqual([1, 99, 3]); - expect(lamb.setPathIn([1, 2, 3], "1", 99)).toEqual([1, 99, 3]); - }); - - it("should allow using negative array indexes in path parts", function () { - var r = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [99, 5], - c: "foo", - d: [, 55, , ] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.setPath("b.b.-2", 99)(obj)).toEqual(r); - expect(lamb.setPathIn(obj, "b.b.-2", 99)).toEqual(r); - expect(lamb.setPath("-2", 99)([1, 2, 3])).toEqual([1, 99, 3]); - expect(lamb.setPathIn([1, 2, 3], "-2", 99)).toEqual([1, 99, 3]); - }); - - it("should build dense arrays when the path target is a sparse array index", function () { - var r1 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [void 0, 99, void 0] - }, - "c.d": {"e.f": 6} - }; - var r2 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [void 0, 55, 99] - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.setPathIn(obj, "b.d.1", 99)).toEqual(r1); - expect(lamb.setPathIn(obj, "b.d.-2", 99)).toEqual(r1); - expect(lamb.setPathIn(obj, "b.d.-1", 99)).toEqual(r2); - expect(lamb.setPathIn(obj, "b.d.2", 99)).toEqual(r2); - expect(lamb.setPath("b.d.1", 99)(obj)).toEqual(r1); - expect(lamb.setPath("b.d.-2", 99)(obj)).toEqual(r1); - expect(lamb.setPath("b.d.-1", 99)(obj)).toEqual(r2); - expect(lamb.setPath("b.d.2", 99)(obj)).toEqual(r2); - }); - - it("should not add new elements to an array and behave like `setAt` which returns a copy of the array", function () { - var r1 = lamb.setPath("b.b.2", 99)(obj); - var r2 = lamb.setPathIn(obj, "b.b.2", 99); - var r3 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [void 0, 55, void 0] - }, - "c.d": {"e.f": 6} - }; - - expect(r1).toEqual(obj); - expect(r2).toEqual(obj); - expect(r1.b.b).not.toBe(obj.b.b); - expect(r2.b.b).not.toBe(obj.b.b); - - expect(lamb.setPathIn(obj, "b.d.11", 99)).toEqual(r3); - expect(lamb.setPath("b.d.11", 99)(obj)).toEqual(r3); - }); - - it("should allow to change values nested in an array", function () { - var o = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 20}, - {id: 3, value: 30} - ] - }; - var r = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 99}, - {id: 3, value: 30} - ] - }; - - expect(lamb.setPath("data.1.value", 99)(o)).toEqual(r); - expect(lamb.setPathIn(o, "data.1.value", 99)).toEqual(r); - expect(lamb.setPath("data.-2.value", 99)(o)).toEqual(r); - expect(lamb.setPathIn(o, "data.-2.value", 99)).toEqual(r); - }); - - it("should build an object with numbered keys when an array-like object is found", function () { - var r = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: {0: "m", 1: "o", 2: "o"}, - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.setPath("b.c.0", "m")(obj)).toEqual(r); - expect(lamb.setPathIn(obj, "b.c.0", "m")).toEqual(r); - }); - - it("should build an object with numbered keys when an array is found and the key is not a string containing an integer", function () { - var r = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: {0: 4, 1: 5, z: 99}, - c: "foo", d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.setPath("b.b.z", 99)(obj)).toEqual(r); - expect(lamb.setPathIn(obj, "b.b.z", 99)).toEqual(r); - }); - - it("should accept integers as paths containing a single key", function () { - expect(lamb.setPath(1, 99)([1, 2, 3])).toEqual([1, 99, 3]); - expect(lamb.setPathIn([1, 2, 3], -1, 99)).toEqual([1, 2, 99]); - expect(lamb.setPath(2, 99)([1, 2])).toEqual([1, 2]); - expect(lamb.setPathIn({a: 1}, 1, 99)).toEqual({a: 1, 1: 99}); - }); - - it("should give priority to object keys over array indexes when a negative index is encountered", function () { - var o = {a: ["abc", "def", "ghi"]}; - - o.a["-1"] = "foo"; - - var r = {a: {0: "abc", 1: "def", 2: "ghi", "-1": 99}}; - - expect(lamb.setPath("a.-1", 99)(o)).toEqual(r); - expect(lamb.setPathIn(o, "a.-1", 99)).toEqual(r); - }); - - it("should consider a negative integer to be an index if the property exists but it's not enumerable", function () { - var o = {a: ["abc", "def", "ghi"]}; - - Object.defineProperty(o.a, "-1", {value: 99}); - - var r = {a: ["abc", "def", "foo"]}; - - expect(lamb.setPath("a.-1", "foo")(o)).toEqual(r); - expect(lamb.setPathIn(o, "a.-1", "foo")).toEqual(r); - }); - - it("should convert other values for the `path` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - var expected = lamb.merge(testObj, {}); - - expected[String(key)] = 99; - - expect(lamb.setPathIn(testObj, key, 99, "_")).toEqual(expected); - expect(lamb.setPath(key, 99, "_")(testObj)).toEqual(expected); - }); - - expect(lamb.setPathIn({a: 2}, 1.5, 99)).toEqual({a: 2, 1: {5: 99}}); - expect(lamb.setPath(1.5, 99)({a: 2})).toEqual({a: 2, 1: {5: 99}}); - - expect(lamb.setPathIn({a: 2})).toEqual({a: 2, undefined: void 0}); - expect(lamb.setPath()({a: 2})).toEqual({a: 2, undefined: void 0}); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.setPathIn).toThrow(); - expect(lamb.setPath()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.setPathIn(null, "a", 99); }).toThrow(); - expect(function () { lamb.setPathIn(void 0, "a", 99); }).toThrow(); - expect(function () { lamb.setPath("a", 99)(null); }).toThrow(); - expect(function () { lamb.setPath("a", 99)(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.setPathIn(value, "a", 99)).toEqual({a: 99}); - expect(lamb.setPath("a", 99)(value)).toEqual({a: 99}); - }); - }); - }); - - describe("updatePath / updatePathIn", function () { - var double = function (n) { return n * 2; }; - var inc = function (n) { return n + 1; }; - var toUpperCase = lamb.invoker("toUpperCase"); - - it("should allow to update a nested property in a copy of the given object using the provided function", function () { - var makeDoubles = lamb.mapWith(double); - var makeDoublesSpy = jasmine.createSpy("makeDoubles").and.callFake(makeDoubles); - var newObjA = lamb.updatePathIn(obj, "b.b", makeDoublesSpy, "."); - var newObjB = lamb.updatePath("b.b", makeDoublesSpy, ".")(obj); - var r1 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [8, 10], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(newObjA).toEqual(r1); - expect(newObjB).toEqual(r1); - expect(makeDoublesSpy.calls.count()).toBe(2); - expect(makeDoublesSpy.calls.argsFor(0).length).toBe(1); - expect(makeDoublesSpy.calls.argsFor(0)[0]).toBe(obj.b.b); - expect(makeDoublesSpy.calls.argsFor(1).length).toBe(1); - expect(makeDoublesSpy.calls.argsFor(1)[0]).toBe(obj.b.b); - - expect(newObjA.b.a).toBe(obj.b.a); - expect(newObjA["c.d"]).toBe(obj["c.d"]); - - var r2 = { - a: 3, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.updatePathIn(obj, "a", inc, ".")).toEqual(r2); - expect(lamb.updatePath("a", inc, ".")(obj)).toEqual(r2); - }); - - it("should use the dot as the default separator", function () { - var r = lamb.updatePath("b.a.g", double)(obj); - - expect(r).toEqual({ - a: 2, - b: { - a: {g: 20, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }); - expect(r.b.b).toBe(obj.b.b); - expect(r["c.d"]).toBe(obj["c.d"]); - expect(lamb.updatePathIn(obj, "b.a.g", double)).toEqual(r); - }); - - it("should ignore extra arguments passed to the built function in its partially applied form", function () { - var r = lamb.updatePath("b.a.h", double)(obj, {}); - - expect(r).toEqual({ - a: 2, - b: { - a: {g: 10, h: 22}, - b: [4, 5], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }); - }); - - it("should allow custom separators", function () { - var r = lamb.updatePath("c.d->e.f", double, "->")(obj); - - expect(r).toEqual({ - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 12} - }); - - expect(r.b).toBe(obj.b); - expect(lamb.updatePathIn(obj, "c.d->e.f", double, "->")).toEqual(r); - }); - - it("should be possible to use a path with a single key", function () { - var arr = [1, 2, 3]; - var o = {a: 1, b: 2}; - - expect(lamb.updatePathIn(arr, "1", inc)).toEqual([1, 3, 3]); - expect(lamb.updatePath("-1", inc)(arr)).toEqual([1, 2, 4]); - expect(lamb.updatePathIn(o, "b", inc)).toEqual({a: 1, b: 3}); - expect(lamb.updatePath("a", inc)(o)).toEqual({a: 2, b: 2}); - }); - - it("should replace indexes when an array is found and the key is a string containing an integer", function () { - var r = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 10], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.updatePath("b.b.1", double)(obj)).toEqual(r); - expect(lamb.updatePathIn(obj, "b.b.1", double)).toEqual(r); - expect(lamb.updatePath("1", double)([1, 2, 3])).toEqual([1, 4, 3]); - expect(lamb.updatePathIn([1, 2, 3], "1", double)).toEqual([1, 4, 3]); - }); - - it("should allow using negative array indexes in path parts", function () { - var arr = [1, 2, 3]; - var r1 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [8, 5], - c: "foo", - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - var r2 = [1, 4, 3]; - - expect(lamb.updatePath("b.b.-2", double)(obj)).toEqual(r1); - expect(lamb.updatePath("b.b.-3", double)(obj)).toEqual(obj); - expect(lamb.updatePath("-2", double)(arr)).toEqual(r2); - expect(lamb.updatePath("-4", double)(arr)).toEqual(arr); - expect(lamb.updatePathIn(obj, "b.b.-2", double)).toEqual(r1); - expect(lamb.updatePathIn(obj, "b.b.-3", double)).toEqual(obj); - expect(lamb.updatePathIn(arr, "-2", double)).toEqual(r2); - expect(lamb.updatePathIn(arr, "-4", double)).toEqual(arr); - }); - - it("should allow to change values nested in an array", function () { - var o = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 20}, - {id: 3, value: 30} - ] - }; - var r = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 21}, - {id: 3, value: 30} - ] - }; - - expect(lamb.updatePath("data.1.value", inc)(o)).toEqual(r); - expect(lamb.updatePathIn(o, "data.1.value", inc)).toEqual(r); - expect(lamb.updatePath("data.-2.value", inc)(o)).toEqual(r); - expect(lamb.updatePathIn(o, "data.-2.value", inc)).toEqual(r); - }); - - it("should build dense arrays when the path target is a sparse array index", function () { - var r1 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [void 0, 99, void 0] - }, - "c.d": {"e.f": 6} - }; - var r2 = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: "foo", - d: [void 0, 55, 99] - }, - "c.d": {"e.f": 6} - }; - var fn99 = lamb.always(99); - - expect(lamb.updatePathIn(obj, "b.d.1", fn99)).toEqual(r1); - expect(lamb.updatePathIn(obj, "b.d.-2", fn99)).toEqual(r1); - expect(lamb.updatePathIn(obj, "b.d.-1", fn99)).toEqual(r2); - expect(lamb.updatePathIn(obj, "b.d.2", fn99)).toEqual(r2); - expect(lamb.updatePath("b.d.1", fn99)(obj)).toEqual(r1); - expect(lamb.updatePath("b.d.-2", fn99)(obj)).toEqual(r1); - expect(lamb.updatePath("b.d.-1", fn99)(obj)).toEqual(r2); - expect(lamb.updatePath("b.d.2", fn99)(obj)).toEqual(r2); - }); - - it("should build an object with numbered keys when an array-like object is found", function () { - var r = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: [4, 5], - c: {0: "m", 1: "o", 2: "o"}, - d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - expect(lamb.updatePath("b.c.0", lamb.always("m"))(obj)).toEqual(r); - expect(lamb.updatePathIn(obj, "b.c.0", lamb.always("m"))).toEqual(r); - }); - - it("should build an object with numbered keys when an array is found and the key is not a string containing an integer", function () { - var r = { - a: 2, - b: { - a: {g: 10, h: 11}, - b: {0: 4, 1: 5, z: 99}, - c: "foo", d: [, 55, ,] // eslint-disable-line comma-spacing, no-sparse-arrays - }, - "c.d": {"e.f": 6} - }; - - obj.b.b.z = 1; - - expect(lamb.updatePath("b.b.z", lamb.always(99))(obj)).toEqual(r); - expect(lamb.updatePathIn(obj, "b.b.z", lamb.always(99))).toEqual(r); - delete obj.b.b.z; - }); - - it("should not add a new property if the given path doesn't exist on the source, and return a copy of the source instead", function () { - var arr = [1]; - var newObjA = lamb.updatePathIn(obj, "b.a.z", lamb.always(99)); - var newObjB = lamb.updatePathIn(obj, "b.b.1.z", lamb.always(99)); - var newObjC = lamb.updatePath("xyz", lamb.always(99))(obj); - var newObjD = lamb.updatePathIn(obj, "b.b.-10", lamb.always(99)); - var newObjE = lamb.updatePath("xyz", lamb.always(99))(arr); - var newObjF = lamb.updatePath("x.y.z", lamb.always(99))(arr); - var newObjG = lamb.updatePath("1", lamb.always(99))(arr); - var newObjH = lamb.updatePath("-10", lamb.always(99))(arr); - - expect(newObjA).toEqual(obj); - expect(newObjA).not.toBe(obj); - expect(newObjB).toEqual(obj); - expect(newObjB).not.toBe(obj); - expect(newObjC).toEqual(obj); - expect(newObjC).not.toBe(obj); - expect(newObjD).toEqual(obj); - expect(newObjD).not.toBe(obj); - expect(newObjE).toEqual(arr); - expect(newObjE).not.toBe(arr); - expect(newObjF).toEqual(arr); - expect(newObjF).not.toBe(arr); - expect(newObjG).toEqual(arr); - expect(newObjG).not.toBe(arr); - expect(newObjH).toEqual(arr); - expect(newObjH).not.toBe(arr); - - var o = {a: null}; - - var newObjI = lamb.updatePath("a.b.c", lamb.always(99))(o); - var newObjJ = lamb.updatePathIn(o, "a.b.c", lamb.always(99)); - - expect(newObjI).toEqual(o); - expect(newObjI).not.toBe(o); - expect(newObjJ).toEqual(o); - expect(newObjJ).not.toBe(o); - }); - - it("should not see a non-existing path when the target is undefined", function () { - var fooObj = {a: {b: {c: 2, d: void 0}}}; - var r = {a: {b: {c: 2, d: 99}}}; - var fn99 = lamb.always(99); - - expect(lamb.updatePathIn(fooObj, "a.b.d", fn99)).toEqual(r); - expect(lamb.updatePath("a.b.d", fn99)(fooObj)).toEqual(r); - }); - - it("should return a copy of the source object when a non enumerable property is part of the path or its target", function () { - var fooObj = Object.create({}, { - a: {enumerable: true, value: 1}, - b: {value: {c: 2, d: {e: 3}}} - }); - - expect(lamb.updatePathIn(fooObj, "b", lamb.setKey("c", 99))).toEqual({a: 1}); - expect(lamb.updatePath("b", lamb.setKey("c", 99))(fooObj)).toEqual({a: 1}); - expect(lamb.updatePathIn(fooObj, "b.c", lamb.always(99))).toEqual({a: 1}); - expect(lamb.updatePath("b.d.e", lamb.always(99))(fooObj)).toEqual({a: 1}); - }); - - it("should accept integers as paths containing a single key", function () { - expect(lamb.updatePath(1, lamb.always(99))([1, 2, 3])).toEqual([1, 99, 3]); - expect(lamb.updatePathIn([1, 2, 3], -1, lamb.always(99))).toEqual([1, 2, 99]); - expect(lamb.updatePath(2, lamb.always(99))([1, 2])).toEqual([1, 2]); - }); - - it("should give priority to object keys over array indexes when a negative index is encountered", function () { - var o = {a: ["abc", "def", "ghi"]}; - - o.a["-1"] = "foo"; - - var r = {a: {0: "abc", 1: "def", 2: "ghi", "-1": 99}}; - - expect(lamb.updatePath("a.-1", lamb.always(99))(o)).toEqual(r); - expect(lamb.updatePathIn(o, "a.-1", lamb.always(99))).toEqual(r); - }); - - it("should consider a negative integer to be an index if the property exists but it's not enumerable", function () { - var o = {a: ["abc", "def", "ghi"]}; - - Object.defineProperty(o.a, "-1", {value: 99}); - - var r = {a: ["abc", "def", "GHI"]}; - - expect(lamb.updatePath("a.-1", toUpperCase)(o)).toEqual(r); - expect(lamb.updatePathIn(o, "a.-1", toUpperCase)).toEqual(r); - }); - - it("should convert other values for the `path` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - var expected = lamb.merge(testObj, {}); - - expected[String(key)] = 99; - - expect(lamb.updatePathIn(testObj, key, lamb.always(99), "_")).toEqual(expected); - expect(lamb.updatePath(key, lamb.always(99), "_")(testObj)).toEqual(expected); - }); - - var fooObj = {a: 2, 1: {5: 3}, undefined: 4}; - var r = {a: 2, 1: {5: 99}, undefined: 4}; - - expect(lamb.updatePathIn(fooObj, 1.5, lamb.always(99))).toEqual(r); - expect(lamb.updatePath(1.5, lamb.always(99))(fooObj)).toEqual(r); - }); - - it("should throw an exception if the `updater` isn't a function or if is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.updatePathIn({a: 2}, "a", value); }).toThrow(); - expect(function () { lamb.updatePath("a", value)({a: 2}); }).toThrow(); - }); - - expect(function () { lamb.updatePathIn({a: 2}, "a"); }).toThrow(); - expect(function () { lamb.updatePath("a")({a: 2}); }).toThrow(); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.updatePathIn).toThrow(); - expect(lamb.updatePath()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.updatePathIn(null, "a", lamb.always(99)); }).toThrow(); - expect(function () { lamb.updatePathIn(void 0, "a", lamb.always(99)); }).toThrow(); - expect(function () { lamb.updatePath("a", lamb.always(99))(null); }).toThrow(); - expect(function () { lamb.updatePath("a", lamb.always(99))(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.updatePathIn(value, "a", lamb.always(99))).toEqual({}); - expect(lamb.updatePath("a", lamb.always(99))(value)).toEqual({}); - expect(lamb.updatePathIn(value, "a.b.c", lamb.always(99))).toEqual({}); - expect(lamb.updatePath("a.b.c", lamb.always(99))(value)).toEqual({}); - }); - }); - }); - }); - }); -}); diff --git a/test/spec/arraySpec.js b/test/spec/arraySpec.js deleted file mode 100644 index 72553b2..0000000 --- a/test/spec/arraySpec.js +++ /dev/null @@ -1,1523 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; -var sparseArrayEquality = commons.equalities.sparseArrayEquality; - -var nonStrings = commons.vars.nonStrings; -var nonArrayLikes = commons.vars.nonArrayLikes; -var nonFunctions = commons.vars.nonFunctions; -var wannabeEmptyArrays = commons.vars.wannabeEmptyArrays; -var zeroesAsIntegers = commons.vars.zeroesAsIntegers; - -describe("lamb.array", function () { - // to check "truthy" and "falsy" values returned by predicates - var isVowel = function (char, idx, s) { - expect(s[idx]).toBe(char); - - return ~"aeiouAEIOU".indexOf(char); - }; - - beforeEach(function () { - jasmine.addCustomEqualityTester(sparseArrayEquality); - }); - - describe("append / appendTo", function () { - var arr = ["a", "b", "c", "d", "e"]; - var s = "abcde"; - var r1 = ["a", "b", "c", "d", "e", "z"]; - var r2 = ["a", "b", "c", "d", "e", ["z"]]; - var r3 = ["a", "b", "c", "d", "e", void 0]; - - afterEach(function () { - expect(arr).toEqual(["a", "b", "c", "d", "e"]); - }); - - it("should append a value at the end of a copy of the given array", function () { - expect(lamb.appendTo(arr, "z")).toEqual(r1); - expect(lamb.append("z")(arr)).toEqual(r1); - expect(lamb.appendTo(arr, ["z"])).toEqual(r2); - expect(lamb.append(["z"])(arr)).toEqual(r2); - }); - - it("should accept array-like objects", function () { - expect(lamb.appendTo(s, "z")).toEqual(r1); - expect(lamb.append("z")(s)).toEqual(r1); - expect(lamb.appendTo(s, ["z"])).toEqual(r2); - expect(lamb.append(["z"])(s)).toEqual(r2); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.appendTo([1, , , 4], 5)).toEqual([1, void 0, void 0, 4, 5]); - expect(lamb.append(5)([1, , , 4])).toEqual([1, void 0, void 0, 4, 5]); - /* eslint-enable no-sparse-arrays */ - }); - - it("should append an `undefined` value when the `value` parameter is missing", function () { - expect(lamb.appendTo(arr)).toEqual(r3); - expect(lamb.append()(arr)).toEqual(r3); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.appendTo).toThrow(); - expect(lamb.append("z")).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.appendTo(null, "z"); }).toThrow(); - expect(function () { lamb.appendTo(void 0, "z"); }).toThrow(); - expect(function () { lamb.append("z")(null); }).toThrow(); - expect(function () { lamb.append("z")(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.appendTo(value, "z")).toEqual(["z"]); - expect(lamb.append("z")(value)).toEqual(["z"]); - }); - }); - }); - - describe("difference", function () { - var a1 = [0, 1, 2, 3, 4, NaN]; - var a2 = [-0, 2, 3, 4, 5, NaN]; - var a3 = [4, 5, 1, 4, 5]; - var a4 = [6, 7]; - - it("should return a new array with the items present only in the first of the two arrays", function () { - var r = lamb.difference(a1, a4); - - expect(r).toEqual(a1); - expect(r).not.toBe(a1); - expect(lamb.difference(a1, a3)).toEqual([0, 2, 3, NaN]); - }); - - it("should use the \"SameValueZero\" comparison and keep the first encountered value in case of equality", function () { - expect(lamb.difference(a1, a2)).toEqual([1]); - expect(lamb.difference([-0, 1, 0, 2], [1, 3, 2])).toEqual([-0]); - }); - - it("should return an array without duplicates", function () { - expect(lamb.difference(a3, a4)).toEqual([4, 5, 1]); - expect(lamb.difference(a3, a1)).toEqual([5]); - }); - - it("should work with array-like objects", function () { - expect(lamb.difference("abc", "bd")).toEqual(["a", "c"]); - expect(lamb.difference(["a", "b", "c"], "bd")).toEqual(["a", "c"]); - expect(lamb.difference("abc", ["b", "d"])).toEqual(["a", "c"]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.difference).toThrow(); - }); - - it("should throw an exception when a parameter is `null` or `undefined`", function () { - expect(function () { lamb.difference(null, a4); }).toThrow(); - expect(function () { lamb.difference(void 0, a4); }).toThrow(); - expect(function () { lamb.difference(a4, null); }).toThrow(); - expect(function () { lamb.difference(a4, void 0); }).toThrow(); - expect(function () { lamb.difference(a4); }).toThrow(); - }); - - it("should treat every other value in the main parameter as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.difference(value, a4)).toEqual([]); - }); - }); - - it("should treat every non-array-like value in the `other` parameter as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.difference(a1, value)).toEqual(a1); - expect(lamb.difference(wannabeEmptyArrays, value)).toEqual(lamb.uniques(wannabeEmptyArrays)); - }); - }); - }); - - describe("drop / dropFrom", function () { - it("should drop the first `n` elements of an array or array-like object", function () { - expect(lamb.dropFrom(["a", "b"], 1)).toEqual(["b"]); - expect(lamb.drop(3)([1, 2, 3, 4])).toEqual([4]); - }); - - it("should work with array-like objects", function () { - expect(lamb.dropFrom("abcd", 2)).toEqual(["c", "d"]); - expect(lamb.drop(2)("abcd")).toEqual(["c", "d"]); - }); - - it("should accept a negative `n`", function () { - expect(lamb.dropFrom([1, 2, 3, 4], -1)).toEqual([4]); - expect(lamb.drop(-3)("abcd")).toEqual(["b", "c", "d"]); - }); - - it("should return a copy of the source array when `n` is 0 or less or equal than the additive inverse of the array-like length", function () { - expect(lamb.dropFrom(["a", "b"], 0)).toEqual(["a", "b"]); - expect(lamb.dropFrom([1, 2, 3, 4], -4)).toEqual([1, 2, 3, 4]); - expect(lamb.drop(-10)([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); - }); - - it("should return an empty array when `n` is greater than or equal to the array-like length", function () { - expect(lamb.dropFrom([1, 2, 3, 4], 4)).toEqual([]); - expect(lamb.dropFrom([1, 2, 3, 4], 5)).toEqual([]); - expect(lamb.drop(10)([1, 2, 3, 4])).toEqual([]); - }); - - it("should convert to integer the value received as `n`", function () { - var arr = [1, 2, 3, 4, 5]; - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.drop(value)(arr)).toEqual(arr); - expect(lamb.dropFrom(arr, value)).toEqual(arr); - }); - - [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { - expect(lamb.drop(value)(arr)).toEqual([2, 3, 4, 5]); - expect(lamb.dropFrom(arr, value)).toEqual([2, 3, 4, 5]); - }); - - expect(lamb.drop(new Date())(arr)).toEqual([]); - expect(lamb.dropFrom(arr, new Date())).toEqual([]); - - expect(lamb.drop()(arr)).toEqual(arr); - expect(lamb.dropFrom(arr)).toEqual(arr); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.dropFrom([1, , 3], 1)).toEqual([void 0, 3]); - expect(lamb.drop(1)([1, , 3])).toEqual([void 0, 3]); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if called without the data argument or without arguments at all", function () { - expect(lamb.dropFrom).toThrow(); - expect(lamb.drop(1)).toThrow(); - expect(lamb.drop()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.dropFrom(null, 0); }).toThrow(); - expect(function () { lamb.dropFrom(void 0, 0); }).toThrow(); - expect(function () { lamb.drop(0)(null); }).toThrow(); - expect(function () { lamb.drop(0)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.dropFrom(value, 0)).toEqual([]); - expect(lamb.drop(0)(value)).toEqual([]); - }); - }); - }); - - describe("dropWhile", function () { - var isEven = function (n, idx, list) { - expect(list[idx]).toBe(n); - - return n % 2 === 0; - }; - var dropWhileIsEven = lamb.dropWhile(isEven); - - it("should build a function that drops the first n elements satisfying a predicate from an array or array-like object", function () { - expect(dropWhileIsEven([])).toEqual([]); - expect(dropWhileIsEven([2, 4, 6, 8])).toEqual([]); - expect(dropWhileIsEven([2, 3, 4, 6, 8])).toEqual([3, 4, 6, 8]); - expect(dropWhileIsEven([2, 4, 6, 7, 8])).toEqual([7, 8]); - expect(dropWhileIsEven([1, 3, 5, 7])).toEqual([1, 3, 5, 7]); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - var dropWhileisVowel = lamb.dropWhile(isVowel); - - expect(dropWhileisVowel("aiuola")).toEqual(["l", "a"]); - }); - - it("should build a function throwing an exception if the predicate isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.dropWhile(value)([1, 2]); }).toThrow(); - }); - - expect(function () { lamb.dropWhile()([1, 2]); }).toThrow(); - }); - - it("should always return dense arrays", function () { - var dropWhileIsUndefined = lamb.dropWhile(lamb.isUndefined); - - /* eslint-disable no-sparse-arrays */ - expect(dropWhileIsEven([2, 4, , , 5])).toEqual([void 0, void 0, 5]); - expect(dropWhileIsUndefined([, , void 0, , void 0, 5, 6])).toEqual([5, 6]); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if called without the data argument", function () { - expect(dropWhileIsEven).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { dropWhileIsEven(null); }).toThrow(); - expect(function () { dropWhileIsEven(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(dropWhileIsEven(value)).toEqual([]); - }); - }); - }); - - describe("flatMap / flatMapWith", function () { - it("should behave like map if the mapping function returns a non-array value", function () { - var double = function (n, idx, list) { - expect(list).toBe(arr); - expect(list[idx]).toBe(n); - - return n * 2; - }; - var arr = [1, 2, 3, 4, 5]; - var result = [2, 4, 6, 8, 10]; - - expect(lamb.flatMap(arr, double)).toEqual(result); - expect(lamb.flatMapWith(double)(arr)).toEqual(result); - }); - - it("should concatenate arrays returned by the mapping function to the result", function () { - var splitString = function (s) { - return s.split(""); - }; - var arr = ["foo", "bar", "baz"]; - var result = ["f", "o", "o", "b", "a", "r", "b", "a", "z"]; - - expect(lamb.flatMap(arr, splitString)).toEqual(result); - expect(lamb.flatMapWith(splitString)(arr)).toEqual(result); - }); - - it("should keeps its behaviour consistent even if the received function returns mixed results", function () { - var fn = function (n, idx, list) { - expect(list).toBe(arr); - expect(list[idx]).toBe(n); - - return n % 2 === 0 ? [n, n + 10] : n * 2; - }; - var arr = [1, 2, 3, 4, 5]; - var result = [2, 2, 12, 6, 4, 14, 10]; - - expect(lamb.flatMap(arr, fn)).toEqual(result); - expect(lamb.flatMapWith(fn)(arr)).toEqual(result); - }); - - it("should not flatten nested arrays", function () { - var splitString = function (s) { - return String(s) === s ? s.split("") : [[s, "not a string"]]; - }; - var arr = ["foo", "bar", 5, "baz"]; - var result = ["f", "o", "o", "b", "a", "r", [5, "not a string"], "b", "a", "z"]; - - expect(lamb.flatMap(arr, splitString)).toEqual(result); - expect(lamb.flatMapWith(splitString)(arr)).toEqual(result); - - var arr2 = ["foo", ["bar", ["baz"]]]; - var result2 = ["foo", "bar", ["baz"]]; - - expect(lamb.flatMap(arr2, lamb.identity)).toEqual(result2); - expect(lamb.flatMapWith(lamb.identity)(arr2)).toEqual(result2); - }); - - it("should work on array-like objects", function () { - var toUpperCase = lamb.generic(String.prototype.toUpperCase); - var testString = "hello world"; - var result = ["H", "E", "L", "L", "O", " ", "W", "O", "R", "L", "D"]; - - expect(lamb.flatMap(testString, toUpperCase)).toEqual(result); - expect(lamb.flatMapWith(toUpperCase)(testString)).toEqual(result); - }); - - it("should always return dense arrays", function () { - /* eslint-disable comma-spacing, no-sparse-arrays */ - var fn = function (v) { return [, v, ,]; }; - var arr = [1, , 3]; - /* eslint-enable comma-spacing, no-sparse-arrays */ - - var result = [void 0, 1, void 0, void 0, void 0, void 0, void 0, 3, void 0]; - - expect(lamb.flatMap(arr, fn)).toEqual(result); - expect(lamb.flatMapWith(fn)(arr)).toEqual(result); - }); - - it("should throw an exception if not supplied with a mapper function", function () { - expect(function () {lamb.flatMap([1, 2, 3]);}).toThrow(); - expect(function () {lamb.flatMapWith()([1, 2, 3]);}).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.flatMap).toThrow(); - expect(lamb.flatMapWith(lamb.identity)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.flatMap(null, lamb.identity); }).toThrow(); - expect(function () { lamb.flatMap(void 0, lamb.identity); }).toThrow(); - expect(function () { lamb.flatMapWith(lamb.identity)(null); }).toThrow(); - expect(function () { lamb.flatMapWith(lamb.identity)(void 0); }).toThrow(); - }); - - it("should return an empty array for every other value", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.flatMap(value, lamb.identity)).toEqual([]); - expect(lamb.flatMapWith(lamb.identity)(value)).toEqual([]); - }); - }); - }); - - describe("flatten", function () { - it("should return a deep flattened array", function () { - var a1 = [[1, [2, [3, 4, ["a", ["b", ["c"]]]]]]]; - var a2 = [1, 2, [3, 4, [5, 6]], 7, 8]; - - expect(lamb.flatten(a1)).toEqual([1, 2, 3, 4, "a", "b", "c"]); - expect(lamb.flatten(a2)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); - }); - - it("shouldn't flatten an array that is a value in an object", function () { - var input = [["a", ["b", [{c: ["d"]}]]]]; - - expect(lamb.flatten(input)).toEqual(["a", "b", {c: ["d"]}]); - }); - - it("should return an array copy of the source object if supplied with an array-like", function () { - expect(lamb.flatten("foo")).toEqual(["f", "o", "o"]); - }); - - it("should always return dense arrays", function () { - // eslint-disable-next-line no-sparse-arrays - var arr = [1, [2, , 4], , [6, , [8, , 10]]]; - var result = [1, 2, void 0, 4, void 0, 6, void 0, 8, void 0, 10]; - - expect(lamb.flatten(arr)).toEqual(result); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.flatten).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.flatten(null); }).toThrow(); - expect(function () { lamb.flatten(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.flatten(value)).toEqual([]); - }); - }); - }); - - describe("init", function () { - it("should return a copy of the given array-like object without the last element", function () { - var arr = [1, 2, 3, 4, 5]; - - expect(lamb.init(arr)).toEqual([1, 2, 3, 4]); - expect(arr.length).toBe(5); - expect(lamb.init("hello")).toEqual(["h", "e", "l", "l"]); - }); - - it("should return an empty array when called with an empty array or an array holding only one element", function () { - expect(lamb.init([1])).toEqual([]); - expect(lamb.init([])).toEqual([]); - }); - - it("should always return dense arrays", function () { - // eslint-disable-next-line no-sparse-arrays - expect(lamb.init([1, , 3, , 4])).toEqual([1, void 0, 3, void 0]); - expect(lamb.init(Array(2))).toEqual([void 0]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.init).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.init(null); }).toThrow(); - expect(function () { lamb.init(void 0); }).toThrow(); - }); - - it("should return an empty array for every other value", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.init(value)).toEqual([]); - }); - }); - }); - - describe("insert / insertAt", function () { - var arr = [1, 2, 3, 4, 5]; - var result1 = [1, 2, 3, 99, 4, 5]; - var result2 = [99, 1, 2, 3, 4, 5]; - var result3 = [1, 2, 3, 4, 5, 99]; - - afterEach(function () { - expect(arr).toEqual([1, 2, 3, 4, 5]); - }); - - it("should allow to insert an element in a copy of an array at the specified index", function () { - var r1 = lamb.insert(arr, 3, 99); - var r2 = lamb.insertAt(3, 99)(arr); - - expect(r1).toEqual(result1); - expect(r2).toEqual(result1); - expect(r1).not.toBe(arr); - expect(r2).not.toBe(arr); - }); - - it("should insert the element at the end of the array if the provided index is greater than its length", function () { - expect(lamb.insert(arr, 99, 99)).toEqual(result3); - expect(lamb.insertAt(99, 99)(arr)).toEqual(result3); - }); - - it("should allow the use of negative indexes", function () { - expect(lamb.insert(arr, -2, 99)).toEqual(result1); - expect(lamb.insertAt(-2, 99)(arr)).toEqual(result1); - }); - - it("should insert the element at the start of the array if provided with a negative index which is out of bounds", function () { - expect(lamb.insert(arr, -99, 99)).toEqual(result2); - expect(lamb.insertAt(-99, 99)(arr)).toEqual(result2); - }); - - it("should convert the index to integer following ECMA specifications", function () { - // see https://www.ecma-international.org/ecma-262/7.0/#sec-tointeger - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.insert(arr, value, 99)).toEqual(result2); - expect(lamb.insertAt(value, 99)(arr)).toEqual(result2); - }); - - [[3], -2, 3.6, 3.2, -2.8, -2.2, "-2", "3"].forEach(function (value) { - expect(lamb.insert(arr, value, 99)).toEqual(result1); - expect(lamb.insertAt(value, 99)(arr)).toEqual(result1); - }); - - expect(lamb.insert(arr, new Date(), 99)).toEqual(result3); - expect(lamb.insertAt(new Date(), 99)(arr)).toEqual(result3); - }); - - it("should work with array-like objects", function () { - var s = "12345"; - - expect(lamb.insert(s, -2, "99")).toEqual(result1.map(String)); - expect(lamb.insertAt(3, "99")(s)).toEqual(result1.map(String)); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.insert([1, , 3, 5], 3, 4)).toEqual([1, void 0, 3, 4, 5]); - expect(lamb.insertAt(3, 4)([1, , 3, 5])).toEqual([1, void 0, 3, 4, 5]); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.insert).toThrow(); - expect(lamb.insertAt(3, "99")).toThrow(); - }); - - it("should throw an exception if a `nil` value is passed in place of an array-like object", function () { - expect(function () { lamb.insert(null, 3, 99); }).toThrow(); - expect(function () { lamb.insert(void 0, 3, 99); }).toThrow(); - expect(function () { lamb.insertAt(3, 99)(null); }).toThrow(); - expect(function () { lamb.insertAt(3, 99)(void 0); }).toThrow(); - }); - - it("should consider every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.insert(value, 3, 99)).toEqual([99]); - expect(lamb.insertAt(3, 99)(value)).toEqual([99]); - }); - }); - }); - - describe("intersection", function () { - var a1 = [0, 1, 2, 3, 4, NaN]; - var a2 = [-0, 2, 2, 3, 4, 5]; - var a3 = [4, 5, 1, NaN]; - var a4 = [6, 7]; - - it("should return an array of every unique item present in all two given arrays", function () { - expect(lamb.intersection(a2, a3)).toEqual([4, 5]); - - expect(lamb.intersection( - a1, - lamb.intersection(a2, a3) - )).toEqual([4]); - - expect(lamb.intersection( - lamb.intersection(a1, a2), - a3 - )).toEqual([4]); - - expect(lamb.intersection( - lamb.intersection(a1, a2), - lamb.intersection(a3, a4) - )).toEqual([]); - }); - - it("should use the SameValueZero comparison and put in the result the first value found when comparing `0` with `-0`", function () { - expect(lamb.intersection(a1, a3)).toEqual([1, 4, NaN]); - expect(lamb.intersection(a2, a1)).toEqual([-0, 2, 3, 4]); - expect(Object.is(-0, lamb.intersection(a2, a1)[0])).toBe(true); - expect(lamb.intersection(a1, a2)).toEqual([0, 2, 3, 4]); - expect(Object.is(0, lamb.intersection(a1, a2)[0])).toBe(true); - }); - - it("should accept array-like objects", function () { - expect(lamb.intersection( - lamb.intersection("123", "23"), - lamb.intersection("432", "321") - )).toEqual(["2", "3"]); - expect(lamb.intersection( - ["1", "2"], - lamb.intersection("23", "42") - )).toEqual(["2"]); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - - expect(lamb.intersection( - lamb.intersection([1, , 3], [void 0, 3]), - [3, , 4] - )).toEqual([void 0, 3]); - - /* eslint-enable no-sparse-arrays */ - - expect(lamb.intersection(Array(2), Array(3))).toEqual([void 0]); - }); - - it("should throw an exception if any of the array-like is `null` or `undefined`", function () { - expect(function () { lamb.intersection(null, [1, 2]); }).toThrow(); - expect(function () { lamb.intersection([1, 2], void 0); }).toThrow(); - - expect(lamb.intersection).toThrow(); - }); - - it("should treat other values as empty arrays", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.intersection([2, 3], value)).toEqual([]); - expect(lamb.intersection(value, [2, 3])).toEqual([]); - }); - }); - }); - - describe("partition / partitionWith", function () { - it("should split an array in two lists; one with the elements satisfying the predicate, the other with the remaining elements", function () { - var persons = [ - {name: "Jane", surname: "Doe", active: false}, - {name: "John", surname: "Doe", active: true}, - {name: "Mario", surname: "Rossi", active: true}, - {name: "Paolo", surname: "Bianchi", active: false} - ]; - - var fooIsActive = function (el, idx, list) { - expect(list[idx]).toBe(el); - expect(list).toBe(persons); - - return el.active; - }; - - var result = [[ - {name: "John", surname: "Doe", active: true}, - {name: "Mario", surname: "Rossi", active: true} - ], [ - {name: "Jane", surname: "Doe", active: false}, - {name: "Paolo", surname: "Bianchi", active: false} - ]]; - - expect(lamb.partition(persons, fooIsActive)).toEqual(result); - expect(lamb.partitionWith(fooIsActive)(persons)).toEqual(result); - }); - - it("should return two lists even when the predicate is always satisfied or never satisfied", function () { - var isGreaterThanTen = lamb.isGT(10); - var arr1 = [1, 2, 3, 4, 5]; - var arr2 = [11, 12, 13, 14, 15]; - var arr3 = []; - var res1 = [[], arr1.concat()]; - var res2 = [arr2.concat(), []]; - var res3 = [[], []]; - - expect(lamb.partition(arr1, isGreaterThanTen)).toEqual(res1); - expect(lamb.partitionWith(isGreaterThanTen)(arr1)).toEqual(res1); - expect(lamb.partition(arr2, isGreaterThanTen)).toEqual(res2); - expect(lamb.partitionWith(isGreaterThanTen)(arr2)).toEqual(res2); - expect(lamb.partition(arr3, isGreaterThanTen)).toEqual(res3); - expect(lamb.partitionWith(isGreaterThanTen)(arr3)).toEqual(res3); - }); - - it("should work with array-like objects and treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - var testString = "Hello world"; - var result = [["e", "o", "o"], ["H", "l", "l", " ", "w", "r", "l", "d"]]; - - expect(lamb.partition(testString, isVowel)).toEqual(result); - expect(lamb.partitionWith(isVowel)(testString)).toEqual(result); - }); - - it("should always return dense arrays", function () { - var sparseArr = [1, , 3, , 5]; // eslint-disable-line no-sparse-arrays - - expect(lamb.partition(sparseArr, lamb.isUndefined)).toEqual([[void 0, void 0], [1, 3, 5]]); - expect(lamb.partitionWith(lamb.isUndefined)(sparseArr)).toEqual([[void 0, void 0], [1, 3, 5]]); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.partition).toThrow(); - expect(lamb.partitionWith(lamb.identity)).toThrow(); - }); - - it("should throw an exception when the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.partition([1, 2], value); }).toThrow(); - expect(function () { lamb.partitionWith(value)([1, 2]); }).toThrow(); - }); - - expect(function () { lamb.partition([1, 2]); }).toThrow(); - expect(function () { lamb.partitionWith()([1, 2]); }).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.partition(null); }).toThrow(); - expect(function () { lamb.partition(void 0); }).toThrow(); - expect(function () { lamb.partitionWith(lamb.identity)(null); }).toThrow(); - expect(function () { lamb.partitionWith(lamb.identity)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.partition(value, lamb.isType("Number"))).toEqual([[], []]); - expect(lamb.partitionWith(lamb.isType("Number"))(value)).toEqual([[], []]); - }); - }); - }); - - describe("pluck / pluckKey", function () { - var arr = [ - {bar: 1, baz: 2, qux: 3}, - {bar: 34, baz: 22, qux: 73}, - {bar: 45, baz: 21, qux: 83}, - {bar: 65, baz: 92, qux: 39} - ]; - - var lists = [[1, 2], [3, 4, 5], [6]]; - var s = "hello"; - - it("should return an array of values taken from the given property of the source array elements", function () { - expect(lamb.pluck(arr, "baz")).toEqual([2, 22, 21, 92]); - expect(lamb.pluckKey("baz")(arr)).toEqual([2, 22, 21, 92]); - expect(lamb.pluck(lists, "length")).toEqual([2, 3, 1]); - expect(lamb.pluckKey("length")(lists)).toEqual([2, 3, 1]); - }); - - it("should work with array-like objects", function () { - expect(lamb.pluck(s, "length")).toEqual([1, 1, 1, 1, 1]); - expect(lamb.pluckKey("length")(s)).toEqual([1, 1, 1, 1, 1]); - }); - - it("should return a list of undefined values if no property is specified or if the property doesn't exist", function () { - var r = [void 0, void 0, void 0, void 0]; - - nonStrings.forEach(function (value) { - expect(lamb.pluck(arr, value)).toEqual(r); - expect(lamb.pluckKey(value)(arr)).toEqual(r); - }); - - expect(lamb.pluck(arr, "foo")).toEqual(r); - expect(lamb.pluckKey("foo")(arr)).toEqual(r); - - expect(lamb.pluck(arr)).toEqual(r); - expect(lamb.pluckKey()(arr)).toEqual(r); - }); - - it("should not skip deleted or unassigned indexes in the array-like and throw an exception", function () { - var a = [, {bar: 2}]; // eslint-disable-line no-sparse-arrays - - expect(function () { lamb.pluck(a, "bar"); }).toThrow(); - expect(function () { lamb.pluckKey("bar")(a); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.pluck).toThrow(); - expect(lamb.pluckKey("bar")).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.pluck(null, "bar"); }).toThrow(); - expect(function () { lamb.pluck(void 0, "bar"); }).toThrow(); - expect(function () { lamb.pluckKey("bar")(null); }).toThrow(); - expect(function () { lamb.pluckKey("bar")(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.pluck(value, "bar")).toEqual([]); - expect(lamb.pluckKey("bar")(value)).toEqual([]); - }); - }); - }); - - describe("pull / pullFrom", function () { - it("should create a copy of the given array without the specified values", function () { - var arr = ["foo", "bar", "baz"]; - - expect(lamb.pullFrom(arr, ["foo", "baz"])).toEqual(["bar"]); - expect(lamb.pull(["foo", "baz"])(arr)).toEqual(["bar"]); - - var r1 = lamb.pullFrom(arr, []); - var r2 = lamb.pull([])(arr); - - expect(r1).toEqual(arr); - expect(r1).not.toBe(arr); - expect(r2).toEqual(arr); - expect(r2).not.toBe(arr); - expect(arr).toEqual(["foo", "bar", "baz"]); - }); - - it("should use the \"SameValueZero\" comparison to perform the equality test", function () { - var obj = {a: 1}; - var arr = [1, 2]; - var mixed = ["bar", obj, arr, "5", 0, NaN, "foo", 5]; - var result = ["bar", "foo", 5]; - var values = [obj, arr, NaN, -0, "5"]; - - expect(lamb.pullFrom(mixed, values)).toEqual(result); - expect(lamb.pull(values)(mixed)).toEqual(result); - }); - - it("should accept array-like objects as the list we want to remove values from", function () { - expect(lamb.pullFrom("hello", ["h", "l"])).toEqual(["e", "o"]); - expect(lamb.pull(["h", "l"])("hello")).toEqual(["e", "o"]); - }); - - it("should accept array-like objects as the list of values we want to remove", function () { - var arr = ["f", "b", "o", "o", "a", "r"]; - var result = ["f", "o", "o"]; - - expect(lamb.pullFrom(arr, "bar")).toEqual(result); - expect(lamb.pull("bar")(arr)).toEqual(result); - }); - - it("should consider non-array-likes received as the list of values as an empty array", function () { - var arr = [1, 2, 3]; - - nonArrayLikes.forEach(function (value) { - var r1 = lamb.pullFrom(arr, value); - var r2 = lamb.pull(value)(arr); - - expect(r1).toEqual(arr); - expect(r1).not.toBe(arr); - expect(r2).toEqual(arr); - expect(r2).not.toBe(arr); - }); - }); - - /* eslint-disable no-sparse-arrays */ - - it("should be able to remove non assigned indexes from sparse arrays", function () { - expect(lamb.pullFrom([1, , 3, , 5], [void 0])).toEqual([1, 3, 5]); - expect(lamb.pull([void 0])([1, , 3, , 5])).toEqual([1, 3, 5]); - }); - - it("should always return dense arrays", function () { - expect(lamb.pullFrom([1, , 3, , 5], [3])).toEqual([1, void 0, void 0, 5]); - expect(lamb.pull([3])([1, , 3, , 5])).toEqual([1, void 0, void 0, 5]); - }); - - it("should accept sparse arrays as the list of values to remove", function () { - expect(lamb.pullFrom([1, , 3, , 5], [1, , 1])).toEqual([3, 5]); - expect(lamb.pullFrom([1, void 0, 3, void 0, 5], [1, , 1])).toEqual([3, 5]); - expect(lamb.pull([1, , 1])([1, , 3, , 5])).toEqual([3, 5]); - expect(lamb.pull([1, void 0, 1])([1, , 3, , 5])).toEqual([3, 5]); - }); - - /* eslint-enable no-sparse-arrays */ - - it("should throw an exception if called without arguments", function () { - expect(lamb.pullFrom).toThrow(); - expect(lamb.pull()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.pullFrom(null, ["foo"]); }).toThrow(); - expect(function () { lamb.pullFrom(void 0, ["foo"]); }).toThrow(); - expect(function () { lamb.pull(["foo"])(null); }).toThrow(); - expect(function () { lamb.pull(["foo"])(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.pullFrom(value, ["lastIndex", "getDay", "valueOf"])).toEqual([]); - expect(lamb.pull(["lastIndex", "getDay", "valueOf"])(value)).toEqual([]); - }); - }); - }); - - describe("rotate", function () { - var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - - afterEach(function () { - expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - }); - - it("should rotate the values of the array by the desired amount", function () { - var r1 = [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var r2 = [7, 8, 9, 10, 1, 2, 3, 4, 5, 6]; - - expect(lamb.rotate(arr, 1)).toEqual(r1); - expect(lamb.rotateBy(1)(arr)).toEqual(r1); - expect(lamb.rotate(arr, 4)).toEqual(r2); - expect(lamb.rotateBy(4)(arr)).toEqual(r2); - expect(lamb.rotate([1], 1)).toEqual([1]); - expect(lamb.rotateBy(1)([1])).toEqual([1]); - }); - - it("should return a copy of the source array if the `amount` is zero or equal to the array length or its additive inverse", function () { - expect(lamb.rotate(arr, 0)).toEqual(arr); - expect(lamb.rotateBy(0)(arr)).toEqual(arr); - expect(lamb.rotate(arr, 10)).toEqual(arr); - expect(lamb.rotateBy(10)(arr)).toEqual(arr); - expect(lamb.rotate(arr, -10)).toEqual(arr); - expect(lamb.rotateBy(-10)(arr)).toEqual(arr); - }); - - it("should accept values greater than the array length or less than its additive inverse and continue \"rotating\" values", function () { - var r1 = [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]; - var r2 = [5, 6, 7, 8, 9, 10, 1, 2, 3, 4]; - - expect(lamb.rotate(arr, 13)).toEqual(r1); - expect(lamb.rotateBy(13)(arr)).toEqual(r1); - expect(lamb.rotate(arr, -14)).toEqual(r2); - expect(lamb.rotateBy(-14)(arr)).toEqual(r2); - }); - - it("should convert to integer the value received as `amount`", function () { - var d = new Date(); - var r1 = [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { - expect(lamb.rotate(arr, value)).toEqual(r1); - expect(lamb.rotateBy(value)(arr)).toEqual(r1); - }); - - expect(lamb.rotate(arr, d)).toEqual(lamb.rotate(arr, d % arr.length)); - expect(lamb.rotateBy(d)(arr)).toEqual(lamb.rotate(arr, d % arr.length)); - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.rotate(arr, value)).toEqual(arr); - expect(lamb.rotateBy(value)(arr)).toEqual(arr); - }); - - expect(lamb.rotate(arr)).toEqual(arr); - expect(lamb.rotateBy()(arr)).toEqual(arr); - }); - - it("should work with array-like objects", function () { - var s = "123456789"; - var r = ["7", "8", "9", "1", "2", "3", "4", "5", "6"]; - - expect(lamb.rotate(s, 3)).toEqual(r); - expect(lamb.rotateBy(3)(s)).toEqual(r); - }); - - it("should always return dense arrays", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - var sparse = [1, , 3, ,]; - var r = [void 0, 3, void 0, 1]; - - expect(lamb.rotate(sparse, 3)).toEqual(r); - expect(lamb.rotateBy(3)(sparse)).toEqual(r); - }); - - it("should throw an exception if called without an array-like", function () { - expect(lamb.rotate).toThrow(); - expect(lamb.rotateBy(10)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.rotate(null, 3); }).toThrow(); - expect(function () { lamb.rotateBy(3)(null); }).toThrow(); - expect(function () { lamb.rotate(void 0, 5); }).toThrow(); - expect(function () { lamb.rotateBy(3)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.rotate(value, 2)).toEqual([]); - expect(lamb.rotateBy(1)(value)).toEqual([]); - }); - }); - }); - - describe("shallowFlatten", function () { - it("should return a shallow flattened array", function () { - var a1 = [[1, [2, [3, ["a", ["b", ["c"]]]]]]]; - var a2 = [1, 2, [3, 4, [5, 6]], 7, 8]; - - expect(lamb.shallowFlatten(a1)).toEqual([1, [2, [3, ["a", ["b", ["c"]]]]]]); - expect(lamb.shallowFlatten(a2)).toEqual([1, 2, 3, 4, [5, 6], 7, 8]); - }); - - it("shouldn't flatten an array that is a value in an object", function () { - var input = ["a", "b", {c: ["d"]}]; - - expect(lamb.shallowFlatten(input)).toEqual(input); - }); - - it("should return an array copy of the source object if supplied with an array-like", function () { - expect(lamb.shallowFlatten("foo")).toEqual(["f", "o", "o"]); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - var arr = [1, [2, , 4], , [6, , [8, , 10]]]; - var result = [1, 2, void 0, 4, void 0, 6, void 0, [8, , 10]]; - /* eslint-enable no-sparse-arrays */ - - expect(lamb.shallowFlatten(arr)).toEqual(result); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.shallowFlatten).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.shallowFlatten(null); }).toThrow(); - expect(function () { lamb.shallowFlatten(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.shallowFlatten(value)).toEqual([]); - }); - }); - }); - - describe("tail", function () { - it("should return a copy of the given array-like object without the first element", function () { - var arr = [1, 2, 3, 4, 5]; - - expect(lamb.tail(arr)).toEqual([2, 3, 4, 5]); - expect(arr.length).toBe(5); - expect(lamb.tail("shell")).toEqual(["h", "e", "l", "l"]); - }); - - it("should return an empty array when called with an empty array or an array holding only one element", function () { - expect(lamb.tail([1])).toEqual([]); - expect(lamb.tail([])).toEqual([]); - }); - - it("should always return dense arrays", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - expect(lamb.tail([1, , 3, ,])).toEqual([void 0, 3, void 0]); - expect(lamb.tail(Array(2))).toEqual([void 0]); - expect(lamb.tail(Array(1))).toEqual([]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.tail).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.tail(null); }).toThrow(); - expect(function () { lamb.tail(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.tail(value)).toEqual([]); - }); - }); - }); - - describe("take / takeFrom", function () { - it("should retrieve the first `n` elements of an array or array-like object", function () { - expect(lamb.takeFrom(["a", "b"], 1)).toEqual(["a"]); - expect(lamb.take(3)([1, 2, 3, 4])).toEqual([1, 2, 3]); - }); - - it("should work with array-like objects", function () { - expect(lamb.takeFrom("abcd", 2)).toEqual(["a", "b"]); - expect(lamb.take(2)("abcd")).toEqual(["a", "b"]); - }); - - it("should accept a negative `n`", function () { - expect(lamb.takeFrom([1, 2, 3, 4], -1)).toEqual([1, 2, 3]); - expect(lamb.take(-3)("abcd")).toEqual(["a"]); - }); - - it("should return a copy of the source array when `n` is greater than or equal to the array-like length", function () { - expect(lamb.takeFrom(["a", "b"], 3)).toEqual(["a", "b"]); - expect(lamb.takeFrom([1, 2, 3, 4], 4)).toEqual([1, 2, 3, 4]); - expect(lamb.take(10)([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); - }); - - it("should return an empty array when `n` is 0 or less or equal than the additive inverse of the array-like length", function () { - expect(lamb.takeFrom([1, 2, 3, 4], 0)).toEqual([]); - expect(lamb.take(-4)([1, 2, 3, 4])).toEqual([]); - expect(lamb.take(-10)([1, 2, 3, 4])).toEqual([]); - }); - - it("should convert to integer the value received as `n`", function () { - var arr = [1, 2, 3, 4, 5]; - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.take(value)(arr)).toEqual([]); - expect(lamb.takeFrom(arr, value)).toEqual([]); - }); - - [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { - expect(lamb.take(value)(arr)).toEqual([1]); - expect(lamb.takeFrom(arr, value)).toEqual([1]); - }); - - expect(lamb.take(new Date())(arr)).toEqual(arr); - expect(lamb.takeFrom(arr, new Date())).toEqual(arr); - - expect(lamb.take()(arr)).toEqual([]); - expect(lamb.takeFrom(arr)).toEqual([]); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.takeFrom([1, , 3], 2)).toEqual([1, void 0]); - expect(lamb.take(2)([1, , 3])).toEqual([1, void 0]); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if called without the data argument or without arguments at all", function () { - expect(lamb.takeFrom).toThrow(); - expect(lamb.take(1)).toThrow(); - expect(lamb.take()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.takeFrom(null, 0); }).toThrow(); - expect(function () { lamb.takeFrom(void 0, 0); }).toThrow(); - expect(function () { lamb.take(0)(null); }).toThrow(); - expect(function () { lamb.take(0)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.takeFrom(value, 0)).toEqual([]); - expect(lamb.take(0)(value)).toEqual([]); - }); - }); - }); - - describe("takeWhile", function () { - var isEven = function (n, idx, list) { - expect(list[idx]).toBe(n); - - return n % 2 === 0; - }; - var takeWhileIsEven = lamb.takeWhile(isEven); - - it("should build a function that takes the first n elements satisfying a predicate from an array or array-like object", function () { - expect(takeWhileIsEven([])).toEqual([]); - expect(takeWhileIsEven([1, 3, 5, 7])).toEqual([]); - expect(takeWhileIsEven([2, 3, 4, 6, 8])).toEqual([2]); - expect(takeWhileIsEven([2, 4, 6, 7, 8])).toEqual([2, 4, 6]); - expect(takeWhileIsEven([2, 4, 6, 8])).toEqual([2, 4, 6, 8]); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - var takeWhileisVowel = lamb.takeWhile(isVowel); - - expect(takeWhileisVowel("aiuola")).toEqual(["a", "i", "u", "o"]); - }); - - it("should build a function throwing an exception if the predicate isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.takeWhile(value)([1, 2]); }).toThrow(); - }); - - expect(function () { lamb.takeWhile()([1, 2]); }).toThrow(); - }); - - it("should always return dense arrays", function () { - var takeWhileIsUndefined = lamb.takeWhile(lamb.isUndefined); - var r = [void 0, void 0, void 0, void 0, void 0]; - - /* eslint-disable no-sparse-arrays */ - expect(takeWhileIsUndefined([, , void 0, , void 0, 5, 6])).toEqual(r); - expect(takeWhileIsEven([2, 4, , , 6])).toEqual([2, 4]); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if called without the data argument", function () { - expect(takeWhileIsEven).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { takeWhileIsEven(null); }).toThrow(); - expect(function () { takeWhileIsEven(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(takeWhileIsEven(value)).toEqual([]); - }); - }); - }); - - describe("union / unionBy", function () { - var a1 = [1, 2, 3]; - var a2 = [2, 3, 5]; - var a3 = [3, 4, 5, [2, 3]]; - var a4 = [2, 3, 4, [2, 3]]; - - var r1 = [1, 2, 3, 5, 4, [2, 3], [2, 3]]; - var r2 = [1, 2, 3, 5, 4, [2, 3]]; - var r3 = [1, 2, 3, 5]; - - var data = [ - [{id: "1", name: "foo"}], - [{id: "1", name: "Foo"}, {id: "2", name: "bar"}, {id: "3", name: "baz"}], - [{id: "2", name: "Bar"}, {id: "1", name: "FOO"}] - ]; - - var dataUnionById = [ - {id: "1", name: "foo"}, - {id: "2", name: "bar"}, - {id: "3", name: "baz"} - ]; - - var unionByIdentity = lamb.unionBy(lamb.identity); - var unionAsStrings = lamb.unionBy(String); - var unionById = lamb.unionBy(lamb.getKey("id")); - - describe("unionBy", function () { - it("should build a function throwing an exception if the `iteratee` is not a function or if is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.unionBy(value)(a1, a2); }).toThrow(); - }); - - expect(function () { lamb.unionBy()(a1, a2); }).toThrow(); - }); - }); - - it("should return a list of every unique element present in the two given arrays", function () { - expect(lamb.union([], [])).toEqual([]); - expect(lamb.union([1, 2], [2, 3])).toEqual([1, 2, 3]); - expect(lamb.union( - lamb.union(a1, a2), - lamb.union(a3, a4) - )).toEqual(r1); - expect(unionAsStrings([], [])).toEqual([]); - expect(unionAsStrings([1, 2], [2, 3])).toEqual([1, 2, 3]); - expect(unionAsStrings( - unionAsStrings(a1, a2), - unionAsStrings(a3, a4) - )).toEqual(r2); - expect(unionById( - unionById(data[0], data[1]), - data[2] - )).toEqual(dataUnionById); - }); - - it("should ignore extra arguments", function () { - expect(lamb.union(a1, a2, a3)).toEqual(r3); - expect(unionAsStrings(a1, a2, a3)).toEqual(r3); - }); - - it("should work with array-like objects", function () { - expect(lamb.union("abc", "bcd")).toEqual(["a", "b", "c", "d"]); - expect(unionByIdentity("abc", "bcd")).toEqual(["a", "b", "c", "d"]); - }); - - it("should use the \"SameValueZero\" comparison and keep the first encountered value in case of equality", function () { - expect(lamb.union([-0, 2, 3, NaN], [1, 0, NaN, 1])).toEqual([-0, 2, 3, NaN, 1]); - expect(unionAsStrings([-0, 2, 3, NaN], [1, 0, NaN, 1])).toEqual([-0, 2, 3, NaN, 1]); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.union( - lamb.union([1, , 3], [3, 5]), - [6, 7]) - ).toEqual([1, void 0, 3, 5, 6, 7]); - expect(lamb.union( - [1, , 3], - lamb.union([3, 5], [void 0, 7]) - )).toEqual([1, void 0, 3, 5, 7]); - - expect(unionAsStrings( - unionAsStrings([1, , 3], [3, 5]), - [6, 7] - )).toEqual([1, void 0, 3, 5, 6, 7]); - expect(unionAsStrings( - [1, , 3], - unionAsStrings([3, 5], [void 0, 7]) - )).toEqual([1, void 0, 3, 5, 7]); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.union(null, a1); }).toThrow(); - expect(function () { lamb.union(void 0, a1); }).toThrow(); - expect(function () { lamb.union(a1, null); }).toThrow(); - expect(function () { lamb.union(a1, void 0); }).toThrow(); - expect(function () { lamb.union(a1); }).toThrow(); - - expect(function () { unionAsStrings(null, a2); }).toThrow(); - expect(function () { unionAsStrings(void 0, a2); }).toThrow(); - expect(function () { unionAsStrings(a2, null); }).toThrow(); - expect(function () { unionAsStrings(a2, void 0); }).toThrow(); - expect(function () { unionAsStrings(a2); }).toThrow(); - - expect(lamb.union).toThrow(); - expect(unionAsStrings).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.union(value, [3, 5])).toEqual([3, 5]); - expect(lamb.union([3, 5], value)).toEqual([3, 5]); - }); - }); - }); - - describe("uniques / uniquesBy", function () { - var data = [ - {id: "1", name: "foo"}, - {id: "1", name: "Foo"}, - {id: "2", name: "bar"}, - {id: "3", name: "baz"}, - {id: "2", name: "Bar"}, - {id: "1", name: "FOO"} - ]; - - var dataUniques = [ - {id: "1", name: "foo"}, - {id: "2", name: "bar"}, - {id: "3", name: "baz"} - ]; - - var uniquesByIdentity = lamb.uniquesBy(lamb.identity); - - describe("uniques", function () { - it("should return the unique elements of an array of simple values", function () { - expect(lamb.uniques([0, 2, 3, 4, 0, 4, 3, 5, 2, 1, 1])).toEqual([0, 2, 3, 4, 5, 1]); - expect(lamb.uniques(["foo", "bar", "bar", "baz"])).toEqual(["foo", "bar", "baz"]); - expect(lamb.uniques(Array(3))).toEqual([void 0]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.uniques).toThrow(); - }); - }); - - describe("uniquesBy", function () { - it("should use the provided iteratee to extract the values to compare", function () { - var chars = ["b", "A", "r", "B", "a", "z"]; - - expect(lamb.uniquesBy(lamb.invoker("toUpperCase"))(chars)).toEqual(["b", "A", "r", "z"]); - expect(lamb.uniquesBy(lamb.getKey("id"))(data)).toEqual(dataUniques); - }); - - it("should build a function throwing an exception if the itereatee isn't a function or if is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.uniquesBy(value)([1, 2, 3]); }).toThrow(); - }); - - expect(function () { lamb.uniquesBy()([1, 2, 3]); }).toThrow(); - }); - }); - - it("should use the SameValueZero comparison", function () { - expect(lamb.uniques([0, 1, 2, NaN, 1, 2, -0, NaN])).toEqual([0, 1, 2, NaN]); - }); - - it("should return a copy of the source array if it's empty or already contains unique values", function () { - var a1 = []; - var r1 = lamb.uniques(a1); - var a2 = [1, 2, 3, 4, 5]; - var r2 = lamb.uniques(a2); - - expect(r1).toEqual([]); - expect(r1).not.toBe(a1); - expect(r2).toEqual(a2); - expect(r2).not.toBe(a2); - }); - - it("should work with array-like objects", function () { - var s = "hello world"; - var r = ["h", "e", "l", "o", " ", "w", "r", "d"]; - - expect(lamb.uniques(s)).toEqual(r); - expect(uniquesByIdentity(s)).toEqual(r); - }); - - it("should prefer the first encountered value if two values are considered equal", function () { - expect(Object.is(0, lamb.uniques([0, -0])[0])).toBe(true); - expect(lamb.uniques([2, -0, 3, 3, 0, 1])).toEqual([2, -0, 3, 1]); - - var r = lamb.uniquesBy(lamb.getKey("id"))(data); - - expect(r).toEqual(dataUniques); - expect(r[0]).toBe(data[0]); - expect(r[1]).toBe(data[2]); - }); - - it("should always return dense arrays", function () { - /* eslint-disable no-sparse-arrays */ - var a1 = [1, , 1, 3]; - var a2 = [1, , 1, void 0, 3]; - /* eslint-enable no-sparse-arrays */ - - var r = [1, void 0, 3]; - - expect(lamb.uniques(a1)).toEqual(r); - expect(lamb.uniques(a2)).toEqual(r); - expect(uniquesByIdentity(a1)).toEqual(r); - expect(uniquesByIdentity(a2)).toEqual(r); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.uniques(null); }).toThrow(); - expect(function () { lamb.uniques(void 0); }).toThrow(); - expect(function () { uniquesByIdentity(null); }).toThrow(); - expect(function () { uniquesByIdentity(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.uniques(value)).toEqual([]); - expect(uniquesByIdentity(value)).toEqual([]); - }); - }); - }); - - describe("transpose / zip", function () { - var a1 = [1, 2, 3, 4]; - var a2 = [5, 6, 7]; - var a3 = [8, 9]; - - var r1 = [[1], [2], [3], [4]]; - var r2 = [[1, 5], [2, 6], [3, 7]]; - var r3 = [[1, 5, 8], [2, 6, 9]]; - var r4 = [[5, 8], [6, 9]]; - - describe("transpose", function () { - it("should transpose a matrix", function () { - expect(lamb.transpose([])).toEqual([]); - expect(lamb.transpose([1, 2, 3])).toEqual([]); - expect(lamb.transpose(r1)).toEqual([a1]); - expect(lamb.transpose(r2)).toEqual([[1, 2, 3], [5, 6, 7]]); - expect(lamb.transpose(r3)).toEqual([[1, 2], [5, 6], [8, 9]]); - }); - - it("should work with array-like objects", function () { - var fn = function () { - return lamb.transpose(arguments); - }; - - expect(fn("abc", [1, 2, 3])).toEqual([["a", 1], ["b", 2], ["c", 3]]); - }); - - it("should build dense arrays when sparse ones are received", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - expect(lamb.transpose([[1, , 3], [4, 5, void 0], [6, 7, ,]])).toEqual([ - [1, 4, 6], - [void 0, 5, 7], - [3, void 0, void 0] - ]); - }); - - it("should throw an exception when a value in the array-like is `nil`", function () { - expect(function () { lamb.transpose([null, [1, 2, 3]]); }).toThrow(); - expect(function () { lamb.transpose([[1, 2, 3], null]); }).toThrow(); - expect(function () { lamb.transpose([void 0, [1, 2, 3]]); }).toThrow(); - expect(function () { lamb.transpose([[1, 2, 3], void 0]); }).toThrow(); - }); - - it("should consider other non-array-like values contained in the main array-like as empty arrays", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.transpose([[1, 2, 3], value])).toEqual([]); - expect(lamb.transpose([value, [1, 2, 3]])).toEqual([]); - }); - }); - }); - - describe("zip", function () { - it("should pair items with the same index in the received lists", function () { - expect(lamb.zip(a1, a2)).toEqual(r2); - expect(lamb.zip(a2, a3)).toEqual(r4); - expect(lamb.zip([], a1)).toEqual([]); - }); - - it("should work with array-like objects", function () { - expect(lamb.zip(a1, "abc")).toEqual([[1, "a"], [2, "b"], [3, "c"]]); - }); - - it("should build dense arrays when sparse ones are received", function () { - expect(lamb.zip(a2, Array(4))).toEqual([[5, void 0], [6, void 0], [7, void 0]]); - }); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.transpose(null); }).toThrow(); - expect(function () { lamb.transpose(void 0); }).toThrow(); - expect(function () { lamb.zip(null); }).toThrow(); - expect(function () { lamb.zip(void 0); }).toThrow(); - expect(function () { lamb.zip([1, 2], null); }).toThrow(); - expect(function () { lamb.zip([1, 2], void 0); }).toThrow(); - expect(function () { lamb.zip([], null); }).toThrow(); - expect(function () { lamb.zip([], void 0); }).toThrow(); - - expect(lamb.transpose).toThrow(); - expect(lamb.zip).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.transpose(value)).toEqual([]); - expect(lamb.zip(value, [1, 2])).toEqual([]); - expect(lamb.zip([1, 2], value)).toEqual([]); - }); - }); - }); - - describe("zipWithIndex", function () { - it("should pair the received values with their index", function () { - expect(lamb.zipWithIndex([])).toEqual([]); - expect(lamb.zipWithIndex([1, 2, 3, 4])).toEqual([[1, 0], [2, 1], [3, 2], [4, 3]]); - }); - - it("should work with array-like objects", function () { - expect(lamb.zipWithIndex("abcd")).toEqual([["a", 0], ["b", 1], ["c", 2], ["d", 3]]); - }); - - it("should not skip deleted or unassigned indexes in sparse arrays", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - expect(lamb.zipWithIndex([1, , 3, ,])).toEqual([ - [1, 0], - [void 0, 1], - [3, 2], - [void 0, 3] - ]); - }); - - it("should throw an error if no arguments are supplied", function () { - expect(lamb.zipWithIndex).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.zipWithIndex(null); }).toThrow(); - expect(function () { lamb.zipWithIndex(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.zipWithIndex(value)).toEqual([]); - }); - }); - }); -}); diff --git a/test/spec/array_basicsSpec.js b/test/spec/array_basicsSpec.js deleted file mode 100644 index 1d950ed..0000000 --- a/test/spec/array_basicsSpec.js +++ /dev/null @@ -1,1055 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; -var sparseArrayEquality = commons.equalities.sparseArrayEquality; - -var nonFunctions = commons.vars.nonFunctions; -var wannabeEmptyArrays = commons.vars.wannabeEmptyArrays; -var zeroesAsIntegers = commons.vars.zeroesAsIntegers; - -describe("lamb.array_basics", function () { - // to check "truthy" and "falsy" values returned by predicates - var isVowel = function (char, idx, s) { - expect(s[idx]).toBe(char); - - return ~"aeiouAEIOU".indexOf(char); - }; - - beforeEach(function () { - jasmine.addCustomEqualityTester(sparseArrayEquality); - }); - - describe("contains / isIn", function () { - var testArray = ["foo", NaN, 0, 12]; - - it("should verify if a value is contained in an array using the \"SameValueZero\" comparison", function () { - expect(lamb.contains(12)(testArray)).toBe(true); - expect(lamb.isIn(testArray, 12)).toBe(true); - expect(lamb.contains(NaN)(testArray)).toBe(true); - expect(lamb.isIn(testArray, NaN)).toBe(true); - expect(lamb.contains(0)(testArray)).toBe(true); - expect(lamb.isIn(testArray, 0)).toBe(true); - expect(lamb.contains(-0)(testArray)).toBe(true); - expect(lamb.isIn(testArray, -0)).toBe(true); - expect(lamb.contains(15)(testArray)).toBe(false); - expect(lamb.isIn(testArray, 15)).toBe(false); - - expect(lamb.contains()([1, 3])).toBe(false); - expect(lamb.contains()([1, 3, void 0])).toBe(true); - }); - - it("should work with array-like objects", function () { - expect(lamb.contains("f")("foo")).toBe(true); - expect(lamb.isIn("foo", "f")).toBe(true); - }); - - it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.contains(void 0)([1, , 3])).toBe(true); - expect(lamb.isIn([1, , 3], void 0)).toBe(true); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if called without the data argument or without arguments at all", function () { - expect(lamb.isIn).toThrow(); - expect(lamb.contains(1)).toThrow(); - expect(lamb.contains()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.isIn(null, 1); }).toThrow(); - expect(function () { lamb.isIn(void 0, 1); }).toThrow(); - expect(function () { lamb.contains(1)(null); }).toThrow(); - expect(function () { lamb.contains(1)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array and return false", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.contains(1)(value)).toBe(false); - expect(lamb.isIn(value, 1)).toBe(false); - }); - }); - }); - - describe("every / everyIn", function () { - var a1 = [2, 4, 6, 8]; - var a2 = [1, 3, 5, 6, 7, 8]; - var isEven = function (n, idx, list) { - expect(list[idx]).toBe(n); - - return n % 2 === 0; - }; - - it("should check if every element of an array satisfies the given predicate", function () { - expect(lamb.everyIn(a1, isEven)).toBe(true); - expect(lamb.every(isEven)(a1)).toBe(true); - expect(lamb.everyIn(a2, isEven)).toBe(false); - expect(lamb.every(isEven)(a2)).toBe(false); - }); - - it("should work with array-like objects", function () { - expect(lamb.everyIn("2468", isEven)).toBe(true); - expect(lamb.every(isEven)("2468")).toBe(true); - expect(lamb.everyIn("24678", isEven)).toBe(false); - expect(lamb.every(isEven)("24678")).toBe(false); - }); - - it("should always return true for empty array-likes because of vacuous truth", function () { - expect(lamb.everyIn([], isEven)).toBe(true); - expect(lamb.every(isEven)([])).toBe(true); - expect(lamb.everyIn("", isEven)).toBe(true); - expect(lamb.every(isEven)("")).toBe(true); - }); - - it("should stop calling the predicate as soon as a `false` value is returned", function () { - var isGreaterThan10 = jasmine.createSpy().and.callFake(function (n) { - return n > 10; - }); - var arr = [12, 13, 9, 15]; - - expect(lamb.everyIn(arr, isGreaterThan10)).toBe(false); - expect(isGreaterThan10.calls.count()).toBe(3); - expect(lamb.every(isGreaterThan10)(arr)).toBe(false); - expect(isGreaterThan10.calls.count()).toBe(6); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.everyIn("aiu", isVowel)).toBe(true); - expect(lamb.every(isVowel)("aiu")).toBe(true); - expect(lamb.everyIn("aiuole", isVowel)).toBe(false); - expect(lamb.every(isVowel)("aiuole")).toBe(false); - }); - - it("should not skip deleted or unassigned elements, unlike the native method", function () { - var isDefined = lamb.not(lamb.isUndefined); - var arr = Array(5); - - arr[2] = 99; - - expect(lamb.everyIn(arr, isDefined)).toBe(false); - expect(lamb.every(isDefined)(arr)).toBe(false); - }); - - it("should throw an exception if the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.everyIn(a1, value); }).toThrow(); - expect(function () { lamb.every(value)(a1); }).toThrow(); - }); - - expect(function () { lamb.everyIn(a1); }).toThrow(); - expect(function () { lamb.every()(a1); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.every(isEven)).toThrow(); - expect(lamb.everyIn).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.everyIn(null, isEven); }).toThrow(); - expect(function () { lamb.everyIn(void 0, isEven); }).toThrow(); - expect(function () { lamb.every(isEven)(null); }).toThrow(); - expect(function () { lamb.every(isEven)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array and return `true`", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.everyIn(value, isEven)).toBe(true); - expect(lamb.every(isEven)(value)).toBe(true); - }); - }); - }); - - describe("filter / filterWith", function () { - var isLowerCase = function (s, idx, list) { - expect(list[idx]).toBe(s); - - return s.toLowerCase() === s; - }; - var getLowerCaseEls = lamb.filterWith(isLowerCase); - var arr = ["Foo", "bar", "baZ"]; - - afterEach(function () { - expect(arr).toEqual(["Foo", "bar", "baZ"]); - }); - - it("should filter an array by keeping the items satisfying the given predicate", function () { - expect(lamb.filter(arr, isLowerCase)).toEqual(["bar"]); - expect(getLowerCaseEls(arr)).toEqual(["bar"]); - }); - - it("should work with array-like objects", function () { - expect(lamb.filter("fooBAR", isLowerCase)).toEqual(["f", "o", "o"]); - expect(getLowerCaseEls("fooBAR")).toEqual(["f", "o", "o"]); - }); - - it("should not skip deleted or unassigned elements, unlike the native method", function () { - var sparseArr = Array(4); - - sparseArr[2] = 3; - - var isOdd = function (n) { return n % 2 !== 0; }; - var result = [void 0, void 0, 3, void 0]; - - expect(lamb.filter(sparseArr, isOdd)).toEqual(result); - expect(lamb.filterWith(isOdd)(sparseArr)).toEqual(result); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.filter("aiuola", isVowel)).toEqual(["a", "i", "u", "o", "a"]); - expect(lamb.filterWith(isVowel)("aiuola")).toEqual(["a", "i", "u", "o", "a"]); - }); - - it("should always return dense arrays", function () { - // eslint-disable-next-line no-sparse-arrays - var sparseArr = [1, , , 4, void 0, 5]; - - expect(lamb.filter(sparseArr, lamb.isUndefined)).toEqual([void 0, void 0, void 0]); - expect(lamb.filterWith(lamb.isUndefined)(sparseArr)).toEqual([void 0, void 0, void 0]); - }); - - it("should throw an exception if the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.filter(arr, value); }).toThrow(); - expect(function () { lamb.filterWith(value)(arr); }).toThrow(); - }); - - expect(function () { lamb.filterWith()(arr); }).toThrow(); - expect(function () { lamb.filter(arr); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(getLowerCaseEls).toThrow(); - expect(lamb.filter).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.filter(null, isLowerCase); }).toThrow(); - expect(function () { lamb.filter(void 0, isLowerCase); }).toThrow(); - expect(function () { getLowerCaseEls(null); }).toThrow(); - expect(function () { getLowerCaseEls(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.filter(value, isLowerCase)).toEqual([]); - expect(getLowerCaseEls(value)).toEqual([]); - }); - }); - }); - - describe("find / findWhere / findIndex / findIndexWhere", function () { - var persons = [ - {name: "Jane", surname: "Doe", age: 12}, - {name: "John", surname: "Doe", age: 40}, - {name: "Mario", surname: "Rossi", age: 18}, - {name: "Paolo", surname: "Bianchi", age: 40} - ]; - - var testString = "Hello world"; - var is40YO = lamb.hasKeyValue("age", 40); - var is41YO = lamb.hasKeyValue("age", 41); - - describe("find", function () { - it("should find an element in an array-like object by using the given predicate", function () { - expect(lamb.find(persons, is40YO)).toEqual(persons[1]); - expect(lamb.findWhere(is40YO)(persons)).toEqual(persons[1]); - }); - - it("should return `undefined` if there is no element satisfying the predicate", function () { - expect(lamb.find(persons, is41YO)).toBeUndefined(); - expect(lamb.findWhere(is41YO)(persons)).toBeUndefined(); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.find(testString, isVowel)).toBe("e"); - expect(lamb.find("zxc", isVowel)).toBeUndefined(); - expect(lamb.findWhere(isVowel)(testString)).toBe("e"); - expect(lamb.findWhere(isVowel)("zxc")).toBeUndefined(); - }); - - it("should throw an exception if the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.find(persons, value); }).toThrow(); - expect(function () { lamb.findWhere(value)(persons); }).toThrow(); - }); - - expect(function () { lamb.find(persons); }).toThrow(); - expect(function () { lamb.findWhere()(persons); }).toThrow(); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.find).toThrow(); - expect(lamb.findWhere()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.find(null, is40YO); }).toThrow(); - expect(function () { lamb.find(void 0, is40YO); }).toThrow(); - expect(function () { lamb.findWhere(is40YO)(null); }).toThrow(); - expect(function () { lamb.findWhere(is40YO)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.find(value, is40YO)).toBeUndefined(); - expect(lamb.findWhere(is40YO)(value)).toBeUndefined(); - }); - }); - }); - - describe("findIndex", function () { - it("should find the index of an element in an array-like object by using the given predicate", function () { - expect(lamb.findIndex(persons, is40YO)).toBe(1); - expect(lamb.findIndexWhere(is40YO)(persons)).toBe(1); - }); - - it("should return `-1` if there is no element satisfying the predicate", function () { - expect(lamb.findIndex(persons, is41YO)).toBe(-1); - expect(lamb.findIndexWhere(is41YO)(persons)).toBe(-1); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.findIndex(testString, isVowel)).toBe(1); - expect(lamb.findIndex("zxc", isVowel)).toBe(-1); - expect(lamb.findIndexWhere(isVowel)(testString)).toBe(1); - expect(lamb.findIndexWhere(isVowel)("zxc")).toBe(-1); - }); - - it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.findIndex([1, , 3], lamb.isUndefined)).toBe(1); - expect(lamb.findIndexWhere(lamb.isUndefined)([1, , 3])).toBe(1); - /* eslint-enable no-sparse-arrays */ - }); - - it("should throw an exception if the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.findIndex(persons, value); }).toThrow(); - expect(function () { lamb.findIndexWhere(value)(persons); }).toThrow(); - }); - - expect(function () { lamb.findIndex(persons); }).toThrow(); - expect(function () { lamb.findIndexWhere()(persons); }).toThrow(); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.findIndex).toThrow(); - expect(lamb.findIndexWhere()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.findIndex(null, is40YO); }).toThrow(); - expect(function () { lamb.findIndex(void 0, is40YO); }).toThrow(); - expect(function () { lamb.findIndexWhere(is40YO)(null); }).toThrow(); - expect(function () { lamb.findIndexWhere(is40YO)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.findIndex(value, is40YO)).toBe(-1); - expect(lamb.findIndexWhere(is40YO)(value)).toBe(-1); - }); - }); - }); - }); - - describe("forEach", function () { - var arr = [1, 2, 3, 4, 5]; - var fn = function (el, idx, list) { - expect(list[idx]).toBe(el); - }; - - it("should execute the given function for every element of the provided array", function () { - expect(lamb.forEach(arr, fn)).toBeUndefined(); - }); - - it("should work with array-like objects", function () { - expect(lamb.forEach("foo bar", fn)).toBeUndefined(); - }); - - it("should not skip deleted or unassigned elements, unlike the native method", function () { - var sparseArr = Array(3); - - sparseArr[1] = 3; - - var fnSpy = jasmine.createSpy().and.callThrough(); - - expect(lamb.forEach(sparseArr, fnSpy)).toBeUndefined(); - expect(fnSpy.calls.count()).toBe(3); - expect(fnSpy.calls.argsFor(0)).toEqual([void 0, 0, sparseArr]); - expect(fnSpy.calls.argsFor(1)).toEqual([3, 1, sparseArr]); - expect(fnSpy.calls.argsFor(2)).toEqual([void 0, 2, sparseArr]); - }); - - it("should throw an exception if the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.forEach(arr, value); }).toThrow(); - }); - - expect(function () { lamb.forEach(arr); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.forEach).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.forEach(null, fn); }).toThrow(); - expect(function () { lamb.forEach(void 0, fn); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - var fnSpy = jasmine.createSpy().and.callThrough(); - - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.forEach(value, fnSpy)).toBeUndefined(); - }); - - expect(fnSpy.calls.count()).toBe(0); - }); - }); - - describe("list", function () { - it("should build an array with the received arguments", function () { - expect(lamb.list(123)).toEqual([123]); - expect(lamb.list(1, 2, 3)).toEqual([1, 2, 3]); - expect(lamb.list(null, void 0)).toEqual([null, void 0]); - expect(lamb.list(void 0)).toEqual([void 0]); - }); - - it("should return an empty array if no arguments are supplied", function () { - expect(lamb.list()).toEqual([]); - }); - }); - - describe("map / mapWith", function () { - var double = function (n, idx, list) { - expect(list[idx]).toBe(n); - - return n * 2; - }; - var makeDoubles = lamb.mapWith(double); - var numbers = [1, 2, 3, 4, 5]; - - afterEach(function () { - expect(numbers).toEqual([1, 2, 3, 4, 5]); - }); - - it("should apply the provided function to the elements of the given array", function () { - expect(lamb.map(numbers, double)).toEqual([2, 4, 6, 8, 10]); - expect(makeDoubles(numbers)).toEqual([2, 4, 6, 8, 10]); - }); - - it("should work with array-like objects", function () { - expect(lamb.map("12345", double)).toEqual([2, 4, 6, 8, 10]); - expect(makeDoubles("12345")).toEqual([2, 4, 6, 8, 10]); - }); - - it("should not skip deleted or unassigned elements, unlike the native method", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - var sparseArr = [, "foo", ,]; - var safeUpperCase = lamb.compose(lamb.invoker("toUpperCase"), String); - var result = ["UNDEFINED", "FOO", "UNDEFINED"]; - - expect(lamb.map(sparseArr, safeUpperCase)).toEqual(result); - expect(lamb.map(sparseArr, lamb.identity)).toEqual([void 0, "foo", void 0]); - expect(lamb.mapWith(safeUpperCase)(sparseArr)).toEqual(result); - expect(lamb.mapWith(lamb.identity)(sparseArr)).toEqual([void 0, "foo", void 0]); - }); - - it("should throw an exception if the iteratee isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.mapWith(value)(numbers); }).toThrow(); - }); - - expect(function () { lamb.mapWith()(numbers); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.map).toThrow(); - expect(makeDoubles).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.map(null, double); }).toThrow(); - expect(function () { lamb.map(void 0, double); }).toThrow(); - expect(function () { makeDoubles(null); }).toThrow(); - expect(function () { makeDoubles(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.map(value, double)).toEqual([]); - expect(makeDoubles(value)).toEqual([]); - }); - }); - }); - - describe("reduceRight / reduceRightWith", function () { - var arr = [1, 2, 3, 4, 5]; - var s = "12345"; - - afterEach(function () { - expect(arr).toEqual([1, 2, 3, 4, 5]); - }); - - it("should fold or reduce the provided array with the given accumulator function starting from the last element", function () { - var subtract = jasmine.createSpy("subtract").and.callFake(function (prev, current, idx, list) { - expect(list).toBe(arr); - expect(list[idx]).toBe(current); - - return prev - current; - }); - - var prevValues = [ - 5, 1, -2, -4, 0, -5, -9, -12, -14, 10, 5, 1, -2, -4, - 5, 1, -2, -4, 0, -5, -9, -12, -14, 10, 5, 1, -2, -4 - ]; - - expect(lamb.reduceRight(arr, subtract)).toBe(-5); - expect(lamb.reduceRight(arr, subtract, 0)).toBe(-15); - expect(lamb.reduceRight(arr, subtract, 10)).toBe(-5); - expect(lamb.reduceRightWith(subtract)(arr)).toBe(-5); - expect(lamb.reduceRightWith(subtract, 0)(arr)).toBe(-15); - expect(lamb.reduceRightWith(subtract, 10)(arr)).toBe(-5); - - expect(subtract.calls.count()).toBe(prevValues.length); - prevValues.forEach(function (prevValue, idx) { - expect(subtract.calls.argsFor(idx)[0]).toEqual(prevValue); - }); - }); - - it("should work with array-like objects", function () { - var fn = lamb.tapArgs(lamb.subtract, [lamb.identity, Number]); - - expect(lamb.reduceRight(s, fn)).toBe(-5); - expect(lamb.reduceRight(s, fn, 0)).toBe(-15); - expect(lamb.reduceRight(s, fn, 10)).toBe(-5); - expect(lamb.reduceRightWith(fn)(s)).toBe(-5); - expect(lamb.reduceRightWith(fn, 0)(s)).toBe(-15); - expect(lamb.reduceRightWith(fn, 10)(s)).toBe(-5); - }); - - it("should not skip deleted or unassigned elements, unlike the native method", function () { - var addSpy = jasmine.createSpy("sum").and.callFake(lamb.sum); - var sparseArr = Array(3); - - sparseArr[1] = 3; - - expect(lamb.reduceRight(sparseArr, addSpy, 0)).toEqual(NaN); - expect(lamb.reduceRightWith(addSpy, 0)(sparseArr)).toEqual(NaN); - expect(addSpy.calls.count()).toBe(6); - }); - - it("should build a function throwing an exception if the accumulator isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.reduceRight(arr, value, 0); }).toThrow(); - expect(function () { lamb.reduceRightWith(value, 0)(arr); }).toThrow(); - }); - - expect(function () { lamb.reduceRight(arr); }).toThrow(); - expect(function () { lamb.reduceRightWith()(arr); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.reduceRight).toThrow(); - expect(lamb.reduceRightWith(lamb.sum, 0)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.reduceRight(null, lamb.subtract, 0); }).toThrow(); - expect(function () { lamb.reduceRight(void 0, lamb.subtract, 0); }).toThrow(); - expect(function () { lamb.reduceRightWith(lamb.subtract, 0)(null); }).toThrow(); - expect(function () { lamb.reduceRightWith(lamb.subtract, 0)(void 0); }).toThrow(); - }); - - it("should throw an exception when supplied with an empty array-like without an initial value", function () { - expect(function () { lamb.reduceRight([], lamb.subtract); }).toThrow(); - expect(function () { lamb.reduceRight("", lamb.subtract); }).toThrow(); - expect(function () { lamb.reduceRightWith(lamb.subtract)([]); }).toThrow(); - expect(function () { lamb.reduceRightWith(lamb.subtract)(""); }).toThrow(); - }); - - it("should treat every other value as an empty array and return the initial value", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.reduceRight(value, lamb.subtract, 99)).toEqual(99); - expect(lamb.reduceRightWith(lamb.subtract, 99)(value)).toEqual(99); - }); - }); - }); - - describe("reduce / reduceWith", function () { - var arr = [1, 2, 3, 4, 5]; - var s = "12345"; - - afterEach(function () { - expect(arr).toEqual([1, 2, 3, 4, 5]); - }); - - it("should fold or reduce the provided array with the given accumulator function starting from the first element", function () { - var subtract = jasmine.createSpy("subtract").and.callFake(function (prev, current, idx, list) { - expect(list).toBe(arr); - expect(list[idx]).toBe(current); - - return prev - current; - }); - - var prevValues = [ - 1, -1, -4, -8, 0, -1, -3, -6, -10, 10, 9, 7, 4, 0, - 1, -1, -4, -8, 0, -1, -3, -6, -10, 10, 9, 7, 4, 0 - ]; - - expect(lamb.reduce(arr, subtract)).toBe(-13); - expect(lamb.reduce(arr, subtract, 0)).toBe(-15); - expect(lamb.reduce(arr, subtract, 10)).toBe(-5); - expect(lamb.reduceWith(subtract)(arr)).toBe(-13); - expect(lamb.reduceWith(subtract, 0)(arr)).toBe(-15); - expect(lamb.reduceWith(subtract, 10)(arr)).toBe(-5); - - expect(subtract.calls.count()).toBe(prevValues.length); - prevValues.forEach(function (prevValue, idx) { - expect(subtract.calls.argsFor(idx)[0]).toEqual(prevValue); - }); - }); - - it("should work with array-like objects", function () { - var fn = lamb.tapArgs(lamb.subtract, [lamb.identity, Number]); - - expect(lamb.reduce(s, fn)).toBe(-13); - expect(lamb.reduce(s, fn, 0)).toBe(-15); - expect(lamb.reduce(s, fn, 10)).toBe(-5); - expect(lamb.reduceWith(fn)(s)).toBe(-13); - expect(lamb.reduceWith(fn, 0)(s)).toBe(-15); - expect(lamb.reduceWith(fn, 10)(s)).toBe(-5); - }); - - it("should not skip deleted or unassigned elements, unlike the native method", function () { - var addSpy = jasmine.createSpy("sum").and.callFake(lamb.sum); - var sparseArr = Array(3); - - sparseArr[1] = 3; - - expect(lamb.reduce(sparseArr, addSpy, 0)).toEqual(NaN); - expect(lamb.reduceWith(addSpy, 0)(sparseArr)).toEqual(NaN); - expect(addSpy.calls.count()).toBe(6); - }); - - it("should build a function throwing an exception if the accumulator isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.reduce(arr, value, 0); }).toThrow(); - expect(function () { lamb.reduceWith(value, 0)(arr); }).toThrow(); - }); - - expect(function () { lamb.reduce(arr); }).toThrow(); - expect(function () { lamb.reduceWith()(arr); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.reduce).toThrow(); - expect(lamb.reduceWith(lamb.sum, 0)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.reduce(null, lamb.subtract, 0); }).toThrow(); - expect(function () { lamb.reduce(void 0, lamb.subtract, 0); }).toThrow(); - expect(function () { lamb.reduceWith(lamb.subtract, 0)(null); }).toThrow(); - expect(function () { lamb.reduceWith(lamb.subtract, 0)(void 0); }).toThrow(); - }); - - it("should throw an exception when supplied with an empty array-like without an initial value", function () { - expect(function () { lamb.reduce([], lamb.subtract); }).toThrow(); - expect(function () { lamb.reduce("", lamb.subtract); }).toThrow(); - expect(function () { lamb.reduceWith(lamb.subtract)([]); }).toThrow(); - expect(function () { lamb.reduceWith(lamb.subtract)(""); }).toThrow(); - }); - - it("should treat every other value as an empty array and return the initial value", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.reduce(value, lamb.subtract, 99)).toEqual(99); - expect(lamb.reduceWith(lamb.subtract, 99)(value)).toEqual(99); - }); - }); - }); - - describe("reverse", function () { - it("should reverse a copy of an array-like object", function () { - var arr = [1, 2, 3, 4, 5]; - var s = "hello"; - - expect(lamb.reverse(arr)).toEqual([5, 4, 3, 2, 1]); - expect(lamb.reverse(s)).toEqual(["o", "l", "l", "e", "h"]); - expect(arr).toEqual([1, 2, 3, 4, 5]); - }); - - it("should always return dense arrays", function () { - // eslint-disable-next-line no-sparse-arrays - expect(lamb.reverse([1, , 3])).toEqual([3, void 0, 1]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.reverse).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.reverse(null); }).toThrow(); - expect(function () { lamb.reverse(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - [{}, /foo/, 1, function () {}, NaN, true, new Date()].forEach(function (value) { - expect(lamb.reverse(value)).toEqual([]); - }); - }); - }); - - describe("slice / sliceAt", function () { - var arr = [1, 2, 3, 4, 5, 6]; - var arrCopy = arr.slice(); - var s = "hello world"; - var users = [{name: "Jane"}, {name: "John"}, {name: "Mario"}, {name: "Paolo"}]; - var usersCopy = [{name: "Jane"}, {name: "John"}, {name: "Mario"}, {name: "Paolo"}]; - - afterEach(function () { - expect(arr).toEqual(arrCopy); - expect(users).toEqual(usersCopy); - }); - - it("should return a copy of the desired portion of an array", function () { - expect(lamb.slice(arr, 0, 3)).toEqual([1, 2, 3]); - expect(lamb.sliceAt(0, 3)(arr)).toEqual([1, 2, 3]); - - var usersSliceA = lamb.slice(users, 1, 3); - var usersSliceB = lamb.sliceAt(1, 3)(users); - - expect(usersSliceA).toEqual([{name: "John"}, {name: "Mario"}]); - expect(usersSliceA[0]).toBe(users[1]); - expect(usersSliceA[1]).toBe(users[2]); - expect(usersSliceB).toEqual([{name: "John"}, {name: "Mario"}]); - expect(usersSliceB[0]).toBe(users[1]); - expect(usersSliceB[1]).toBe(users[2]); - }); - - it("should work on array-like objects", function () { - expect(lamb.slice(s, 1, 3)).toEqual(["e", "l"]); - expect(lamb.sliceAt(1, 3)(s)).toEqual(["e", "l"]); - }); - - it("should return an array copy of the array-like if the desired slice encompasses the whole array-like", function () { - var r1 = lamb.slice(arr, 0, arr.length); - var r2 = lamb.sliceAt(0, arr.length)(arr); - - expect(r1).toEqual(arrCopy); - expect(r2).toEqual(arrCopy); - expect(r1).not.toBe(arr); - expect(r2).not.toBe(arr); - expect(lamb.slice(s, 0, s.length)).toEqual(s.split("")); - expect(lamb.sliceAt(0, s.length)(s)).toEqual(s.split("")); - }); - - it("should accept negative indexes in the `start` parameter", function () { - expect(lamb.slice(arr, -3, 5)).toEqual([4, 5]); - expect(lamb.slice(s, -5, 9)).toEqual(["w", "o", "r"]); - - expect(lamb.sliceAt(-3, 5)(arr)).toEqual([4, 5]); - expect(lamb.sliceAt(-5, 9)(s)).toEqual(["w", "o", "r"]); - }); - - it("should accept negative indexes in the `end` parameter", function () { - expect(lamb.slice(arr, 2, -2)).toEqual([3, 4]); - expect(lamb.slice(s, 2, -2)).toEqual(["l", "l", "o", " ", "w", "o", "r"]); - - expect(lamb.sliceAt(2, -2)(arr)).toEqual([3, 4]); - expect(lamb.sliceAt(2, -2)(s)).toEqual(["l", "l", "o", " ", "w", "o", "r"]); - }); - - it("should slice from the beginning of the array-like if the `start` parameter is less than or equal to the additive inverse of the array-like length", function () { - expect(lamb.slice(arr, -20, 3)).toEqual([1, 2, 3]); - expect(lamb.slice(arr, -arr.length, 3)).toEqual([1, 2, 3]); - expect(lamb.slice(s, -20, 4)).toEqual(["h", "e", "l", "l"]); - expect(lamb.slice(s, -s.length, 4)).toEqual(["h", "e", "l", "l"]); - - expect(lamb.sliceAt(-20, 3)(arr)).toEqual([1, 2, 3]); - expect(lamb.sliceAt(-arr.length, 3)(arr)).toEqual([1, 2, 3]); - expect(lamb.sliceAt(-20, 4)(s)).toEqual(["h", "e", "l", "l"]); - expect(lamb.sliceAt(-s.length, 4)(s)).toEqual(["h", "e", "l", "l"]); - }); - - it("should slice to the end of the array-like if the `end` parameter is greater than its length", function () { - expect(lamb.slice(arr, 3, 30)).toEqual([4, 5, 6]); - expect(lamb.slice(s, 8, 40)).toEqual(["r", "l", "d"]); - - expect(lamb.sliceAt(3, 30)(arr)).toEqual([4, 5, 6]); - expect(lamb.sliceAt(8, 40)(s)).toEqual(["r", "l", "d"]); - }); - - it("should return an empty array if `start` is greater than or equal to the length of the array-like", function () { - expect(lamb.slice(arr, 6, 6)).toEqual([]); - expect(lamb.slice(arr, 6, -1)).toEqual([]); - expect(lamb.slice(arr, 7, -1)).toEqual([]); - expect(lamb.slice(s, 11, 11)).toEqual([]); - expect(lamb.slice(s, 11, -1)).toEqual([]); - expect(lamb.slice(s, 12, -1)).toEqual([]); - - expect(lamb.sliceAt(6, 6)(arr)).toEqual([]); - expect(lamb.sliceAt(6, -1)(arr)).toEqual([]); - expect(lamb.sliceAt(7, -1)(arr)).toEqual([]); - expect(lamb.sliceAt(11, 11)(s)).toEqual([]); - expect(lamb.sliceAt(11, -1)(s)).toEqual([]); - expect(lamb.sliceAt(12, -1)(s)).toEqual([]); - }); - - it("should return an empty array if `start` is greater than or equal to `end` (as natural indexes)", function () { - expect(lamb.slice(arr, 4, 4)).toEqual([]); - expect(lamb.slice(arr, 5, 4)).toEqual([]); - expect(lamb.slice(arr, -5, -40)).toEqual([]); - expect(lamb.slice(s, 4, 4)).toEqual([]); - expect(lamb.slice(s, 5, 4)).toEqual([]); - expect(lamb.slice(s, -5, -40)).toEqual([]); - - expect(lamb.sliceAt(4, 4)(arr)).toEqual([]); - expect(lamb.sliceAt(5, 4)(arr)).toEqual([]); - expect(lamb.sliceAt(-5, -40)(arr)).toEqual([]); - expect(lamb.sliceAt(4, 4)(s)).toEqual([]); - expect(lamb.sliceAt(5, 4)(s)).toEqual([]); - expect(lamb.sliceAt(-5, -40)(s)).toEqual([]); - }); - - it("should convert to integer any value received as `start` or `end` following ECMA specifications", function () { - // see https://www.ecma-international.org/ecma-262/7.0/#sec-tointeger - - expect(lamb.slice(arr, new Date(1), new Date(4))).toEqual([2, 3, 4]); - expect(lamb.slice(arr, 1.8, 4.9)).toEqual([2, 3, 4]); - expect(lamb.slice(arr, null, 3)).toEqual([1, 2, 3]); - expect(lamb.slice(arr, 1, null)).toEqual([]); - expect(lamb.slice(arr, "1.8", "4.9")).toEqual([2, 3, 4]); - expect(lamb.slice(arr, false, true)).toEqual([1]); - expect(lamb.slice(arr, [1.8], [4.9])).toEqual([2, 3, 4]); - expect(lamb.slice(arr, ["1.8"], ["4.9"])).toEqual([2, 3, 4]); - - expect(lamb.slice(s, new Date(1), new Date(3))).toEqual(["e", "l"]); - expect(lamb.slice(s, 1.8, 3.9)).toEqual(["e", "l"]); - expect(lamb.slice(s, null, 3)).toEqual(["h", "e", "l"]); - expect(lamb.slice(s, 1, null)).toEqual([]); - expect(lamb.slice(s, "1.8", "4.9")).toEqual(["e", "l", "l"]); - expect(lamb.slice(s, false, true)).toEqual(["h"]); - expect(lamb.slice(s, [1.8], [4.9])).toEqual(["e", "l", "l"]); - expect(lamb.slice(s, ["1.8"], ["4.9"])).toEqual(["e", "l", "l"]); - - expect(lamb.sliceAt(new Date(1), new Date(4))(arr)).toEqual([2, 3, 4]); - expect(lamb.sliceAt(1.8, 4.9)(arr)).toEqual([2, 3, 4]); - expect(lamb.sliceAt(null, 3)(arr)).toEqual([1, 2, 3]); - expect(lamb.sliceAt(1, null)(arr)).toEqual([]); - expect(lamb.sliceAt("1.8", "4.9")(arr)).toEqual([2, 3, 4]); - expect(lamb.sliceAt(false, true)(arr)).toEqual([1]); - expect(lamb.sliceAt([1.8], [4.9])(arr)).toEqual([2, 3, 4]); - expect(lamb.sliceAt(["1.8"], ["4.9"])(arr)).toEqual([2, 3, 4]); - - expect(lamb.sliceAt(new Date(1), new Date(3))(s)).toEqual(["e", "l"]); - expect(lamb.sliceAt(1.8, 3.9)(s)).toEqual(["e", "l"]); - expect(lamb.sliceAt(null, 3)(s)).toEqual(["h", "e", "l"]); - expect(lamb.sliceAt(1, null)(s)).toEqual([]); - expect(lamb.sliceAt("1.8", "4.9")(s)).toEqual(["e", "l", "l"]); - expect(lamb.sliceAt(false, true)(s)).toEqual(["h"]); - expect(lamb.sliceAt([1.8], [4.9])(s)).toEqual(["e", "l", "l"]); - expect(lamb.sliceAt(["1.8"], ["4.9"])(s)).toEqual(["e", "l", "l"]); - - expect(lamb.slice(arr, -4.8, -2.9)).toEqual([3, 4]); - expect(lamb.slice(arr, "-4.8", "-2.9")).toEqual([3, 4]); - expect(lamb.slice(arr, [-4.8], [-2.9])).toEqual([3, 4]); - - expect(lamb.slice(s, -4.8, -2.9)).toEqual(["o", "r"]); - expect(lamb.slice(s, "-4.8", "-2.9")).toEqual(["o", "r"]); - expect(lamb.slice(s, [-4.8], [-2.9])).toEqual(["o", "r"]); - - expect(lamb.sliceAt(-4.8, -2.9)(arr)).toEqual([3, 4]); - expect(lamb.sliceAt("-4.8", "-2.9")(arr)).toEqual([3, 4]); - expect(lamb.sliceAt([-4.8], [-2.9])(arr)).toEqual([3, 4]); - - expect(lamb.sliceAt(-4.8, -2.9)(s)).toEqual(["o", "r"]); - expect(lamb.sliceAt("-4.8", "-2.9")(s)).toEqual(["o", "r"]); - expect(lamb.sliceAt([-4.8], [-2.9])(s)).toEqual(["o", "r"]); - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.slice(arr, value, arr.length)).toEqual(arrCopy); - expect(lamb.slice(s, value, s.length)).toEqual(s.split("")); - expect(lamb.slice(arr, 0, value)).toEqual([]); - expect(lamb.slice(s, 0, value)).toEqual([]); - - expect(lamb.sliceAt(value, arr.length)(arr)).toEqual(arrCopy); - expect(lamb.sliceAt(value, s.length)(s)).toEqual(s.split("")); - expect(lamb.sliceAt(0, value)(arr)).toEqual([]); - expect(lamb.sliceAt(0, value)(s)).toEqual([]); - }); - }); - - it("should interpret as zeroes missing values in the `start` or `end` parameter", function () { - expect(lamb.slice(arr, 0)).toEqual([]); - expect(lamb.slice(s, 0)).toEqual([]); - expect(lamb.sliceAt(0)(arr)).toEqual([]); - expect(lamb.sliceAt(0)(s)).toEqual([]); - - expect(lamb.slice(arr)).toEqual([]); - expect(lamb.sliceAt()(arr)).toEqual([]); - expect(lamb.slice(s)).toEqual([]); - expect(lamb.sliceAt()(s)).toEqual([]); - }); - - it("should return an array with `undefined` values in place of unassigned or deleted indexes if a sparse array is received", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - var aSparse = [, 1, 2, , 4, , ,]; // length === 7, same as aDense - var aDense = [void 0, 1, 2, void 0, 4, void 0, void 0]; - var r1 = lamb.slice(aSparse, 0, aSparse.length); - var r2 = lamb.sliceAt(0, aSparse.length)(aSparse); - - expect(r1).toEqual(aDense); - expect(r1).not.toEqual(aSparse); - expect(r2).toEqual(aDense); - expect(r2).not.toEqual(aSparse); - }); - - it("should try, as native `slice` does, to retrieve elements from objects with a `length` property, but it should always build dense arrays", function () { - var objs = [ - {length: 3, 0: 1, 1: 2, 2: 3}, - {length: 3, 0: 1, 2: 3}, - {length: "2", 0: 1, 1: 2, 2: 3}, - {length: "-2", 0: 1, 1: 2, 2: 3} - ]; - - var results = [ [2, 3], [void 0, 3], [2], [] ]; - - objs.forEach(function (obj, idx) { - expect(lamb.slice(obj, 1, 3)).toEqual(results[idx]); - expect(lamb.sliceAt(1, 3)(obj)).toEqual(results[idx]); - }); - - var oSparse = {length: 2}; - var rSparse = Array(2); - var rDense = [void 0, void 0]; - - expect(lamb.slice(oSparse, 0, oSparse.length)).toEqual(rDense); - expect(lamb.slice(oSparse, 0, oSparse.length)).not.toEqual(rSparse); - expect(lamb.sliceAt(0, oSparse.length)(oSparse)).toEqual(rDense); - expect(lamb.sliceAt(0, oSparse.length)(oSparse)).not.toEqual(rSparse); - }); - - it("should work with array-like lengths up to 2^32 - 1", function () { - var maxLen = Math.pow(2, 32) - 1; - var maxIndex = maxLen - 1; - var obj = {length: maxLen + 100}; - - obj[maxIndex] = 99; - obj[maxIndex + 1] = 88; - - expect(lamb.slice(obj, -1, obj.length)).toEqual([99]); - expect(lamb.sliceAt(-1, obj.length)(obj)).toEqual([99]); - }); - - it("should throw an exception if it receives `null` or `undefined` instead of an array-like or if it's called without parameters", function () { - expect(function () { lamb.slice(null, 1, 2); }).toThrow(); - expect(function () { lamb.slice(void 0, 1, 2); }).toThrow(); - expect(function () { lamb.sliceAt(1, 2)(null); }).toThrow(); - expect(function () { lamb.sliceAt(1, 2)(void 0); }).toThrow(); - - expect(lamb.slice).toThrow(); - expect(lamb.sliceAt()).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.slice(value, 0, 2)).toEqual([]); - expect(lamb.sliceAt(0, 2)(value)).toEqual([]); - }); - }); - }); - - describe("some / someIn", function () { - var a1 = [1, 3, 5, 6, 7, 8]; - var a2 = [1, 3, 5, 7]; - var isEven = function (n, idx, list) { - expect(list[idx]).toBe(n); - - return n % 2 === 0; - }; - - it("should check if at least one element of an array satisfies the given predicate", function () { - expect(lamb.someIn(a1, isEven)).toBe(true); - expect(lamb.some(isEven)(a1)).toBe(true); - expect(lamb.someIn(a2, isEven)).toBe(false); - expect(lamb.some(isEven)(a2)).toBe(false); - }); - - it("should work with array-like objects", function () { - expect(lamb.someIn("134567", isEven)).toBe(true); - expect(lamb.some(isEven)("134567")).toBe(true); - expect(lamb.someIn("1357", isEven)).toBe(false); - expect(lamb.some(isEven)("1357")).toBe(false); - }); - - it("should always return false for empty array-likes", function () { - expect(lamb.someIn([], isEven)).toBe(false); - expect(lamb.some(isEven)([])).toBe(false); - expect(lamb.someIn("", isEven)).toBe(false); - expect(lamb.some(isEven)("")).toBe(false); - }); - - it("should stop calling the predicate as soon as a `true` value is returned", function () { - var isGreaterThan10 = jasmine.createSpy().and.callFake(function (n) { - return n > 10; - }); - var arr = [1, 3, 15, 10, 11]; - - expect(lamb.someIn(arr, isGreaterThan10)).toBe(true); - expect(isGreaterThan10.calls.count()).toBe(3); - expect(lamb.some(isGreaterThan10)(arr)).toBe(true); - expect(isGreaterThan10.calls.count()).toBe(6); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.someIn("hello", isVowel)).toBe(true); - expect(lamb.some(isVowel)("hello")).toBe(true); - expect(lamb.someIn("hll", isVowel)).toBe(false); - expect(lamb.some(isVowel)("hll")).toBe(false); - }); - - it("should not skip deleted or unassigned elements, unlike the native method", function () { - var arr = Array(5); - - arr[2] = 99; - - expect(lamb.someIn(arr, lamb.isUndefined)).toBe(true); - expect(lamb.some(lamb.isUndefined)(arr)).toBe(true); - }); - - it("should throw an exception if the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.someIn(a1, value); }).toThrow(); - expect(function () { lamb.some(value)(a1); }).toThrow(); - }); - - expect(function () { lamb.someIn(a1); }).toThrow(); - expect(function () { lamb.some()(a1); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.some(isEven)).toThrow(); - expect(lamb.someIn).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.someIn(null, isEven); }).toThrow(); - expect(function () { lamb.someIn(void 0, isEven); }).toThrow(); - expect(function () { lamb.some(isEven)(null); }).toThrow(); - expect(function () { lamb.some(isEven)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array and return `false`", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.someIn(value, isEven)).toBe(false); - expect(lamb.some(isEven)(value)).toBe(false); - }); - }); - }); -}); diff --git a/test/spec/coreSpec.js b/test/spec/coreSpec.js deleted file mode 100644 index 7f34f78..0000000 --- a/test/spec/coreSpec.js +++ /dev/null @@ -1,260 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var nonArrayLikes = commons.vars.nonArrayLikes; -var nonFunctions = commons.vars.nonFunctions; - -describe("lamb.core", function () { - describe("@@lamb/placeholder", function () { - it("should be the `lamb` object itself by default", function () { - expect(lamb["@@lamb/placeholder"]).toBe(lamb); - }); - - describe("placheholder change", function () { - var __ = {}; - - beforeEach(function () { - lamb["@@lamb/placeholder"] = __; - }); - - afterEach(function () { - expect(lamb["@@lamb/placeholder"]).toBe(__); - - lamb["@@lamb/placeholder"] = lamb; - - expect(lamb["@@lamb/placeholder"]).toBe(lamb); - }); - - it("should be possible to use a custom placeholder", function () { - var f1 = lamb.partial(lamb.list, ["a", __, __, "d"]); - var f2 = lamb.partialRight(lamb.list, ["a", __, __, "d"]); - var f3 = lamb.asPartial(lamb.list); - - expect(f1("b", "c", "e")).toEqual(["a", "b", "c", "d", "e"]); - expect(f2("b", "c", "e")).toEqual(["b", "a", "c", "e", "d"]); - expect(f3("a", __, __, "d")("b", __)("c", __)("e")).toEqual(["a", "b", "c", "d", "e"]); - }); - - it("should not affect lamb's functions built using partial application", function () { - expect(lamb.updateIndex([1, 2, 3], 1, lamb.add(5))).toEqual([1, 7, 3]); - }); - }); - }); - - describe("@@lamb/version", function () { - it("should equal the package.json version", function () { - expect(lamb["@@lamb/version"]).toBe(require("../../package.json").version); - }); - }); - - describe("always", function () { - it("should return a function that returns a constant value", function () { - var o = {a: 1}; - var fn = lamb.always(o); - var r1 = fn("foo"); - var r2 = fn(); - - expect(r1).toBe(o); - expect(r2).toBe(o); - expect(r1).toBe(r2); - }); - - it("should build a function returning `undefined` if called without arguments", function () { - expect(lamb.always()()).toBeUndefined(); - }); - }); - - describe("compose", function () { - var double = function (n) { return n * 2; }; - var cube = function (n) { return Math.pow(n, 3); }; - var changeSign = function (n) { return -n; }; - - var cubeAndDouble = lamb.compose(double, cube); - var doubleAndCube = lamb.compose(cube, double); - - it("should return a function that is the composition of the given functions; the first one consuming the return value of the function that follows", function () { - expect(cubeAndDouble(5)).toBe(250); - expect(doubleAndCube(5)).toBe(1000); - }); - - it("should be possible to reuse composed functions", function () { - var changeSignCubeAndDouble = lamb.compose(cubeAndDouble, changeSign); - - expect(changeSignCubeAndDouble(2)).toBe(-16); - }); - - it("should behave like `identity` if no functions are passed", function () { - var obj = {}; - - expect(lamb.compose()(obj)).toBe(obj); - expect(lamb.compose()()).toBeUndefined(); - expect(lamb.compose()(2, 3, 4)).toBe(2); - }); - - it("should ignore extra arguments", function () { - expect(lamb.compose(double, cube, changeSign)(5)).toBe(250); - }); - - it("should build a function throwing an exception if any parameter is not a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.compose(lamb.identity, value)).toThrow(); - expect(lamb.compose(value, lamb.identity)).toThrow(); - }); - - expect(lamb.compose(lamb.identity)).toThrow(); - }); - }); - - describe("generic", function () { - it("should create a generic function out of an object method", function () { - spyOn(Array.prototype, "map").and.callThrough(); - - var fakeContext = {}; - var double = function (n) { - expect(this).toBe(fakeContext); - - return n * 2; - }; - var numbers = [1, 2, 3, 4, 5]; - var map = lamb.generic(Array.prototype.map); - - expect(map(numbers, double, fakeContext)).toEqual([2, 4, 6, 8, 10]); - expect(Array.prototype.map.calls.count()).toBe(1); - expect(Array.prototype.map.calls.first().object).toBe(numbers); - expect(Array.prototype.map.calls.argsFor(0)[0]).toBe(double); - expect(Array.prototype.map.calls.argsFor(0)[1]).toBe(fakeContext); - }); - - it("should build a function throwing an exception if called without arguments or if `method` isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.generic(value)).toThrow(); - }); - - expect(lamb.generic()).toThrow(); - }); - }); - - describe("identity", function () { - it("should return the value received as argument", function () { - expect(lamb.identity(0)).toBe(0); - expect(lamb.identity("foo")).toBe("foo"); - expect(lamb.identity(null)).toBe(null); - expect(lamb.identity()).toBeUndefined(); - - var someRefType = {foo: 5}; - - expect(lamb.identity(someRefType)).toBe(someRefType); - }); - }); - - describe("partial / partialRight", function () { - var foo = function (a, b, c, d, e) { - return a + b + c + d + e; - }; - var _ = lamb; - - it("should return a partially applied function using the given arguments", function () { - var f1 = lamb.partial(foo, ["a", "b", "c"]); - var f2 = lamb.partialRight(foo, ["a", "b", "c"]); - - expect(f1("d", "e")).toBe("abcde"); - expect(f2("d", "e")).toBe("deabc"); - }); - - it("should accept placeholders as arguments", function () { - var f1 = lamb.partial(foo, [_, _, "c", _, "e"]); - var f2 = lamb.partialRight(foo, [_, _, "c", _, "e"]); - var f3 = lamb.partial(foo, [_, "c", _, "e"]); - var f4 = lamb.partialRight(foo, [_, "c", _, "e"]); - - expect(f1("a", "b", "d")).toBe("abcde"); - expect(f2("a", "b", "d")).toBe("abcde"); - expect(f3("a", "b", "d")).toBe("acbed"); - expect(f4("a", "b", "d")).toBe("abcde"); - }); - - it("should be possible to create a partial application from another partial application", function () { - var f1 = lamb.partial(foo, [_, "b", _, "d"]); - var f2 = lamb.partialRight(foo, [_, "b", _, "d"]); - - expect(lamb.partial(f1, ["a", _, "e"])("c")).toBe("abcde"); - expect(lamb.partial(f2, ["a", _, "e"])("c")).toBe("acbed"); - }); - - it("should give an `undefined` value to unfilled placeholders", function () { - var f1 = lamb.partial(lamb.list, [_, 2, _, 3, _, 5, _]); - var f2 = lamb.partialRight(lamb.list, [_, 2, _, 3, _, 5, _]); - - expect(f1(99)).toEqual([99, 2, void 0, 3, void 0, 5, void 0]); - expect(f1(98, 99)).toEqual([98, 2, 99, 3, void 0, 5, void 0]); - expect(f2(99)).toEqual([void 0, 2, void 0, 3, void 0, 5, 99]); - expect(f2(98, 99)).toEqual([void 0, 2, void 0, 3, 98, 5, 99]); - }); - - it("should be safe to call the partial application multiple times with different values for unfilled placeholders", function () { - var list = [{id: "foo"}, {id: "bar"}, {id: "baz"}]; - var getID1 = lamb.partial(lamb.getIn, [_, "id"]); - var getID2 = lamb.unary(lamb.partialRight(lamb.getIn, [_, "id"])); - - expect(list.map(getID1)).toEqual(["foo", "bar", "baz"]); - expect(list.map(getID2)).toEqual(["foo", "bar", "baz"]); - }); - - it("should pass all the received arguments to the partially applied function, even if they exceed the original function's arity", function () { - function binaryFoo (a, b) { - return a + b + arguments[2] + arguments[3] + arguments[4]; - } - - expect(lamb.partial(binaryFoo, ["a", _, _, "d"])("b", "c", "e")).toBe("abcde"); - expect(lamb.partialRight(binaryFoo, ["a", _, _, "d"])("b", "c", "e")).toBe("baced"); - }); - - it("should preserve the function's context", function () { - var fn = function (a, b) { - this.values.push(a - b); - }; - - var obj = { - values: [1, 2, 3], - foo: lamb.partial(fn, [4, _]), - bar: lamb.partialRight(fn, [_, 4]) - }; - - obj.foo(5); - expect(obj.values).toEqual([1, 2, 3, -1]); - - obj.bar(5); - expect(obj.values).toEqual([1, 2, 3, -1, 1]); - }); - - it("should consider non-arrays received as the `args` parameter as empty arrays", function () { - var divideSpy = jasmine.createSpy().and.callFake(lamb.divide); - - nonArrayLikes.forEach(function (value, idx) { - expect(lamb.partial(divideSpy, value)(5, 4)).toBe(1.25); - expect(lamb.partialRight(divideSpy, value)(5, 4)).toBe(1.25); - expect(divideSpy.calls.argsFor(idx * 2)).toEqual([5, 4]); - expect(divideSpy.calls.argsFor(idx * 2 + 1)).toEqual([5, 4]); - }); - - expect(lamb.partial(divideSpy)(5, 4)).toBe(1.25); - expect(lamb.partialRight(divideSpy)(5, 4)).toBe(1.25); - expect(divideSpy.calls.argsFor(nonArrayLikes.length * 2)).toEqual([5, 4]); - expect(divideSpy.calls.argsFor(nonArrayLikes.length * 2 + 1)).toEqual([5, 4]); - expect(divideSpy.calls.count()).toBe(nonArrayLikes.length * 2 + 2); - }); - - it("should build a function throwing an exception if called without arguments or if `fn` isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.partial(value)).toThrow(); - expect(lamb.partialRight(value)).toThrow(); - }); - - expect(lamb.partial()).toThrow(); - expect(lamb.partialRight()).toThrow(); - }); - }); -}); diff --git a/test/spec/functionSpec.js b/test/spec/functionSpec.js deleted file mode 100644 index 1ffa9da..0000000 --- a/test/spec/functionSpec.js +++ /dev/null @@ -1,983 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var nonArrayLikes = commons.vars.nonArrayLikes; -var nonFunctions = commons.vars.nonFunctions; -var nonStrings = commons.vars.nonStrings; -var nonStringsAsStrings = commons.vars.nonStringsAsStrings; -var wannabeEmptyArrays = commons.vars.wannabeEmptyArrays; -var wannabeEmptyObjects = commons.vars.wannabeEmptyObjects; -var zeroesAsIntegers = commons.vars.zeroesAsIntegers; - -describe("lamb.function", function () { - function Foo (value) { - this.value = value; - } - - Foo.prototype = { - value: 0, - bar: function (a, b) { - return (this.value + a) / b; - } - }; - - describe("application / apply / applyTo", function () { - it("should apply the desired function to the given arguments", function () { - expect(lamb.application(Math.max, [-1, 3, 2, 15, 7])).toBe(15); - expect(lamb.apply(Math.max)([-1, 3, 2, 15, 7])).toBe(15); - expect(lamb.applyTo([-1, 3, 2, 15, 7])(Math.max)).toBe(15); - }); - - it("should accept an array-like object as arguments for the function", function () { - expect(lamb.application(Math.max, "3412")).toBe(4); - expect(lamb.apply(Math.max)("3412")).toBe(4); - expect(lamb.applyTo("3412")(Math.max)).toBe(4); - }); - - it("should not alter the function's context", function () { - var obj = { - value: 4, - application: lamb.application, - applyBar: lamb.apply(Foo.prototype.bar), - baz: lamb.applyTo([1, 2]) - }; - - expect(obj.application(Foo.prototype.bar, [1, 2])).toBe(2.5); - expect(obj.applyBar([1, 2])).toBe(2.5); - expect(obj.baz(Foo.prototype.bar)).toBe(2.5); - }); - - it("should treat non-array-like values for the `args` parameter as empty arrays", function () { - var fooSpy = jasmine.createSpy("fooSpy"); - - for (var i = 0, ofs = 0; i < nonArrayLikes.length; i++, ofs += 3) { - lamb.application(fooSpy, nonArrayLikes[i]); - lamb.apply(fooSpy)(nonArrayLikes[i]); - lamb.applyTo(nonArrayLikes[i])(fooSpy); - expect(fooSpy.calls.argsFor(ofs).length).toBe(0); - expect(fooSpy.calls.argsFor(ofs + 1).length).toBe(0); - expect(fooSpy.calls.argsFor(ofs + 2).length).toBe(0); - } - - lamb.application(fooSpy); - lamb.apply(fooSpy)(); - lamb.applyTo()(fooSpy); - - expect(fooSpy.calls.argsFor(ofs).length).toBe(0); - expect(fooSpy.calls.argsFor(ofs + 1).length).toBe(0); - expect(fooSpy.calls.argsFor(ofs + 2).length).toBe(0); - expect(fooSpy.calls.count()).toBe(ofs + 3); - }); - - it("should throw an exception if `fn` isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.application(value, []); }).toThrow(); - expect(lamb.apply(value)).toThrow(); - expect(function () { lamb.applyTo([])(value); }).toThrow(); - }); - }); - }); - - describe("aritize", function () { - var maxArgument = function () { return Math.max.apply(null, arguments); }; - var maxArgumentSpy = jasmine.createSpy("maxArgument").and.callFake(maxArgument); - - afterEach(function () { - maxArgumentSpy.calls.reset(); - }); - - it("should change the arity of the given function to the specified value", function () { - var maxOfFirst3 = lamb.aritize(maxArgumentSpy, 3); - - expect(maxOfFirst3(0, 1, 2, 3, 4, 5)).toBe(2); - expect(maxArgumentSpy.calls.argsFor(0)).toEqual([0, 1, 2]); - }); - - it("should allow negative arities", function () { - expect(lamb.aritize(maxArgumentSpy, -1)(0, 1, 2, 3)).toBe(2); - expect(maxArgumentSpy.calls.argsFor(0)).toEqual([0, 1, 2]); - }); - - it("should call the function without arguments if the arity is zero or if it's out of bounds", function () { - expect(lamb.aritize(maxArgumentSpy, -10)(0, 1, 2, 3)).toBe(-Infinity); - expect(lamb.aritize(maxArgumentSpy, 0)(0, 1, 2, 3)).toBe(-Infinity); - expect(maxArgumentSpy.calls.count()).toBe(2); - expect(maxArgumentSpy.calls.argsFor(0).length).toBe(0); - expect(maxArgumentSpy.calls.argsFor(1).length).toBe(0); - }); - - it("should add `undefined` arguments if the desired arity is greater than the amount of received parameters", function () { - expect(lamb.aritize(maxArgumentSpy, 6)(0, 1, 2, 3)).toEqual(NaN); - expect(maxArgumentSpy.calls.argsFor(0)).toEqual([0, 1, 2, 3, void 0, void 0]); - }); - - it("should convert the arity to an integer following ECMA specifications", function () { - // see https://www.ecma-international.org/ecma-262/7.0/#sec-tointeger - - zeroesAsIntegers.forEach(function (value, idx) { - expect(lamb.aritize(maxArgumentSpy, value)(0, 1, 2, 3, 4, 5)).toBe(-Infinity); - expect(maxArgumentSpy.calls.argsFor(idx).length).toBe(0); - }); - - expect(lamb.aritize(maxArgumentSpy)(0, 1, 2, 3, 4, 5)).toBe(-Infinity); - expect(maxArgumentSpy.calls.mostRecent().args.length).toBe(0); - - maxArgumentSpy.calls.reset(); - - [[5], 5.9, "5.9", "-1", ["-1.9"]].forEach(function (value, idx) { - expect(lamb.aritize(maxArgumentSpy, value)(0, 1, 2, 3, 4, 5)).toBe(4); - expect(maxArgumentSpy.calls.argsFor(idx)).toEqual([0, 1, 2, 3, 4]); - }); - }); - - it("should not modify the function's context", function () { - var fn = function () { - this.values = this.values.concat(lamb.slice(arguments, 0, arguments.length)); - }; - - var obj = {values: [1, 2, 3], addValues: lamb.aritize(fn, 2)}; - - obj.addValues(4, 5, 6, 7); - - expect(obj.values).toEqual([1, 2, 3, 4, 5]); - }); - - it("should build a function throwing an exception if the `fn` parameter isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.aritize(value, 0)).toThrow(); - }); - - expect(lamb.aritize()).toThrow(); - }); - }); - - describe("asPartial", function () { - var fooSubtract = function (a, b, c, d) { - return a - b - c - d; - }; - var _ = lamb; - var fooSubtractSpy = jasmine.createSpy("fooSubtractSpy").and.callFake(fooSubtract); - - afterEach(function () { - fooSubtractSpy.calls.reset(); - }); - - it("should build a function that returns a partial application of the original one as long as it's called with placeholders", function () { - var fn = _.asPartial(fooSubtract); - - expect(fn(_, 4, _, _)(_, 3, _)(5, _)(2)).toBe(-4); - expect(fn(_)(_, _)(_)(5, _)(_, 3)(4, _)(2)).toBe(-4); - expect(fn(_, 4, _, _)(_, 3, _)(5, _, _, _, 2, _, _)(99, 6)).toBe(-101); - expect(fn(3, 2, 1, 0)).toBe(0); - expect(fn(5, _, 3)(_)(_, _)(4, _)(2)).toBe(-4); - expect(fn(_, 2, _, 0)(_, _)(3, _)(_)(1)).toBe(0); - expect(fn(5, _, _, _)(4, 3, 2)).toBe(-4); - }); - - it("should be safe to call the partial application multiple times with different values for unfilled placeholders", function () { - var fn = _.asPartial(fooSubtract)(_, 5, _, _); - var minusTen1 = fn(_, 4, _)(_, 1); - var minusTen2 = fn(_, 3, 2); - var minusTen3 = fn(_, -5, 10); - - var numbers = [-1000, 6, 502, 856, 790, 547, 157, 750, 111, -419]; - var results = [-1010, -4, 492, 846, 780, 537, 147, 740, 101, -429]; - - expect(numbers.map(minusTen1)).toEqual(results); - expect(numbers.map(minusTen2)).toEqual(results); - expect(numbers.map(minusTen3)).toEqual(results); - }); - - it("should build a function that applies the original function when it's called without placeholders even if its arity isn't consumed", function () { - var fn = _.asPartial(fooSubtractSpy); - - expect(fn(5, _, 3)(4)).toEqual(NaN); - expect(fooSubtractSpy.calls.count()).toBe(1); - expect(fooSubtractSpy.calls.argsFor(0)).toEqual([5, 4, 3]); - }); - - it("should pass all the received arguments, even if they exceed the original function's arity", function () { - var fn = _.asPartial(fooSubtractSpy); - - expect(fn(_, 4, _, 2)(5, 3, 6, 7, 8)).toBe(-4); - expect(fooSubtractSpy.calls.count()).toBe(1); - expect(fooSubtractSpy.calls.argsFor(0)).toEqual([5, 4, 3, 2, 6, 7, 8]); - }); - - it("should give an `undefined` value to unfilled placeholders", function () { - var fn = lamb.asPartial(lamb.list)(_, 2, _, 3, _, 5, _); - - expect(fn(1)).toEqual([1, 2, void 0, 3, void 0, 5, void 0]); - }); - - it("should preserve the function's context", function () { - var fn = lamb.asPartial(function (a, b) { - this.values.push(a - b); - }); - - var obj = { - values: [1, 2, 3], - foo: fn(4, _), - bar: fn(_, 4) - }; - - obj.foo(5); - expect(obj.values).toEqual([1, 2, 3, -1]); - - obj.bar(5); - expect(obj.values).toEqual([1, 2, 3, -1, 1]); - }); - - it("should build a function throwing an exception if called without arguments or if `fn` isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.asPartial(value)).toThrow(); - }); - - expect(lamb.asPartial()).toThrow(); - }); - }); - - describe("binary", function () { - var binaryList; - - beforeEach(function () { - spyOn(lamb, "list").and.callThrough(); - binaryList = lamb.binary(lamb.list); - }); - - afterEach(function () { - lamb.list.calls.reset(); - }); - - it("should build a function that passes only two arguments to the given one", function () { - expect(binaryList.length).toBe(2); - expect(binaryList(1, 2, 3)).toEqual([1, 2]); - expect(lamb.list.calls.argsFor(0)).toEqual([1, 2]); - }); - - it("should add `undefined` arguments if the received parameters aren't two", function () { - expect(binaryList()).toEqual([void 0, void 0]); - expect(binaryList(1)).toEqual([1, void 0]); - }); - - it("should not modify the function's context", function () { - var fn = function () { - this.values = this.values.concat(lamb.slice(arguments, 0, arguments.length)); - }; - - var obj = {values: [1, 2, 3], addValues: lamb.binary(fn)}; - - obj.addValues(4, 5, 6, 7); - - expect(obj.values).toEqual([1, 2, 3, 4, 5]); - }); - - it("should build a function throwing an exception if the `fn` parameter isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.binary(value)).toThrow(); - }); - - expect(lamb.binary()).toThrow(); - }); - }); - - describe("collect", function () { - it("should collect the values returned by the given series of functions applied with the provided parameters", function () { - var min = jasmine.createSpy("min").and.callFake(Math.min); - var max = jasmine.createSpy("max").and.callFake(Math.max); - var minAndMax = lamb.collect([min, max]); - - expect(minAndMax(3, 1, -2, 5, 4, -1)).toEqual([-2, 5]); - expect(min.calls.count()).toBe(1); - expect(max.calls.count()).toBe(1); - expect(min.calls.argsFor(0)).toEqual([3, 1, -2, 5, 4, -1]); - expect(max.calls.argsFor(0)).toEqual([3, 1, -2, 5, 4, -1]); - }); - - it("should return an empty array if the array of functions is empty", function () { - expect(lamb.collect([])(1, 2, 3)).toEqual([]); - }); - - it("should call the received functions even if there are no provided parameters", function () { - expect(lamb.collect([lamb.identity, lamb.always(99)])()).toEqual([void 0, 99]); - }); - - it("should throw an exception if the received parameter isn't an array", function () { - nonArrayLikes.forEach(function (value) { - expect(function () { lamb.collect(value); }).toThrow(); - }); - - expect(lamb.collect).toThrow(); - }); - - it("should build a function returning an exception if it receives a value that isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.collect([lamb.always(99), value])(1, 2, 3); }).toThrow(); - expect(function () { lamb.collect([value, lamb.always(99)])(1, 2, 3); }).toThrow(); - }); - }); - }); - - describe("currying", function () { - var fooSubtract = function (a, b, c) { - return a - b - c; - }; - var subtractSpy = jasmine.createSpy("fooSubtract").and.callFake(fooSubtract); - - afterEach(function () { - subtractSpy.calls.reset(); - }); - - describe("curry / curryRight", function () { - it("should allow currying by always returning a function with an arity of one", function () { - var sub1 = lamb.curry(fooSubtract, 3); - var sub2 = lamb.curryRight(fooSubtract, 3); - - expect(sub1(1)(2)(3)).toBe(-4); - expect(sub2(1)(2)(3)).toBe(0); - }); - - it("should try to desume the arity from the function's length", function () { - var sub1 = lamb.curry(fooSubtract); - var sub2 = lamb.curryRight(fooSubtract); - - expect(sub1(1)(2)(3)).toBe(-4); - expect(sub2(1)(2)(3)).toBe(0); - }); - - it("should return the received function if the desumed or given arity isn't greater than one", function () { - expect(lamb.curry(lamb.list)).toBe(lamb.list); - expect(lamb.curryRight(lamb.list)).toBe(lamb.list); - expect(lamb.curry(lamb.head)).toBe(lamb.head); - expect(lamb.curryRight(lamb.head)).toBe(lamb.head); - }); - - it("should give priority to the `arity` parameter over the function's length", function () { - var sub1 = lamb.curry(subtractSpy, 2); - var sub2 = lamb.curryRight(subtractSpy, 2); - - expect(sub1(1)(2)).toEqual(NaN); - expect(sub2(1)(2)).toEqual(NaN); - - expect(subtractSpy.calls.count()).toBe(2); - expect(subtractSpy.calls.argsFor(0)).toEqual([1, 2]); - expect(subtractSpy.calls.argsFor(1)).toEqual([2, 1]); - }); - - it("should ignore extra parameters", function () { - var sub1 = lamb.curry(subtractSpy, 3); - var sub2 = lamb.curryRight(subtractSpy, 3); - - expect(sub1(1, 99)(2, 88, 77)(3, 66)).toBe(-4); - expect(sub2(1, 99)(2, 88, 77)(3, 66)).toBe(0); - - expect(subtractSpy.calls.count()).toBe(2); - expect(subtractSpy.calls.argsFor(0)).toEqual([1, 2, 3]); - expect(subtractSpy.calls.argsFor(1)).toEqual([3, 2, 1]); - }); - - it("should let empty calls consume the arity", function () { - var collectLeft = lamb.curry(lamb.list, 4); - var collectRight = lamb.curryRight(lamb.list, 4); - - expect(collectLeft("a", "z")()("c")("d")).toEqual(["a", void 0, "c", "d"]); - expect(collectRight("a", "z")()("c")("d")).toEqual(["d", "c", void 0, "a"]); - }); - - it("should return a reusable function", function () { - var fooSubFromFive = lamb.curry(fooSubtract)(5); - var fooSubMinusFive = lamb.curryRight(fooSubtract)(5); - var subFromOne = fooSubFromFive(4); - var subFromTwo = fooSubFromFive(3); - var minusNine = fooSubMinusFive(4); - var minusSix = fooSubMinusFive(1); - - expect(fooSubFromFive(4)(3)).toBe(-2); - expect(fooSubMinusFive(4)(3)).toBe(-6); - expect(subFromOne(1)).toBe(0); - expect(subFromOne(3)).toBe(-2); - expect(subFromTwo(3)).toBe(-1); - expect(subFromTwo(2)).toBe(0); - expect(minusNine(10)).toBe(1); - expect(minusNine(9)).toBe(0); - expect(minusSix(10)).toBe(4); - expect(minusSix(4)).toBe(-2); - }); - - it("should preserve the function's context", function () { - var fn = function (a, b) { - this.values.push(a - b); - }; - - var obj = { - values: [1, 2, 3], - foo: lamb.curry(fn)(4), - bar: lamb.curryRight(fn)(4) - }; - - obj.foo(5); - expect(obj.values).toEqual([1, 2, 3, -1]); - - obj.bar(5); - expect(obj.values).toEqual([1, 2, 3, -1, 1]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.curry).toThrow(); - expect(lamb.curryRight).toThrow(); - }); - }); - - describe("curryable / curryableRight", function () { - it("should build an \"auto-curried\" function that allows us to consume its arity in any moment", function () { - var sub1 = lamb.curryable(fooSubtract, 3); - var sub2 = lamb.curryableRight(fooSubtract, 3); - - expect(sub1(1, 2, 3)).toBe(-4); - expect(sub1(1, 2)(3)).toBe(-4); - expect(sub1(1)(2, 3)).toBe(-4); - expect(sub1(1)(2)(3)).toBe(-4); - expect(sub2(1, 2, 3)).toBe(0); - expect(sub2(1, 2)(3)).toBe(0); - expect(sub2(1)(2, 3)).toBe(0); - expect(sub2(1)(2)(3)).toBe(0); - }); - - it("should try to desume the arity from the function's length", function () { - var sub1 = lamb.curryable(fooSubtract); - var sub2 = lamb.curryableRight(fooSubtract); - - expect(sub1(1)(2, 3)).toBe(-4); - expect(sub2(1)(2, 3)).toBe(0); - }); - - it("should return the received function if the desumed or given arity isn't greater than one", function () { - expect(lamb.curryable(lamb.list)).toBe(lamb.list); - expect(lamb.curryableRight(lamb.list)).toBe(lamb.list); - expect(lamb.curryable(lamb.head)).toBe(lamb.head); - expect(lamb.curryableRight(lamb.head)).toBe(lamb.head); - }); - - it("should give priority to the `arity` parameter over the function's length", function () { - var sub1 = lamb.curryable(subtractSpy, 2); - var sub2 = lamb.curryableRight(subtractSpy, 2); - - expect(sub1(1)(2)).toEqual(NaN); - expect(sub2(1)(2)).toEqual(NaN); - - expect(subtractSpy.calls.count()).toBe(2); - expect(subtractSpy.calls.argsFor(0)).toEqual([1, 2]); - expect(subtractSpy.calls.argsFor(1)).toEqual([2, 1]); - }); - - it("should pass extra arguments received to the original function", function () { - var foo = jasmine.createSpy("foo"); - var args = [1, 2, 3, 4, 5]; - - lamb.curryable(foo, 3)(1, 2)(3, 4, 5); - lamb.curryableRight(foo, 4)(5, 4)(3)(2, 1); - lamb.curryable(subtractSpy, 3)(1, 2)(3, 4, 5); - lamb.curryableRight(subtractSpy, 3)(5, 4)(3, 2, 1); - - expect(foo.calls.count()).toBe(2); - expect(foo.calls.argsFor(0)).toEqual(args); - expect(foo.calls.argsFor(1)).toEqual(args); - expect(subtractSpy.calls.count()).toBe(2); - expect(subtractSpy.calls.argsFor(0)).toEqual(args); - expect(subtractSpy.calls.argsFor(1)).toEqual(args); - }); - - it("should let empty calls consume the arity", function () { - var collectLeft = lamb.curryable(lamb.list, 4); - var collectRight = lamb.curryableRight(lamb.list, 4); - - expect(collectLeft("a")()("c", "d")).toEqual(["a", void 0, "c", "d"]); - expect(collectRight("a")()("c", "d")).toEqual(["d", "c", void 0, "a"]); - }); - - it("should return a reusable function", function () { - var fooSubFromFive = lamb.curryable(fooSubtract)(5); - var fooSubMinusFive = lamb.curryableRight(fooSubtract)(5); - var subFromOne = fooSubFromFive(4); - var subFromTwo = fooSubFromFive(3); - var minusNine = fooSubMinusFive(4); - var minusSix = fooSubMinusFive(1); - - expect(fooSubFromFive(4, 3)).toBe(-2); - expect(fooSubMinusFive(4, 3)).toBe(-6); - expect(subFromOne(1)).toBe(0); - expect(subFromOne(3)).toBe(-2); - expect(subFromTwo(3)).toBe(-1); - expect(subFromTwo(2)).toBe(0); - expect(minusNine(10)).toBe(1); - expect(minusNine(9)).toBe(0); - expect(minusSix(10)).toBe(4); - expect(minusSix(4)).toBe(-2); - }); - - it("should preserve the function's context", function () { - var fn = function (a, b) { - this.values.push(a - b); - }; - - var obj = { - values: [1, 2, 3], - foo: lamb.curryable(fn), - bar: lamb.curryableRight(fn) - }; - - obj.foo(4, 5); - expect(obj.values).toEqual([1, 2, 3, -1]); - - obj.bar(4, 5); - expect(obj.values).toEqual([1, 2, 3, -1, 1]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.curryable).toThrow(); - expect(lamb.curryableRight).toThrow(); - }); - }); - }); - - describe("debounce", function () { - var value; - var testFn; - - beforeEach(function () { - jasmine.clock().install(); - value = 0; - testFn = jasmine.createSpy("testFn").and.callFake(function (n) { value += n; }); - }); - - afterEach(function () { - jasmine.clock().uninstall(); - }); - - it("should return a function that will execute the given function only if it stops being called for the specified timespan", function () { - var debounced = lamb.debounce(testFn, 100); - - jasmine.clock().mockDate(new Date()); - - debounced(1); - debounced(2); - debounced(3); - - expect(testFn.calls.count()).toBe(0); - expect(value).toBe(0); - - jasmine.clock().tick(101); - - expect(testFn.calls.count()).toBe(1); - expect(value).toBe(3); - }); - }); - - describe("flip", function () { - it("should return a function that applies its arguments to the original function in reverse order", function () { - var appendTo = function (a, b) { return a + b; }; - var prependTo = lamb.flip(appendTo); - - expect(prependTo("foo", "bar")).toBe("barfoo"); - }); - }); - - describe("getArgAt", function () { - it("should build a function that returns the argument received at the given index", function () { - expect(lamb.getArgAt(1)("a", "b", "c")).toBe("b"); - }); - - it("should allow negative indexes", function () { - expect(lamb.getArgAt(-1)("a", "b", "c")).toBe("c"); - expect(lamb.getArgAt(-2)("a", "b", "c")).toBe("b"); - }); - - it("should build a function returning `undefined` if no arguments are passed or if the index is out of bounds", function () { - expect(lamb.getArgAt(6)("a", "b", "c")).toBeUndefined(); - expect(lamb.getArgAt(-4)("a", "b", "c")).toBeUndefined(); - expect(lamb.getArgAt(2)()).toBeUndefined(); - }); - - it("should convert the `idx` parameter to integer", function () { - zeroesAsIntegers.forEach(function (value) { - expect(lamb.getArgAt(value)("a", "b", "c")).toBe("a"); - }); - - [[1], 1.5, 1.25, 1.75, true, "1"].forEach(function (value) { - expect(lamb.getArgAt(value)("a", "b", "c")).toBe("b"); - }); - - expect(lamb.getArgAt(new Date())("a", "b", "c")).toBeUndefined(); - expect(lamb.getArgAt()("a", "b", "c")).toBe("a"); - }); - }); - - describe("invoker", function () { - var slice = lamb.invoker("slice"); - var tail = lamb.invoker("slice", 1); - var arr = [1, 2, 3, 4, 5]; - - beforeEach(function () { - spyOn(arr, "slice").and.callThrough(); - }); - - afterEach(function () { - arr.slice.calls.reset(); - }); - - it("should build a function that will invoke the desired method on the given object", function () { - var s = "foo bar"; - - expect(slice(arr, 1, 3)).toEqual([2, 3]); - expect(arr.slice.calls.count()).toBe(1); - expect(arr.slice.calls.first().object).toBe(arr); - expect(arr.slice.calls.argsFor(0)).toEqual([1, 3]); - expect(slice(s, 1, 3)).toBe("oo"); - }); - - it("should allow bound arguments", function () { - expect(tail(arr)).toEqual([2, 3, 4, 5]); - expect(tail(arr, -1)).toEqual([2, 3, 4]); - expect(arr.slice.calls.count()).toBe(2); - expect(arr.slice.calls.first().object).toBe(arr); - expect(arr.slice.calls.mostRecent().object).toBe(arr); - expect(arr.slice.calls.argsFor(0)).toEqual([1]); - expect(arr.slice.calls.argsFor(1)).toEqual([1, -1]); - }); - - it("should build a function returning `undefined` if the given method doesn't exist on the received object", function () { - expect(slice({})).toBeUndefined(); - expect(slice(new Date())).toBeUndefined(); - }); - - it("should accept an empty string as a method name", function () { - var obj = {"": function () { return 99; }}; - - expect(lamb.invoker("")(obj)).toBe(99); - }); - - it("should convert to string every value received as a method name", function () { - var obj = {}; - - nonStringsAsStrings.forEach(function (method, idx) { - obj[method] = lamb.always(method); - - expect(lamb.invoker(nonStrings[idx])(obj)).toBe(method); - }); - - expect(lamb.invoker()(obj)).toBe("undefined"); - }); - - it("should build a function throwing an exception if the received object is `null`, `undefined` or is missing", function () { - expect(function () { slice(null); }).toThrow(); - expect(function () { slice(void 0); }).toThrow(); - expect(slice).toThrow(); - }); - - it("should build a function that converts to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - if (value !== "") { - expect(slice(value)).toBeUndefined(); - } - }); - }); - }); - - describe("invokerOn", function () { - var arr = [1, 2, 3, 4, 5]; - var callOnArr = lamb.invokerOn(arr); - - beforeEach(function () { - spyOn(arr, "slice").and.callThrough(); - spyOn(arr, "join").and.callThrough(); - }); - - afterEach(function () { - arr.slice.calls.reset(); - arr.join.calls.reset(); - }); - - it("should accept an object and build a function expecting a method name to be called on such object with the given parameters", function () { - var s = "foo bar"; - var callOnS = lamb.invokerOn(s); - - expect(callOnArr("slice", 1, 3)).toEqual([2, 3]); - expect(arr.slice.calls.count()).toBe(1); - expect(arr.slice.calls.first().object).toBe(arr); - expect(arr.slice.calls.argsFor(0)).toEqual([1, 3]); - - expect(callOnArr("join", "")).toBe("12345"); - expect(arr.join.calls.count()).toBe(1); - expect(arr.join.calls.first().object).toBe(arr); - expect(arr.join.calls.argsFor(0)).toEqual([""]); - - expect(callOnS("slice", 1, 3)).toBe("oo"); - expect(callOnS("toUpperCase")).toBe("FOO BAR"); - }); - - it("should build a function returning `undefined` if the given method doesn't exist on the received object", function () { - expect(callOnArr("foo")).toBeUndefined(); - }); - - it("should accept an empty string as a method name", function () { - var obj = {"": function () { return 99; }}; - - expect(lamb.invokerOn(obj)("")).toBe(99); - }); - - it("should convert to string every value received as a method name", function () { - var obj = {}; - var callOnObj = lamb.invokerOn(obj); - - nonStringsAsStrings.forEach(function (method, idx) { - obj[method] = lamb.always(method); - - expect(callOnObj(nonStrings[idx])).toBe(method); - }); - - expect(callOnObj()).toBe("undefined"); - }); - - it("should build a function throwing an exception if the received object is `null`, `undefined` or is missing", function () { - expect(lamb.invokerOn(null)).toThrow(); - expect(lamb.invokerOn(void 0)).toThrow(); - expect(lamb.invokerOn()).toThrow(); - }); - - it("should build a function that converts to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.invokerOn(value)("someMethod")).toBeUndefined(); - }); - }); - }); - - describe("mapArgs", function () { - var double = jasmine.createSpy("double").and.callFake(function (n) { return n * 2; }); - - afterEach(function () { - double.calls.reset(); - }); - - it("should build a function that allows to map over the received arguments before applying them to the original one", function () { - expect(lamb.mapArgs(lamb.sum, double)(5, 3)).toBe(16); - expect(double.calls.count()).toBe(2); - expect(double.calls.argsFor(0)).toEqual([5, 0, [5, 3]]); - expect(double.calls.argsFor(1)).toEqual([3, 1, [5, 3]]); - }); - - it("should build a function throwing an exception if the mapper isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.mapArgs(lamb.sum, value)(1, 2); }).toThrow(); - }); - - expect(function () { lamb.mapArgs(lamb.sum)(1, 2); }).toThrow(); - }); - - it("should build a function throwing an exception if `fn` isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.mapArgs(value, double)).toThrow(); - }); - }); - - it("should build a function throwing an exception if called without arguments", function () { - expect(lamb.mapArgs()).toThrow(); - }); - }); - - describe("pipe", function () { - var double = function (n) { return n * 2; }; - var cube = function (n) { return Math.pow(n, 3); }; - var changeSign = function (n) { return -n; }; - - it("should return a function that is the pipeline of the given ones", function () { - var pipeline = lamb.pipe([cube, changeSign, double]); - - expect(pipeline(2)).toBe(-16); - }); - - it("should be possible to reuse piped functions", function () { - var cubeAndDouble = lamb.pipe([cube, double]); - var fn1 = lamb.pipe([cubeAndDouble, cube, double]); - var fn2 = lamb.pipe([cubeAndDouble, cubeAndDouble]); - - expect(fn1(5)).toBe(31250000); - expect(fn2(5)).toBe(31250000); - }); - - it("should behave like the received function if only one function is supplied", function () { - var fn = function (a, b, c) { return a - b - c; }; - - expect(lamb.pipe([fn])(5, 4, 3)).toBe(-2); - }); - - it("should behave like `identity` if no functions are passed", function () { - var obj = {}; - - expect(lamb.pipe([])(obj)).toBe(obj); - expect(lamb.pipe([])()).toBeUndefined(); - expect(lamb.pipe([])(2, 3, 4)).toBe(2); - }); - - it("should throw an exception if the received parameter isn't an array", function () { - nonArrayLikes.forEach(function (value) { - expect(function () { lamb.pipe(value); }).toThrow(); - }); - - expect(lamb.pipe).toThrow(); - }); - - it("should build a function throwing an exception if any parameter is not a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.pipe([lamb.identity, value])).toThrow(); - expect(lamb.pipe([value, lamb.identity])).toThrow(); - }); - }); - }); - - describe("tapArgs", function () { - var someObject = {count: 5}; - var someArrayData = [2, 3, 123, 5, 6, 7, 54, 65, 76, 0]; - var sum = jasmine.createSpy("sum").and.callFake(lamb.sum); - - afterEach(function () { - sum.calls.reset(); - }); - - it("should build a function that allows to tap into the arguments of the original one", function () { - var getDataAmount = lamb.tapArgs(sum, [lamb.getKey("count"), lamb.getKey("length")]); - - expect(getDataAmount(someObject, someArrayData)).toBe(15); - expect(sum.calls.count()).toBe(1); - expect(sum.calls.argsFor(0)).toEqual([someObject.count, someArrayData.length]); - }); - - it("should use arguments as they are when tappers are missing", function () { - expect(lamb.tapArgs(sum, [lamb.getKey("count")])(someObject, -10)).toBe(-5); - expect(sum.calls.count()).toBe(1); - expect(sum.calls.argsFor(0)).toEqual([someObject.count, -10]); - }); - - it("should build a function throwing an exception if a tapper isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.tapArgs(lamb.sum, [value, lamb.always(99)])(1, 2); }).toThrow(); - expect(function () { lamb.tapArgs(lamb.sum, [lamb.always(99), value])(1, 2); }).toThrow(); - }); - }); - - it("should build a function that doesn't throw an exception if a tapper isn't a function but is not called", function () { - var inc = function (n) { return ++n; }; - - nonFunctions.forEach(function (value) { - expect(lamb.tapArgs(lamb.sum, [value, lamb.always(99)])()).toEqual(NaN); - expect(lamb.tapArgs(inc, [inc, value])(25)).toBe(27); - }); - }); - - it("should build a function throwing an exception if a `nil` value is passed as the tappers array", function () { - expect(function () { lamb.tapArgs(sum, null)(2, 3); }).toThrow(); - expect(function () { lamb.tapArgs(sum, void 0)(2, 3); }).toThrow(); - }); - - it("should consider other values as empty arrays", function () { - wannabeEmptyArrays.forEach(function (value, idx) { - expect(lamb.tapArgs(sum, value)(2, 3)).toBe(5); - expect(sum.calls.argsFor(idx)).toEqual([2, 3]); - }); - }); - - it("should build a function throwing an exception if `fn` isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.tapArgs(value, [lamb.always(99)])).toThrow(); - }); - - expect(lamb.tapArgs()).toThrow(); - }); - }); - - describe("throttle", function () { - var foo; - - beforeEach(function () { - jasmine.clock().install(); - foo = jasmine.createSpy("foo").and.returnValue(42); - }); - - afterEach(function () { - jasmine.clock().uninstall(); - }); - - it("should return a function that will invoke the passed function at most once in the given timespan", function () { - var throttledFoo = lamb.throttle(foo, 100); - var r1 = throttledFoo(); - var r2 = throttledFoo(); - var r3 = throttledFoo(); - - expect(foo.calls.count()).toBe(1); - expect(r1 === 42).toBe(true); - expect(r2 === 42).toBe(true); - expect(r3 === 42).toBe(true); - - foo.calls.reset(); - - jasmine.clock().mockDate(new Date()); - throttledFoo = lamb.throttle(foo, 100); - - r1 = throttledFoo(); - r2 = throttledFoo(); - - jasmine.clock().tick(101); - - r3 = throttledFoo(); - - expect(foo.calls.count()).toBe(2); - expect(r1 === 42).toBe(true); - expect(r2 === 42).toBe(true); - expect(r3 === 42).toBe(true); - }); - }); - - describe("unary", function () { - var unaryList; - - beforeEach(function () { - spyOn(lamb, "list").and.callThrough(); - unaryList = lamb.unary(lamb.list); - }); - - afterEach(function () { - lamb.list.calls.reset(); - }); - - it("should build a function that passes only one argument to the given one", function () { - expect(unaryList.length).toBe(1); - expect(unaryList(1, 2, 3)).toEqual([1]); - expect(lamb.list.calls.argsFor(0)).toEqual([1]); - }); - - it("should add an `undefined` argument if the built function doesn't receive parameters", function () { - expect(unaryList()).toEqual([void 0]); - }); - - it("should not modify the function's context", function () { - var fn = function () { - this.values.push(arguments[0]); - }; - var obj = {values: [1, 2, 3], addValue: lamb.unary(fn)}; - - obj.addValue(4); - - expect(obj.values).toEqual([1, 2, 3, 4]); - }); - - it("should build a function throwing an exception if the `fn` parameter isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.unary(value)).toThrow(); - }); - - expect(lamb.unary()).toThrow(); - }); - }); -}); diff --git a/test/spec/groupingSpec.js b/test/spec/groupingSpec.js deleted file mode 100644 index 77e41c8..0000000 --- a/test/spec/groupingSpec.js +++ /dev/null @@ -1,256 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; -var sparseArrayEquality = commons.equalities.sparseArrayEquality; - -var nonFunctions = commons.vars.nonFunctions; -var wannabeEmptyArrays = commons.vars.wannabeEmptyArrays; - -describe("lamb.grouping", function () { - var persons = [ - {name: "Jane", surname: "Doe", age: 12, city: "New York"}, - {name: "John", surname: "Doe", age: 40, city: "New York"}, - {name: "Mario", surname: "Rossi", age: 18, city: "Rome"}, - {name: "Paolo", surname: "Bianchi", age: 15} - ]; - - var personsCityCount = { - "New York": 2, - Rome: 1, - undefined: 1 - }; - - var personsAgeGroupCount = { - under20: 3, - over20: 1 - }; - - var personsByAgeGroup = { - under20: [ - {name: "Jane", surname: "Doe", age: 12, city: "New York"}, - {name: "Mario", surname: "Rossi", age: 18, city: "Rome"}, - {name: "Paolo", surname: "Bianchi", age: 15} - ], - over20: [ - {name: "John", surname: "Doe", age: 40, city: "New York"} - ] - }; - - var personsByCity = { - "New York": [ - {name: "Jane", surname: "Doe", age: 12, city: "New York"}, - {name: "John", surname: "Doe", age: 40, city: "New York"} - ], - Rome: [ - {name: "Mario", surname: "Rossi", age: 18, city: "Rome"} - ], - undefined: [ - {name: "Paolo", surname: "Bianchi", age: 15} - ] - }; - - var personsByAgeIndex = { - 12: {name: "Jane", surname: "Doe", age: 12, city: "New York"}, - 15: {name: "Paolo", surname: "Bianchi", age: 15}, - 18: {name: "Mario", surname: "Rossi", age: 18, city: "Rome"}, - 40: {name: "John", surname: "Doe", age: 40, city: "New York"} - }; - - var personsByCityIndex = { - "New York": {name: "John", surname: "Doe", age: 40, city: "New York"}, - Rome: {name: "Mario", surname: "Rossi", age: 18, city: "Rome"}, - undefined: {name: "Paolo", surname: "Bianchi", age: 15} - }; - - var getCity = lamb.getKey("city"); - - var splitByAgeGroup = function (person, idx, list) { - expect(list).toBe(persons); - expect(persons[idx]).toBe(person); - - return person.age > 20 ? "over20" : "under20"; - }; - - beforeEach(function () { - jasmine.addCustomEqualityTester(sparseArrayEquality); - }); - - describe("count / countBy", function () { - it("should count the occurences of the key generated by the provided iteratee", function () { - expect(lamb.count(persons, getCity)).toEqual(personsCityCount); - expect(lamb.countBy(getCity)(persons)).toEqual(personsCityCount); - expect(lamb.count(persons, splitByAgeGroup)).toEqual(personsAgeGroupCount); - expect(lamb.countBy(splitByAgeGroup)(persons)).toEqual(personsAgeGroupCount); - }); - - it("should work with array-like objects", function () { - var result = { - h: 1, e: 1, l: 3, o: 2, " ": 1, w: 1, r: 1, d: 1 - }; - - expect(lamb.count("hello world", lamb.identity)).toEqual(result); - expect(lamb.countBy(lamb.identity)("hello world")).toEqual(result); - }); - - it("should throw an exception if the iteratee isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.count(persons, value); }).toThrow(); - expect(function () { lamb.countBy(value)(persons); }).toThrow(); - }); - - expect(function () { lamb.count(persons); }).toThrow(); - expect(function () { lamb.countBy()(persons); }).toThrow(); - }); - - it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { - var arr = [1, , 3, void 0, 5]; // eslint-disable-line no-sparse-arrays - var result = {false: 3, true: 2}; - - expect(lamb.count(arr, lamb.isUndefined)).toEqual(result); - expect(lamb.countBy(lamb.isUndefined)(arr)).toEqual(result); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.count).toThrow(); - expect(lamb.countBy(lamb.identity)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.count(null, lamb.identity); }).toThrow(); - expect(function () { lamb.count(void 0, lamb.identity); }).toThrow(); - expect(function () { lamb.countBy(lamb.identity)(null); }).toThrow(); - expect(function () { lamb.countBy(lamb.identity)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array and return an empty object", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.countBy(lamb.identity)(value)).toEqual({}); - expect(lamb.count(value, lamb.identity)).toEqual({}); - }); - }); - }); - - describe("group / groupBy", function () { - it("should build an object using the provided iteratee to group the list with the desired criterion", function () { - expect(lamb.group(persons, splitByAgeGroup)).toEqual(personsByAgeGroup); - expect(lamb.groupBy(splitByAgeGroup)(persons)).toEqual(personsByAgeGroup); - expect(lamb.group(persons, getCity)).toEqual(personsByCity); - expect(lamb.groupBy(getCity)(persons)).toEqual(personsByCity); - }); - - it("should work with array-like objects", function () { - var evenAndOdd = function (n) { return n % 2 === 0 ? "even" : "odd"; }; - var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - var result = {even: [2, 4, 6, 8, 10], odd: [1, 3, 5, 7, 9]}; - var argsTest = function () { - return lamb.group(arguments, evenAndOdd); - }; - - expect(argsTest.apply(null, numbers)).toEqual(result); - }); - - it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { - var arr = [1, , 3, void 0, 5]; // eslint-disable-line no-sparse-arrays - var result = {false: [1, 3, 5], true: [void 0, void 0]}; - - expect(lamb.group(arr, lamb.isUndefined)).toEqual(result); - expect(lamb.groupBy(lamb.isUndefined)(arr)).toEqual(result); - }); - - it("should throw an exception if the iteratee isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.group(persons, value); }).toThrow(); - expect(function () { lamb.groupBy(value)(persons); }).toThrow(); - }); - - expect(function () { lamb.group(persons); }).toThrow(); - expect(function () { lamb.groupBy()(persons); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.group).toThrow(); - expect(lamb.groupBy(lamb.identity)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.group(null, lamb.identity); }).toThrow(); - expect(function () { lamb.group(void 0, lamb.identity); }).toThrow(); - expect(function () { lamb.groupBy(lamb.identity)(null); }).toThrow(); - expect(function () { lamb.groupBy(lamb.identity)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array and return an empty object", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.groupBy(lamb.identity)(value)).toEqual({}); - expect(lamb.group(value, lamb.identity)).toEqual({}); - }); - }); - }); - - describe("index / indexBy", function () { - it("should build a lookup table with keys generated by the iteratee and one value for each key from the original list", function () { - var indexByAge = function (person, idx, list) { - expect(list).toBe(persons); - expect(persons[idx]).toBe(person); - - return person.age; - }; - - expect(lamb.index(persons, indexByAge)).toEqual(personsByAgeIndex); - expect(lamb.indexBy(indexByAge)(persons)).toEqual(personsByAgeIndex); - }); - - it("should use the last evaluated value when the iteratee produces a duplicate key", function () { - expect(lamb.index(persons, getCity)).toEqual(personsByCityIndex); - expect(lamb.indexBy(getCity)(persons)).toEqual(personsByCityIndex); - }); - - it("should work with array-like objects", function () { - var result = { - h: "h", e: "e", l: "l", o: "o", " ": " ", w: "w", r: "r", d: "d" - }; - - expect(lamb.index("hello world", lamb.identity)).toEqual(result); - expect(lamb.indexBy(lamb.identity)("hello world")).toEqual(result); - }); - - it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { - var arr = [1, , 3, void 0, 5]; // eslint-disable-line no-sparse-arrays - var result = {0: 1, 1: void 0, 2: 3, 3: void 0, 4: 5}; - - expect(lamb.index(arr, lamb.getArgAt(1))).toEqual(result); - expect(lamb.indexBy(lamb.getArgAt(1))(arr)).toEqual(result); - }); - - it("should throw an exception if the iteratee isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.index(persons, value); }).toThrow(); - expect(function () { lamb.indexBy(value)(persons); }).toThrow(); - }); - - expect(function () { lamb.index(persons); }).toThrow(); - expect(function () { lamb.indexBy()(persons); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.index).toThrow(); - expect(lamb.indexBy(lamb.identity)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.index(null, lamb.identity); }).toThrow(); - expect(function () { lamb.index(void 0, lamb.identity); }).toThrow(); - expect(function () { lamb.indexBy(lamb.identity)(null); }).toThrow(); - expect(function () { lamb.indexBy(lamb.identity)(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array and return an empty object", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.indexBy(lamb.identity)(value)).toEqual({}); - expect(lamb.index(value, lamb.identity)).toEqual({}); - }); - }); - }); -}); diff --git a/test/spec/logicSpec.js b/test/spec/logicSpec.js deleted file mode 100644 index 640e6c7..0000000 --- a/test/spec/logicSpec.js +++ /dev/null @@ -1,542 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var nonArrayLikes = commons.vars.nonArrayLikes; -var nonFunctions = commons.vars.nonFunctions; -var valuesList = commons.vars.valuesList; - -describe("lamb.logic", function () { - var isEven = function (n) { return n % 2 === 0; }; - var isGreaterThanTwo = lamb.isGT(2); - var isLessThanTen = lamb.isLT(10); - - // to check "truthy" and "falsy" values returned by predicates - var hasEvens = function (array) { return ~lamb.findIndex(array, isEven); }; - var isVowel = function (char) { return ~"aeiouAEIOU".indexOf(char); }; - - function Foo (value) { - this.value = value; - } - - Foo.prototype = { - _safeValue: 99, - value: 0, - getSafeValue: function () { - return this._safeValue; - }, - getValue: function () { - return typeof this.value === "number" ? this.value : void 0; - }, - isEven: function () { - return this.value % 2 === 0; - }, - isPositive: function () { - return this.value > 0; - } - }; - Foo.prototype.getIfPositiveOrGetSafe = lamb.condition( - Foo.prototype.isPositive, - Foo.prototype.getValue, - Foo.prototype.getSafeValue - ); - Foo.prototype.getIfPositiveOrUndefined = lamb.case(Foo.prototype.isPositive, Foo.prototype.getValue); - Foo.prototype.getWhenPositiveOrElse = lamb.when(Foo.prototype.isPositive, Foo.prototype.getValue); - Foo.prototype.getUnlessIsPositiveOrElse = lamb.unless(Foo.prototype.isPositive, Foo.prototype.getValue); - Foo.prototype.isOdd = lamb.not(Foo.prototype.isEven); - Foo.prototype.isPositiveEven = lamb.allOf([Foo.prototype.isEven, Foo.prototype.isPositive]); - Foo.prototype.isPositiveOrEven = lamb.anyOf([Foo.prototype.isEven, Foo.prototype.isPositive]); - - describe("adapter", function () { - it("should accept an array of functions and build another function that calls them one by one until a non-undefined value is returned", function () { - var filterString = lamb.case( - lamb.isType("String"), - lamb.compose(lamb.invoker("join", ""), lamb.filter) - ); - var filterAdapter = lamb.adapter([lamb.invoker("filter"), filterString]); - - expect(filterAdapter([1, 2, 3, 4, 5, 6], isEven)).toEqual([2, 4, 6]); - expect(filterAdapter("123456", isEven)).toBe("246"); - expect(filterAdapter({}, isEven)).toBeUndefined(); - - var filterWithDefault = lamb.adapter([filterAdapter, lamb.always("Not implemented")]); - - expect(filterWithDefault([1, 2, 3, 4, 5, 6], isEven)).toEqual([2, 4, 6]); - expect(filterWithDefault("123456", isEven)).toBe("246"); - expect(filterWithDefault({}, isEven)).toBe("Not implemented"); - }); - - it("should not modify the functions' context", function () { - var obj = {value: 5, getValue: lamb.adapter([Foo.prototype.getValue])}; - - expect(obj.getValue()).toBe(5); - }); - - it("should not throw an exception if some parameter isn't a function if a previous function returned a non-undefined value", function () { - nonFunctions.forEach(function (value) { - expect(lamb.adapter([isEven, value])(2)).toBe(true); - }); - }); - - it("should build a function returning an exception if a parameter isn't a function and no previous function returned a non-unefined value", function () { - var fn = function (v) { return isEven(v) ? true : void 0; }; - - nonFunctions.forEach(function (value) { - expect(function () { lamb.adapter([value, fn])(2); }).toThrow(); - expect(function () { lamb.adapter([fn, value])(3); }).toThrow(); - }); - }); - - it("should return `undefined` if it receives an empty array", function () { - expect(lamb.adapter([])()).toBeUndefined(); - expect(lamb.adapter([])(1, 2, 3)).toBeUndefined(); - }); - - it("should throw an exception if the received parameter isn't an array", function () { - nonArrayLikes.forEach(function (value) { - expect(function () { lamb.adapter(value); }).toThrow(); - }); - - expect(lamb.adapter).toThrow(); - }); - }); - - describe("allOf", function () { - it("should return true if all the given predicates are satisfied", function () { - var check = lamb.allOf([isEven, isGreaterThanTwo, isLessThanTen]); - - expect([4, 6, 8].map(check)).toEqual([true, true, true]); - }); - - it("should return false if one the given predicates isn't satisfied", function () { - var check = lamb.allOf([isEven, isGreaterThanTwo, isLessThanTen]); - - expect([2, 3, 16].map(check)).toEqual([false, false, false]); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.allOf([hasEvens])([1, 3, 5, 7])).toBe(false); - expect(lamb.allOf([hasEvens])([1, 2, 5, 7])).toBe(true); - expect(lamb.allOf([isVowel])("b")).toBe(false); - expect(lamb.allOf([isVowel])("a")).toBe(true); - }); - - it("should keep the predicate context", function () { - expect(new Foo(6).isPositiveEven()).toBe(true); - expect(new Foo(5).isPositiveEven()).toBe(false); - }); - - it("should return `true` for any value if not supplied with predicates because of vacuous truth", function () { - valuesList.forEach(function (value) { - expect(lamb.allOf([])(value)).toBe(true); - }); - }); - - it("should throw an exception if the received parameter isn't an array", function () { - nonArrayLikes.forEach(function (value) { - expect(function () { lamb.allOf(value); }).toThrow(); - }); - - expect(lamb.allOf).toThrow(); - }); - - it("should build a function returning an exception if any given predicate isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.allOf([value, isEven])(2); }).toThrow(); - expect(function () { lamb.allOf([isEven, value])(2); }).toThrow(); - }); - }); - }); - - describe("anyOf", function () { - it("should return true if at least one of the given predicates is satisfied", function () { - var check = lamb.anyOf([isEven, isGreaterThanTwo, isLessThanTen]); - - expect([33, 44, 5].map(check)).toEqual([true, true, true]); - }); - - it("should return false if none of the given predicates is satisfied", function () { - var check = lamb.anyOf([isEven, isLessThanTen]); - - expect([33, 35, 55].map(check)).toEqual([false, false, false]); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.anyOf([hasEvens])([1, 3, 5, 7])).toBe(false); - expect(lamb.anyOf([hasEvens])([1, 2, 5, 7])).toBe(true); - expect(lamb.anyOf([isVowel])("b")).toBe(false); - expect(lamb.anyOf([isVowel])("a")).toBe(true); - }); - - it("should keep the predicate context", function () { - expect(new Foo(5).isPositiveOrEven()).toBe(true); - expect(new Foo(-5).isPositiveOrEven()).toBe(false); - }); - - it("should return `false` for any value if not supplied with predicates", function () { - valuesList.forEach(function (value) { - expect(lamb.anyOf([])(value)).toBe(false); - }); - }); - - it("should throw an exception if the received parameter isn't an array", function () { - nonArrayLikes.forEach(function (value) { - expect(function () { lamb.anyOf(value); }).toThrow(); - }); - - expect(lamb.anyOf).toThrow(); - }); - - it("should not throw an exception if some predicate isn't a function if a previous predicate satisfies the condition", function () { - nonFunctions.forEach(function (value) { - expect(lamb.anyOf([isEven, value])(2)).toBe(true); - }); - }); - - it("should build a function returning an exception if a predicate isn't a function and the condition isn't satisfied yet", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.anyOf([value, isEven])(2); }).toThrow(); - expect(function () { lamb.anyOf([isEven, value])(3); }).toThrow(); - }); - }); - }); - - describe("areSame / is", function () { - it("should verify the equality of two values", function () { - var o = {foo: "bar"}; - - expect(lamb.areSame(o, o)).toBe(true); - expect(lamb.areSame(o, {foo: "bar"})).toBe(false); - expect(lamb.areSame(42, 42)).toBe(true); - expect(lamb.areSame([], [])).toBe(false); - expect(lamb.areSame(0, -0)).toBe(false); - expect(lamb.areSame(NaN, NaN)).toBe(true); - - expect(lamb.is(o)(o)).toBe(true); - expect(lamb.is(o)({foo: "bar"})).toBe(false); - expect(lamb.is(42)(42)).toBe(true); - expect(lamb.is([])([])).toBe(false); - expect(lamb.is(0)(-0)).toBe(false); - expect(lamb.is(NaN)(NaN)).toBe(true); - }); - }); - - describe("areSVZ / isSVZ", function () { - it("should verify the equality of two values using the \"SameValueZero\" comparison", function () { - var o = {foo: "bar"}; - - expect(lamb.areSVZ(o, o)).toBe(true); - expect(lamb.areSVZ(o, {foo: "bar"})).toBe(false); - expect(lamb.areSVZ(42, 42)).toBe(true); - expect(lamb.areSVZ([], [])).toBe(false); - expect(lamb.areSVZ(0, -0)).toBe(true); - expect(lamb.areSVZ(NaN, NaN)).toBe(true); - - expect(lamb.isSVZ(o)(o)).toBe(true); - expect(lamb.isSVZ(o)({foo: "bar"})).toBe(false); - expect(lamb.isSVZ(42)(42)).toBe(true); - expect(lamb.isSVZ([])([])).toBe(false); - expect(lamb.isSVZ(0)(-0)).toBe(true); - expect(lamb.isSVZ(NaN)(NaN)).toBe(true); - }); - }); - - describe("case / condition", function () { - var halve = lamb.divideBy(2); - var isGreaterThan5 = lamb.isGT(5); - var halveIfGreaterThan5 = lamb.condition(isGreaterThan5, halve, lamb.identity); - - describe("case", function () { - it("should call the received function if the predicate is satisfied by the same arguments", function () { - expect(lamb.case(isGreaterThan5, halve)(10)).toBe(5); - }); - - it("should build a function that returns `undefined` if the predicate isn't satisfied", function () { - expect(lamb.case(isGreaterThan5, halve)(3)).toBeUndefined(); - }); - }); - - describe("condition", function () { - it("should build a function that conditionally executes the received functions evaluating a predicate", function () { - expect(halveIfGreaterThan5(3)).toBe(3); - expect(halveIfGreaterThan5(10)).toBe(5); - }); - - it("should build a function throwing an exception if `falseFn` isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.condition(lamb.always(false), lamb.always(99), value)).toThrow(); - }); - - expect(lamb.condition(lamb.always(false), lamb.always(99))).toThrow(); - }); - }); - - it("should pass all the received arguments to both the predicate and the chosen branching function", function () { - var satisfiedPredicate = jasmine.createSpy().and.returnValue(true); - var notSatisfiedPredicate = jasmine.createSpy().and.returnValue(false); - - var condA = lamb.condition(satisfiedPredicate, lamb.list, lamb.always([])); - var condB = lamb.condition(notSatisfiedPredicate, lamb.always([]), lamb.list); - var caseA = lamb.case(satisfiedPredicate, lamb.list); - var caseB = lamb.case(notSatisfiedPredicate, lamb.always([])); - - expect(condA(1, 2, 3, 4)).toEqual([1, 2, 3, 4]); - expect(condB(5, 6, 7)).toEqual([5, 6, 7]); - expect(caseA(8, 9, 10)).toEqual([8, 9, 10]); - expect(caseB(11, 12)).toBeUndefined(); - - expect(satisfiedPredicate.calls.count()).toBe(2); - expect(satisfiedPredicate.calls.argsFor(0)).toEqual([1, 2, 3, 4]); - expect(satisfiedPredicate.calls.argsFor(1)).toEqual([8, 9, 10]); - - expect(notSatisfiedPredicate.calls.count()).toBe(2); - expect(notSatisfiedPredicate.calls.argsFor(0)).toEqual([5, 6, 7]); - expect(notSatisfiedPredicate.calls.argsFor(1)).toEqual([11, 12]); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - var condA = lamb.condition(hasEvens, lamb.always("yes"), lamb.always("no")); - var condB = lamb.condition(isVowel, lamb.always("yes"), lamb.always("no")); - var caseA = lamb.case(hasEvens, lamb.always("yes")); - var caseB = lamb.case(isVowel, lamb.always("yes")); - - expect(condA([1, 2, 5, 7])).toBe("yes"); - expect(condA([1, 3, 5, 7])).toBe("no"); - expect(condB("a")).toBe("yes"); - expect(condB("b")).toBe("no"); - - expect(caseA([1, 2, 5, 7])).toBe("yes"); - expect(caseA([1, 3, 5, 7])).toBeUndefined(); - expect(caseB("a")).toBe("yes"); - expect(caseB("b")).toBeUndefined(); - }); - - it("should keep the functions' context", function () { - expect(new Foo(55).getIfPositiveOrGetSafe()).toBe(55); - expect(new Foo(-55).getIfPositiveOrGetSafe()).toBe(99); - - expect(new Foo(55).getIfPositiveOrUndefined()).toBe(55); - expect(new Foo(-55).getIfPositiveOrUndefined()).toBeUndefined(); - }); - - it("should build a function throwing an exception if the predicate isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.condition(value, lamb.always(99))).toThrow(); - expect(lamb.case(value, lamb.always(99))).toThrow(); - }); - }); - - it("should build a function throwing an exception if `trueFn` isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.condition(lamb.always(true), value, lamb.always(99))).toThrow(); - expect(lamb.case(lamb.always(true), value)).toThrow(); - }); - - expect(lamb.condition(lamb.always(true))).toThrow(); - expect(lamb.case(lamb.always(true))).toThrow(); - }); - - it("should build a function throwing an exception if called without arguments", function () { - expect(lamb.condition()).toThrow(); - expect(lamb.case()).toThrow(); - }); - }); - - describe("comparison operators", function () { - var d1 = new Date(2010, 11, 2); - var d2 = new Date(2010, 11, 3); - var d3 = new Date(2010, 11, 2); - - describe("gt / isGT", function () { - it("should verify if the first argument is greater than the second", function () { - expect(lamb.gt(2, 1)).toBe(true); - expect(lamb.gt(1, 2)).toBe(false); - expect(lamb.gt(2, 2)).toBe(false); - expect(lamb.gt(true, false)).toBe(true); - expect(lamb.gt(d2, d1)).toBe(true); - expect(lamb.gt("a", "A")).toBe(true); - expect(lamb.gt("", "a")).toBe(false); - - expect(lamb.isGT(1)(2)).toBe(true); - expect(lamb.isGT(2)(1)).toBe(false); - expect(lamb.isGT(2)(2)).toBe(false); - expect(lamb.isGT(false)(true)).toBe(true); - expect(lamb.isGT(d1)(d2)).toBe(true); - expect(lamb.isGT("A")("a")).toBe(true); - expect(lamb.isGT("a")("")).toBe(false); - }); - }); - - describe("gte / isGTE", function () { - it("should verify if the first argument is greater than or equal to the second", function () { - expect(lamb.gte(2, 1)).toBe(true); - expect(lamb.gte(1, 2)).toBe(false); - expect(lamb.gte(2, 2)).toBe(true); - expect(lamb.gte(true, false)).toBe(true); - expect(lamb.gte(d2, d1)).toBe(true); - expect(lamb.gte(d1, d3)).toBe(true); - expect(lamb.gte("a", "A")).toBe(true); - expect(lamb.gte("", "a")).toBe(false); - - expect(lamb.isGTE(1)(2)).toBe(true); - expect(lamb.isGTE(2)(1)).toBe(false); - expect(lamb.isGTE(2)(2)).toBe(true); - expect(lamb.isGTE(false)(true)).toBe(true); - expect(lamb.isGTE(d1)(d2)).toBe(true); - expect(lamb.isGTE(d3)(d1)).toBe(true); - expect(lamb.isGTE("A")("a")).toBe(true); - expect(lamb.isGTE("a")("")).toBe(false); - }); - }); - - describe("lt / isLT", function () { - it("should verify if the first argument is less than the second", function () { - expect(lamb.lt(2, 1)).toBe(false); - expect(lamb.lt(1, 2)).toBe(true); - expect(lamb.lt(2, 2)).toBe(false); - expect(lamb.lt(true, false)).toBe(false); - expect(lamb.lt(d2, d1)).toBe(false); - expect(lamb.lt("a", "A")).toBe(false); - expect(lamb.lt("", "a")).toBe(true); - - expect(lamb.isLT(1)(2)).toBe(false); - expect(lamb.isLT(2)(1)).toBe(true); - expect(lamb.isLT(2)(2)).toBe(false); - expect(lamb.isLT(false)(true)).toBe(false); - expect(lamb.isLT(d1)(d2)).toBe(false); - expect(lamb.isLT("A")("a")).toBe(false); - expect(lamb.isLT("a")("")).toBe(true); - }); - }); - - describe("lte / isLTE", function () { - it("should verify if the first argument is less than or equal to the second", function () { - expect(lamb.lte(2, 1)).toBe(false); - expect(lamb.lte(1, 2)).toBe(true); - expect(lamb.lte(2, 2)).toBe(true); - expect(lamb.lte(true, false)).toBe(false); - expect(lamb.lte(d2, d1)).toBe(false); - expect(lamb.lte(d1, d3)).toBe(true); - expect(lamb.lte("a", "A")).toBe(false); - expect(lamb.lte("", "a")).toBe(true); - - expect(lamb.isLTE(1)(2)).toBe(false); - expect(lamb.isLTE(2)(1)).toBe(true); - expect(lamb.isLTE(2)(2)).toBe(true); - expect(lamb.isLTE(false)(true)).toBe(false); - expect(lamb.isLTE(d1)(d2)).toBe(false); - expect(lamb.isLTE(d3)(d1)).toBe(true); - expect(lamb.isLTE("A")("a")).toBe(false); - expect(lamb.isLTE("a")("")).toBe(true); - }); - }); - }); - - describe("not", function () { - it("should reverse the truthiness of the given predicate", function () { - var isOdd = lamb.not(isEven); - - expect(isOdd(3)).toBe(true); - }); - - it("should keep the predicate context", function () { - expect(new Foo(4).isOdd()).toBe(false); - expect(new Foo(5).isOdd()).toBe(true); - }); - - it("should build a function returning an exception if the given predicate is missing or isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(lamb.not(value)).toThrow(); - }); - - expect(lamb.not()).toThrow(); - }); - }); - - describe("unless / when", function () { - var increment = lamb.add(1); - var incArray = lamb.mapWith(increment); - var a1 = [1, 3, 5]; - var a2 = [1, 4, 5]; - - it("should build a function that conditionally applies its `fn` parameter to the received value depending on the evaluation of a predicate", function () { - expect(lamb.unless(isEven, increment)(5)).toBe(6); - expect(lamb.when(isEven, increment)(4)).toBe(5); - }); - - it("should build a function returning the received value when the desired condition is not met", function () { - expect(lamb.unless(isEven, increment)(6)).toBe(6); - expect(lamb.when(isEven, increment)(5)).toBe(5); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(lamb.unless(hasEvens, incArray)(a1)).toEqual([2, 4, 6]); - expect(lamb.unless(hasEvens, incArray)(a2)).toBe(a2); - expect(lamb.when(hasEvens, incArray)(a1)).toBe(a1); - expect(lamb.when(hasEvens, incArray)(a2)).toEqual([2, 5, 6]); - }); - - it("should build a unary function and ignore extra arguments", function () { - var incSpy = jasmine.createSpy().and.callFake(increment); - var isEvenSpy = jasmine.createSpy().and.callFake(isEven); - var incUnlessEven = lamb.unless(isEvenSpy, incSpy); - var incWhenEven = lamb.when(isEvenSpy, incSpy); - - expect(incUnlessEven.length).toBe(1); - expect(incUnlessEven(5, 6, 7)).toBe(6); - expect(incUnlessEven(6, 7, 8)).toBe(6); - - expect(incWhenEven.length).toBe(1); - expect(incWhenEven(4, 5)).toBe(5); - expect(incWhenEven(5, 6)).toBe(5); - - expect(isEvenSpy.calls.count()).toBe(4); - expect(isEvenSpy.calls.argsFor(0)).toEqual([5]); - expect(isEvenSpy.calls.argsFor(1)).toEqual([6]); - expect(isEvenSpy.calls.argsFor(2)).toEqual([4]); - expect(isEvenSpy.calls.argsFor(3)).toEqual([5]); - - expect(incSpy.calls.count()).toBe(2); - expect(incSpy.calls.argsFor(0)).toEqual([5]); - expect(incSpy.calls.argsFor(1)).toEqual([4]); - }); - - it("should keep the functions' context", function () { - var positiveFoo = new Foo(33); - var negativeFoo = new Foo(-33); - - expect(positiveFoo.getWhenPositiveOrElse(88)).toBe(33); - expect(negativeFoo.getWhenPositiveOrElse(88)).toBe(88); - expect(positiveFoo.getUnlessIsPositiveOrElse(-88)).toBe(-88); - expect(negativeFoo.getUnlessIsPositiveOrElse(-88)).toBe(-33); - }); - - it("should build a function throwing an exception if the predicate isn't a function", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.unless(value, increment)(5); }).toThrow(); - expect(function () { lamb.when(value, increment)(2); }).toThrow(); - }); - }); - - it("should not throw an exception if the transformer isn't a function and the conditions aren't met", function () { - nonFunctions.forEach(function (value) { - expect(lamb.unless(isEven, value)(2)).toBe(2); - expect(lamb.when(isEven, value)(5)).toBe(5); - }); - }); - - it("should build a function throwing an exception if the transformer isn't a function and the conditions are met", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.unless(isEven, value)(5); }).toThrow(); - expect(function () { lamb.when(isEven, value)(2); }).toThrow(); - }); - }); - - it("should build a function throwing an exception if called without arguments", function () { - expect(lamb.unless()).toThrow(); - expect(lamb.when()).toThrow(); - }); - }); -}); diff --git a/test/spec/mathSpec.js b/test/spec/mathSpec.js deleted file mode 100644 index 49b4e20..0000000 --- a/test/spec/mathSpec.js +++ /dev/null @@ -1,315 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var nonNumbers = commons.vars.nonNumbers; -var wannabeNaNs = commons.vars.wannabeNaNs; -var zeroesAsNumbers = commons.vars.zeroesAsNumbers; - -describe("lamb.math", function () { - describe("add / sum", function () { - it("should add two numbers", function () { - expect(lamb.sum(2, -3)).toBe(-1); - expect(lamb.add(2)(-3)).toBe(-1); - }); - }); - - describe("clamp / clampWithin", function () { - it("should clamp a number within the given limits", function () { - expect(lamb.clamp(0, -5, 5)).toBe(0); - expect(lamb.clamp(-5, -5, 5)).toBe(-5); - expect(lamb.clamp(5, -5, 5)).toBe(5); - expect(lamb.clamp(-6, -5, 5)).toBe(-5); - expect(lamb.clamp(6, -5, 5)).toBe(5); - expect(lamb.clamp(5, 5, 5)).toBe(5); - expect(Object.is(lamb.clamp(-0, 0, 0), -0)).toBe(true); - expect(Object.is(lamb.clamp(0, -0, 0), 0)).toBe(true); - expect(Object.is(lamb.clamp(0, 0, -0), 0)).toBe(true); - - expect(lamb.clampWithin(-5, 5)(0)).toBe(0); - expect(lamb.clampWithin(-5, 5)(-5)).toBe(-5); - expect(lamb.clampWithin(-5, 5)(5)).toBe(5); - expect(lamb.clampWithin(-5, 5)(-6)).toBe(-5); - expect(lamb.clampWithin(-5, 5)(6)).toBe(5); - expect(lamb.clampWithin(5, 5)(5)).toBe(5); - expect(Object.is(lamb.clampWithin(0, 0)(-0), -0)).toBe(true); - expect(Object.is(lamb.clampWithin(-0, 0)(0), 0)).toBe(true); - expect(Object.is(lamb.clampWithin(0, -0)(0), 0)).toBe(true); - }); - - it("should return `NaN` if `min` is greater than `max`", function () { - expect(lamb.clamp(5, 10, 7)).toEqual(NaN); - expect(lamb.clampWithin(10, 7)(5)).toEqual(NaN); - }); - - it("should convert all expected arguments to `Number`, received or not", function () { - var d1 = new Date(2015, 1, 1); - var d2 = new Date(2016, 1, 1); - var d3 = new Date(2017, 1, 1); - - expect(lamb.clamp(d1, d2, d3)).toBe(+d2); - expect(lamb.clamp([97], [98], [99])).toBe(98); - expect(lamb.clamp("5", "3", "4")).toBe(4); - expect(lamb.clamp("5", "3", "4")).toBe(4); - expect(lamb.clamp(true, false, true)).toBe(1); - expect(lamb.clamp(null, -1, 1)).toBe(0); - expect(lamb.clamp(-1, null, 2)).toBe(0); - expect(lamb.clamp(5, -1, null)).toBe(0); - - expect(lamb.clampWithin(d2, d3)(d1)).toBe(+d2); - expect(lamb.clampWithin([98], [99])([97])).toBe(98); - expect(lamb.clampWithin(false, true)(true)).toBe(1); - expect(lamb.clampWithin(-1, 1)(null)).toBe(0); - expect(lamb.clampWithin(null, 2)(-1)).toBe(0); - expect(lamb.clampWithin(-1, null)(5)).toBe(0); - - wannabeNaNs.forEach(function (value) { - expect(lamb.clamp(value, 99, 100)).toEqual(NaN); - expect(lamb.clamp(99, value, 100)).toBe(99); - expect(lamb.clamp(99, value, 98)).toBe(98); - expect(lamb.clamp(99, 98, value)).toBe(99); - expect(lamb.clamp(99, 100, value)).toBe(100); - - expect(lamb.clampWithin(99, 100)(value)).toEqual(NaN); - expect(lamb.clampWithin(value, 100)(99)).toBe(99); - expect(lamb.clampWithin(value, 98)(99)).toBe(98); - expect(lamb.clampWithin(98, value)(99)).toBe(99); - expect(lamb.clampWithin(100, value)(99)).toBe(100); - }); - - expect(lamb.clamp(5, 10)).toBe(10); - expect(lamb.clamp(-5, void 0, 10)).toBe(-5); - expect(lamb.clamp()).toEqual(NaN); - - expect(lamb.clampWithin(10)(5)).toBe(10); - expect(lamb.clampWithin(void 0, 10)(-5)).toBe(-5); - expect(lamb.clampWithin()()).toEqual(NaN); - }); - }); - - describe("divide / divideBy", function () { - it("should divide two numbers", function () { - var divideBy3 = lamb.divideBy(3); - - expect(lamb.divide(15, 3)).toBe(5); - expect(lamb.divide(15, 0)).toBe(Infinity); - expect(divideBy3(15)).toBe(5); - expect(divideBy3(16)).toBe(5.333333333333333); - }); - }); - - describe("generate", function () { - it("should generate a sequence of values of the desired length with the provided iteratee", function () { - var fibonacci = function (n, idx, list) { - return n + (list[idx - 1] || 0); - }; - - expect(lamb.generate(1, 20, fibonacci)).toEqual( - [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] - ); - }); - }); - - describe("isFinite", function () { - it("should verify whether the received value is a finite number", function () { - expect(lamb.isFinite(0)).toBe(true); - expect(lamb.isFinite(-2e64)).toBe(true); - expect(lamb.isFinite(new Number(5))).toBe(true); - expect(lamb.isFinite(Infinity)).toBe(false); - expect(lamb.isFinite(-Infinity)).toBe(false); - expect(lamb.isFinite(NaN)).toBe(false); - }); - - it("should return `false` for any non-number value and when it's called without arguments", function () { - nonNumbers.forEach(function (value) { - expect(lamb.isFinite(value)).toBe(false); - }); - - expect(lamb.isFinite()).toBe(false); - }); - }); - - describe("isInteger", function () { - it("should verify whether the received value is an integer", function () { - expect(lamb.isInteger(0)).toBe(true); - expect(lamb.isInteger(-2e64)).toBe(true); - expect(lamb.isInteger(new Number(5))).toBe(true); - expect(lamb.isInteger(2.4)).toBe(false); - expect(lamb.isInteger(Infinity)).toBe(false); - expect(lamb.isInteger(-Infinity)).toBe(false); - expect(lamb.isInteger(NaN)).toBe(false); - }); - - it("should return `false` for any non-number value and when it's called without arguments", function () { - nonNumbers.forEach(function (value) { - expect(lamb.isInteger(value)).toBe(false); - }); - - expect(lamb.isInteger()).toBe(false); - }); - }); - - describe("isSafeInteger", function () { - it("shoud verify whether the received value is a \"safe integer\"", function () { - expect(lamb.isSafeInteger(0)).toBe(true); - expect(lamb.isSafeInteger(-2e10)).toBe(true); - expect(lamb.isSafeInteger(new Number(5))).toBe(true); - expect(lamb.isSafeInteger(Math.pow(2, 53) - 1)).toBe(true); - expect(lamb.isSafeInteger(2.4)).toBe(false); - expect(lamb.isSafeInteger(Math.pow(2, 53))).toBe(false); - expect(lamb.isSafeInteger(-2e64)).toBe(false); - expect(lamb.isSafeInteger(Infinity)).toBe(false); - expect(lamb.isSafeInteger(-Infinity)).toBe(false); - expect(lamb.isSafeInteger(NaN)).toBe(false); - }); - - it("should return `false` for any non-number value and when it's called without arguments", function () { - nonNumbers.forEach(function (value) { - expect(lamb.isSafeInteger(value)).toBe(false); - }); - - expect(lamb.isSafeInteger()).toBe(false); - }); - }); - - describe("modulo", function () { - it("should calculate the modulo of two numbers", function () { - expect(lamb.modulo(5, 3)).toBe(2); - expect(lamb.modulo(-5, 3)).toBe(1); - expect(isNaN(lamb.modulo(-5, 0))).toBe(true); - }); - }); - - describe("multiply / multiplyBy", function () { - it("should multiply two numbers", function () { - expect(lamb.multiply(5, -3)).toBe(-15); - expect(lamb.multiplyBy(5)(-3)).toBe(-15); - }); - }); - - describe("randomInt", function () { - var rndSpy; - - beforeEach(function () { - rndSpy = spyOn(Math, "random").and.callThrough(); - }); - - it("should generate a random integer between the min and max provided values", function () { - var n = lamb.randomInt(3, 42); - - expect(Math.floor(n)).toBe(n); - expect(n).toBeGreaterThan(2); - expect(n).toBeLessThan(43); - - rndSpy.and.returnValue(0.9999999999999999); - - expect(lamb.randomInt(0, 0)).toBe(0); - expect(lamb.randomInt(0, 1)).toBe(1); - expect(lamb.randomInt(0, 2)).toBe(2); - - rndSpy.and.returnValue(0); - - expect(lamb.randomInt(0, 0)).toBe(0); - expect(lamb.randomInt(0, 1)).toBe(0); - expect(lamb.randomInt(0, 2)).toBe(0); - }); - }); - - describe("range", function () { - it("should generate an arithmetic progression of integers with the given parameters", function () { - expect(lamb.range(3, 15, 2)).toEqual([3, 5, 7, 9, 11, 13]); - expect(lamb.range(1, -10, -2)).toEqual([1, -1, -3, -5, -7, -9]); - }); - - it("should return an empty array if `start` isn't less than `limit` with a positive `step`", function () { - expect(lamb.range(5, 3, 1)).toEqual([]); - expect(lamb.range(1, -10, 2)).toEqual([]); - }); - - it("should return an empty array if `start` isn't greater than `limit` with a negative `step`", function () { - expect(lamb.range(3, 5, -1)).toEqual([]); - expect(lamb.range(-10, -1, -2)).toEqual([]); - }); - - it("should return an empty array if `step` is zero and `limit` equals `start`", function () { - expect(lamb.range(2, 2, 0)).toEqual([]); - }); - - it("should return an array containing `start` if `step` is zero and `limit` isn't equal to `start`", function () { - expect(lamb.range(2, 10, 0)).toEqual([2]); - }); - - it("should keep the sign of zeroes received as `start`", function () { - expect(lamb.range(0, 2, 1)).toEqual([0, 1]); - expect(lamb.range(-0, 2, 1)).toEqual([-0, 1]); - }); - - it("should convert the `start` parameter to number", function () { - zeroesAsNumbers.forEach(function (value) { - expect(lamb.range(value, 3)).toEqual([0, 1, 2]); - }); - - [[1], true, "1"].forEach(function (value) { - expect(lamb.range(value, 3)).toEqual([1, 2]); - }); - - [[1.5], "1.5"].forEach(function (value) { - expect(lamb.range(value, 3)).toEqual([1.5, 2.5]); - }); - }); - - it("should convert the `limit` parameter to number", function () { - zeroesAsNumbers.forEach(function (value) { - expect(lamb.range(-3, value)).toEqual([-3, -2, -1]); - }); - - [[1], true, "1"].forEach(function (value) { - expect(lamb.range(-1, value)).toEqual([-1, 0]); - }); - - [[1.5], "1.5"].forEach(function (value) { - expect(lamb.range(0, value, .5)).toEqual([0, 0.5, 1]); - }); - }); - - it("should use one as the default value of `step` if the parameter is missing", function () { - expect(lamb.range(2, 10)).toEqual([2, 3, 4, 5, 6, 7, 8, 9]); - }); - - it("should convert the `step` parameter to number", function () { - zeroesAsNumbers.forEach(function (value) { - expect(lamb.range(-3, 3, value)).toEqual([-3]); - expect(lamb.range(3, 3, value)).toEqual([]); - }); - - [[1], true, "1"].forEach(function (value) { - expect(lamb.range(-1, 1, value)).toEqual([-1, 0]); - }); - - [[.5], ".5"].forEach(function (value) { - expect(lamb.range(0, 1.5, value)).toEqual([0, 0.5, 1]); - }); - }); - - it("should return an empty array if called without parameters", function () { - expect(lamb.range()).toEqual([]); - }); - }); - - describe("remainder", function () { - it("should calculate the remainder of the division of two numbers", function () { - expect(lamb.remainder(5, 3)).toBe(2); - expect(lamb.remainder(-5, 3)).toBe(-2); - expect(isNaN(lamb.remainder(-5, 0))).toBe(true); - }); - }); - - describe("subtract / deduct", function () { - it("should subtract two numbers", function () { - expect(lamb.subtract(5, 7)).toBe(-2); - expect(lamb.deduct(7)(5)).toBe(-2); - }); - }); -}); diff --git a/test/spec/objectSpec.js b/test/spec/objectSpec.js deleted file mode 100644 index 043d849..0000000 --- a/test/spec/objectSpec.js +++ /dev/null @@ -1,1156 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var nonStringsAsStrings = commons.vars.nonStringsAsStrings; -var nonFunctions = commons.vars.nonFunctions; -var wannabeEmptyObjects = commons.vars.wannabeEmptyObjects; -var wannabeEmptyArrays = commons.vars.wannabeEmptyArrays; - -describe("lamb.object", function () { - describe("enumerables", function () { - it("should build an array with all the enumerables keys of an object", function () { - var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - var foo = Object.create(baseFoo, { - c: {value: 3}, - d: {value: 4, enumerable: true} - }); - - expect(lamb.enumerables(foo)).toEqual(["d", "a"]); - }); - - it("should work with arrays and array-like objects", function () { - expect(lamb.enumerables([1, 2, 3])).toEqual(["0", "1", "2"]); - expect(lamb.enumerables("abc")).toEqual(["0", "1", "2"]); - }); - - it("should retrieve only defined keys in sparse arrays", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - expect(lamb.enumerables([, 5, 6, ,])).toEqual(["1", "2"]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.enumerables).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.enumerables(null); }).toThrow(); - expect(function () { lamb.enumerables(void 0); }).toThrow(); - }); - - it("should consider other values as empty objects", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.enumerables(value)).toEqual([]); - }); - }); - }); - - describe("fromPairs", function () { - it("should build an object from a list of key / value pairs", function () { - expect(lamb.fromPairs([["a", 1], ["b", 2], ["c", 3]])).toEqual({a: 1, b: 2, c: 3}); - }); - - it("should use the last key / value pair in case of duplicate keys", function () { - expect(lamb.fromPairs([["a", 1], ["b", 2], ["a", 3]])).toEqual({a: 3, b: 2}); - }); - - it("should convert missing or non-string keys to strings and missing values to `undefined`", function () { - /* eslint-disable comma-spacing, no-sparse-arrays */ - var pairs = [[1], [void 0, 2], [null, 3], ["z", ,]]; - var result = {1: void 0, undefined: 2, null: 3, z: void 0}; - - expect(lamb.fromPairs(pairs)).toEqual(result); - expect(lamb.fromPairs([[, 4]])).toEqual({undefined: 4}); - /* eslint-enable comma-spacing, no-sparse-arrays */ - }); - - it("should return an empty object if supplied with an empty array", function () { - expect(lamb.fromPairs([])).toEqual({}); - }); - - it("should accept array-like objects as pairs", function () { - expect(lamb.fromPairs(["a1", "b2"])).toEqual({a: "1", b: "2"}); - }); - - it("should try to retrieve pairs from array-like objects", function () { - expect(lamb.fromPairs("foo")).toEqual({f: void 0, o: void 0}); - }); - - it("should throw an exception if any of the pairs is `nil`", function () { - expect(function () { lamb.fromPairs([["a", 1], null, ["c", 3]]); }).toThrow(); - expect(function () { lamb.fromPairs([["a", 1], void 0, ["c", 3]]); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.fromPairs).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.fromPairs(null); }).toThrow(); - expect(function () { lamb.fromPairs(void 0); }).toThrow(); - }); - - it("should treat every other value as an empty array and return an empty object", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.fromPairs(value)).toEqual({}); - }); - }); - }); - - describe("immutable", function () { - var persons; - var newPerson; - - beforeEach(function () { - persons = [ - {name: "Jane", surname: "Doe", age: 12, city: "New York"}, - {name: "John", surname: "Doe", age: 40, city: "London"}, - {name: "Mario", surname: "Rossi", age: 18, city: "Rome"} - ]; - - newPerson = { - name: "Paolo", - surname: "Bianchi", - age: null, - city: "Amsterdam", - contact: { - mail: "paolo@bianchi.it", - phone: "+39123456789" - }, - luckyNumbers: [13, 17] - }; - }); - - it("should make an object immutable", function () { - var immutableNewPerson = lamb.immutable(newPerson); - - expect(immutableNewPerson).toBe(newPerson); - expect(function () { - newPerson.name = "Foo"; - }).toThrow(); - expect(function () { - newPerson.luckyNumbers.splice(0, 1); - }).toThrow(); - expect(Array.isArray(newPerson.luckyNumbers)).toBe(true); - expect(newPerson.name).toBe("Paolo"); - - var immutablePersons = lamb.immutable(persons); - - expect(immutablePersons).toBe(persons); - expect(Array.isArray(immutablePersons)).toBe(true); - expect(function () { - persons.push(newPerson); - }).toThrow(); - expect(persons.length).toBe(3); - - expect(function () { - persons[0].age = 50; - }).toThrow(); - expect(persons[0].age).toBe(12); - }); - - it("should handle circular references", function () { - var foo = { - bar: 2, - baz: persons - }; - - persons.push(foo); - - lamb.immutable(persons); - - expect(Object.isFrozen(persons[3])).toBe(true); - expect(persons[3].baz).toBe(persons); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.immutable).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.immutable(null); }).toThrow(); - expect(function () { lamb.immutable(void 0); }).toThrow(); - }); - - it("should not throw an exception when a `nil` value is in a nested property", function () { - var o = { - a: null, - b: void 0, - c: {d: null, e: void 0} - }; - - expect(function () { lamb.immutable(o); }).not.toThrow(); - }); - }); - - describe("keys", function () { - it("should build an array with all the enumerables own keys of an object", function () { - var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - var foo = Object.create(baseFoo, { - c: {value: 3}, - d: {value: 4, enumerable: true} - }); - - expect(lamb.keys(foo)).toEqual(["d"]); - }); - - it("should work with arrays and array-like objects", function () { - expect(lamb.keys([1, 2, 3])).toEqual(["0", "1", "2"]); - expect(lamb.keys("abc")).toEqual(["0", "1", "2"]); - }); - - it("should retrieve only defined keys in sparse arrays", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - expect(lamb.keys([, 5, 6, ,])).toEqual(["1", "2"]); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.keys).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.keys(null); }).toThrow(); - expect(function () { lamb.keys(void 0); }).toThrow(); - }); - - it("should consider other values as empty objects", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.keys(value)).toEqual([]); - }); - }); - }); - - describe("make", function () { - it("should build an object with the given keys and values lists", function () { - expect(lamb.make(["a", "b", "c"], [1, 2, 3])).toEqual({a: 1, b: 2, c: 3}); - }); - - it("should create undefined values if the keys list is longer", function () { - expect(lamb.make(["a", "b", "c"], [1, 2])).toEqual({a: 1, b: 2, c: void 0}); - }); - - it("should ignore extra values if the keys list is shorter", function () { - expect(lamb.make(["a", "b"], [1, 2, 3])).toEqual({a: 1, b: 2}); - }); - - it("should convert non-string keys to strings", function () { - expect(lamb.make([null, void 0, 2], [1, 2, 3])).toEqual({null: 1, undefined: 2, 2: 3}); - }); - - it("should convert unassigned or deleted indexes in sparse arrays to `undefined` values", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.make(["a", "b", "c"], [1, , 3])).toEqual({a: 1, b: void 0, c: 3}); - expect(lamb.make(["a", , "c"], [1, 2, 3])).toEqual({a: 1, undefined: 2, c: 3}); - /* eslint-enable no-sparse-arrays */ - }); - - it("should accept array-like objects in both parameters", function () { - expect(lamb.make("abcd", "1234")).toEqual({a: "1", b: "2", c: "3", d: "4"}); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.make).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` in the `keys` or in the `values` parameter", function () { - expect(function () { lamb.make(null, []); }).toThrow(); - expect(function () { lamb.make(void 0, []); }).toThrow(); - expect(function () { lamb.make([], null); }).toThrow(); - expect(function () { lamb.make([], void 0); }).toThrow(); - }); - - it("should consider other values for the `keys` parameter as empty arrays and return an empty object", function () { - wannabeEmptyArrays.forEach(function (v) { - expect(lamb.make(v, [])).toEqual({}); - }); - }); - - it("should consider other values for the `values` parameter to be empty arrays", function () { - wannabeEmptyArrays.forEach(function (v) { - expect(lamb.make(["foo", "bar"], v)).toEqual({foo: void 0, bar: void 0}); - }); - }); - }); - - describe("mapValues / mapValuesWith", function () { - var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - var foo = Object.create(baseFoo, {c: {value: 3, enumerable: true}}); - - var fooEquivalent = {a: 1, b: 2, c: 3, z: 5}; - - var inc = lamb.add(1); - - // The "toEqual" matcher would have checked only own enumerable properties - afterEach(function () { - for (var key in fooEquivalent) { - expect(fooEquivalent[key]).toBe(foo[key]); - } - }); - - it("should create a new object by applying the received function to the values of the source one", function () { - var times3 = lamb.multiplyBy(3); - var r = {a: 3, b: 6, c: 9}; - - expect(lamb.mapValues(foo, times3)).toEqual(r); - expect(lamb.mapValuesWith(times3)(foo)).toEqual(r); - }); - - it("should use all the enumerable properties of the source object, inherited or not", function () { - var r = {a: 2, b: 3, c: 4}; - - expect(lamb.mapValues(foo, inc)).toEqual(r); - expect(lamb.mapValuesWith(inc)(foo)).toEqual(r); - }); - - it("should pass the key value, its name and the object being traversed to the mapping function", function () { - var deductFiveSpy = jasmine.createSpy().and.callFake(function (v, k, o) { - expect(o).toBe(foo); - expect(o[k]).toBe(v); - - return v - 5; - }); - var r = {a: -4, b: -3, c: -2}; - - expect(lamb.mapValues(foo, deductFiveSpy)).toEqual(r); - expect(lamb.mapValuesWith(deductFiveSpy)(foo)).toEqual(r); - }); - - it("should work with array-like objects", function () { - var a = [97, 97, 98]; - var s = "abc"; - var r1 = {0: 98, 1: 98, 2: 99}; - var r2 = {0: "A", 1: "B", 2: "C"}; - - var toUpperCase = lamb.invoker("toUpperCase"); - - expect(lamb.mapValues(a, inc)).toEqual(r1); - expect(lamb.mapValuesWith(inc)(a)).toEqual(r1); - - expect(lamb.mapValues(s, toUpperCase)).toEqual(r2); - expect(lamb.mapValuesWith(toUpperCase)(s)).toEqual(r2); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.mapValues).toThrow(); - expect(lamb.mapValuesWith()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.mapValues(null, inc); }).toThrow(); - expect(function () { lamb.mapValues(void 0, inc); }).toThrow(); - expect(function () { lamb.mapValuesWith(inc)(null); }).toThrow(); - expect(function () { lamb.mapValuesWith(inc)(void 0); }).toThrow(); - }); - - it("should consider other values as empty objects", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.mapValues(value, inc)).toEqual({}); - expect(lamb.mapValuesWith(inc)(value)).toEqual({}); - }); - }); - - it("should throw an exception if `fn` isn't a function or if it's missing", function () { - expect(function () { lamb.mapValues(foo); }).toThrow(); - expect(function () { lamb.mapValuesWith()(foo); }).toThrow(); - }); - }); - - describe("merge / mergeOwn", function () { - var baseFoo = Object.create({a: 1}, {b: {value: 2, enumerable: true}, z: {value: 5}}); - var foo = Object.create(baseFoo, {c: {value: 3, enumerable: true}}); - var bar = {d: 4}; - var fooEquivalent = {a: 1, b: 2, c: 3, z: 5}; - - // The "toEqual" matcher would have checked only own enumerable properties - afterEach(function () { - for (var key in fooEquivalent) { - expect(fooEquivalent[key]).toBe(foo[key]); - } - - expect(bar).toEqual({d: 4}); - }); - - describe("merge", function () { - it("should merge the enumerable properties of the provided sources into a new object without mutating them", function () { - var newObj = lamb.merge(foo, bar); - - expect(newObj).toEqual({a: 1, b: 2, c: 3, d: 4}); - expect(newObj.z).toBeUndefined(); - }); - }); - - describe("mergeOwn", function () { - it("should merge the enumerable own properties of the provided sources into a new object without mutating them", function () { - var newObj = lamb.mergeOwn(foo, bar); - - expect(newObj).toEqual({c: 3, d: 4}); - expect(newObj.a).toBeUndefined(); - expect(newObj.b).toBeUndefined(); - expect(newObj.z).toBeUndefined(); - }); - }); - - it("should transform array-like objects in objects with numbered string as properties", function () { - expect(lamb.merge([1, 2], {a: 2})).toEqual({0: 1, 1: 2, a: 2}); - expect(lamb.mergeOwn([1, 2], {a: 2})).toEqual({0: 1, 1: 2, a: 2}); - expect(lamb.merge("foo", {a: 2})).toEqual({0: "f", 1: "o", 2: "o", a: 2}); - expect(lamb.mergeOwn("foo", {a: 2})).toEqual({0: "f", 1: "o", 2: "o", a: 2}); - }); - - it("should handle key homonymy by giving the last source precedence over the first one", function () { - expect(lamb.merge({a: 1, b: 3}, {b: 5, c: 4})).toEqual({a: 1, b: 5, c: 4}); - expect(lamb.mergeOwn({a: 1, b: 3}, {b: 5, c: 4})).toEqual({a: 1, b: 5, c: 4}); - }); - - it("should throw an exception if called with `nil` values", function () { - expect(function () { lamb.merge({a: 2}, null); }).toThrow(); - expect(function () { lamb.merge(null, {a: 2}); }).toThrow(); - expect(function () { lamb.merge({a: 2}, void 0); }).toThrow(); - expect(function () { lamb.merge(void 0, {a: 2}); }).toThrow(); - - expect(function () { lamb.mergeOwn({a: 2}, null); }).toThrow(); - expect(function () { lamb.mergeOwn(null, {a: 2}); }).toThrow(); - expect(function () { lamb.mergeOwn({a: 2}, void 0); }).toThrow(); - expect(function () { lamb.mergeOwn(void 0, {a: 2}); }).toThrow(); - - expect(lamb.merge).toThrow(); - expect(lamb.mergeOwn).toThrow(); - }); - - it("should consider other values as empty objects", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.merge({a: 2}, value)).toEqual({a: 2}); - expect(lamb.merge(value, {a: 2})).toEqual({a: 2}); - - expect(lamb.mergeOwn({a: 2}, value)).toEqual({a: 2}); - expect(lamb.mergeOwn(value, {a: 2})).toEqual({a: 2}); - }); - }); - }); - - describe("ownPairs / pairs", function () { - var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - var foo = Object.create(baseFoo, { - c: {value: 3}, - d: {value: 4, enumerable: true} - }); - - it("should convert an object in a list of key / value pairs", function () { - var source = {a: 1, b: 2, c: 3}; - var result = [["a", 1], ["b", 2], ["c", 3]]; - - expect(lamb.ownPairs(source)).toEqual(result); - expect(lamb.pairs(source)).toEqual(result); - }); - - it("should keep `undefined` values in the result", function () { - var source = {a: null, b: void 0}; - var result = [["a", null], ["b", void 0]]; - - expect(lamb.ownPairs(source)).toEqual(result); - expect(lamb.pairs(source)).toEqual(result); - }); - - it("should work with array-like objects", function () { - var r1 = [["0", 1], ["1", 2], ["2", 3]]; - var r2 = [["0", "a"], ["1", "b"], ["2", "c"]]; - - expect(lamb.ownPairs([1, 2, 3])).toEqual(r1); - expect(lamb.ownPairs("abc")).toEqual(r2); - expect(lamb.pairs([1, 2, 3])).toEqual(r1); - expect(lamb.pairs("abc")).toEqual(r2); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.ownPairs).toThrow(); - expect(lamb.pairs).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.ownPairs(null); }).toThrow(); - expect(function () { lamb.ownPairs(void 0); }).toThrow(); - expect(function () { lamb.pairs(null); }).toThrow(); - expect(function () { lamb.pairs(void 0); }).toThrow(); - }); - - it("should consider other values as empty objects", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.ownPairs(value)).toEqual([]); - expect(lamb.pairs(value)).toEqual([]); - }); - }); - - describe("ownPairs", function () { - it("should use only the own enumerable properties of the source object", function () { - expect(lamb.ownPairs(foo)).toEqual([["d", 4]]); - }); - }); - - describe("pairs", function () { - it("should use all the enumerable properties of the source object, inherited or not", function () { - expect(lamb.pairs(foo)).toEqual([["d", 4], ["a", 1]]); - }); - }); - }); - - describe("ownValues / values", function () { - var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - var foo = Object.create(baseFoo, { - c: {value: 3}, - d: {value: 4, enumerable: true} - }); - - it("should return an array of values of the given object properties", function () { - var source = {a: 1, b: 2, c: 3}; - var result = [1, 2, 3]; - - expect(lamb.ownValues(source)).toEqual(result); - expect(lamb.values(source)).toEqual(result); - }); - - it("should accept array-like objects, returning an array copy of their values", function () { - expect(lamb.ownValues([1, 2, 3])).toEqual([1, 2, 3]); - expect(lamb.ownValues("abc")).toEqual(["a", "b", "c"]); - expect(lamb.values([1, 2, 3])).toEqual([1, 2, 3]); - expect(lamb.values("abc")).toEqual(["a", "b", "c"]); - }); - - it("should keep `undefined` values in the result", function () { - var source = {a: null, b: void 0}; - var result = [null, void 0]; - - expect(lamb.ownValues(source)).toEqual(result); - expect(lamb.values(source)).toEqual(result); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.ownValues).toThrow(); - expect(lamb.values).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.ownValues(null); }).toThrow(); - expect(function () { lamb.ownValues(void 0); }).toThrow(); - expect(function () { lamb.values(null); }).toThrow(); - expect(function () { lamb.values(void 0); }).toThrow(); - }); - - it("should consider other values as empty objects", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.ownValues(value)).toEqual([]); - expect(lamb.values(value)).toEqual([]); - }); - }); - - describe("ownValues", function () { - it("should pick only from the own enumerable properties of the given object", function () { - expect(lamb.ownValues(foo)).toEqual([4]); - }); - }); - - describe("values", function () { - it("should pick all the enumerable properties of the given object", function () { - expect(lamb.values(foo)).toEqual([4, 1]); - }); - }); - }); - - describe("Property filtering", function () { - var baseSimpleObj = {bar: 2}; - var simpleObj = Object.create(baseSimpleObj, { - foo: {value: 1, enumerable: true}, - baz: {value: 3, enumerable: true} - }); - - var persons = [ - {name: "Jane", surname: "Doe", age: 12, city: "New York"}, - {name: "John", surname: "Doe", age: 40, city: "London"}, - {name: "Mario", surname: "Rossi", age: 18, city: "Rome"}, - {name: "Paolo", surname: "Bianchi", age: 15, city: "Amsterdam"} - ]; - - var agesAndCities = [ - {age: 12, city: "New York"}, - {age: 40, city: "London"}, - {age: 18, city: "Rome"}, - {age: 15, city: "Amsterdam"} - ]; - - var names = [ - {name: "Jane", surname: "Doe"}, - {name: "John", surname: "Doe"}, - {name: "Mario", surname: "Rossi"}, - {name: "Paolo", surname: "Bianchi"} - ]; - - var isNumber = function (v) { return typeof v === "number"; }; - var isNameKey = function (value, key) { return key.indexOf("name") !== -1; }; - - // to check "truthy" and "falsy" values returned by predicates - var isNameKey2 = function (value, key) { return ~key.indexOf("name"); }; - - var oddObject = {}; - - nonStringsAsStrings.forEach(function (key, idx) { - oddObject[key] = idx; - }); - - describe("pick / pickKeys", function () { - it("should return an object having only the specified properties of the source object (if they exist)", function () { - expect(lamb.pick(simpleObj, ["foo", "baz", "foobaz"])).toEqual({foo: 1, baz: 3}); - expect(lamb.pickKeys(["foo", "baz", "foobaz"])(simpleObj)).toEqual({foo: 1, baz: 3}); - }); - - it("should include inherited properties", function () { - expect(lamb.pick(simpleObj, ["foo", "bar"])).toEqual({foo: 1, bar: 2}); - expect(lamb.pickKeys(["foo", "bar"])(simpleObj)).toEqual({foo: 1, bar: 2}); - }); - - it("should include properties with `undefined` values in the result", function () { - expect(lamb.pick({a: null, b: void 0}, ["a", "b"])).toEqual({a: null, b: void 0}); - expect(lamb.pickKeys(["a", "b"])({a: null, b: void 0})).toEqual({a: null, b: void 0}); - }); - - it("should accept arrays and array-like objects and integers as keys", function () { - var result = { - 0: {name: "Jane", surname: "Doe"}, - 2: {name: "Mario", surname: "Rossi"} - }; - - expect(lamb.pick(names, ["0", 2])).toEqual(result); - expect(lamb.pickKeys(["0", 2])(names)).toEqual(result); - expect(lamb.pick("bar", [0, 2])).toEqual({0: "b", 2: "r"}); - expect(lamb.pickKeys([0, 2])("bar")).toEqual({0: "b", 2: "r"}); - }); - - it("should see unassigned or deleted indexes in sparse arrays as non-existing keys", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.pick([1, , 3], [0, 1])).toEqual({0: 1}); - expect(lamb.pickKeys([0, 1])([1, , 3])).toEqual({0: 1}); - /* eslint-enable no-sparse-arrays */ - }); - - it("should see unassigned or deleted indexes in sparse arrays received as the `whitelist` as `undefined` values", function () { - /* eslint-disable comma-spacing, no-sparse-arrays */ - expect(lamb.pick({undefined: 1, a: 2, b: 3}, ["a", ,])).toEqual({undefined: 1, a: 2}); - expect(lamb.pickKeys(["a", ,])({undefined: 1, a: 2, b: 3})).toEqual({undefined: 1, a: 2}); - /* eslint-enable comma-spacing, no-sparse-arrays */ - }); - - it("should return an empty object if supplied with an empty list of keys", function () { - expect(lamb.pick(simpleObj, [])).toEqual({}); - expect(lamb.pickKeys([])(simpleObj)).toEqual({}); - }); - - it("should accept an array-like object as the `whitelist` parameter", function () { - expect(lamb.pick({a: 1, b: 2, c: 3}, "ac")).toEqual({a: 1, c: 3}); - expect(lamb.pickKeys("ac")({a: 1, b: 2, c: 3})).toEqual({a: 1, c: 3}); - }); - - it("should convert to string every value in the `whitelist` parameter", function () { - var testObj = lamb.merge({bar: "baz"}, oddObject); - - expect(lamb.pick(testObj, nonStringsAsStrings)).toEqual(oddObject); - expect(lamb.pickKeys(nonStringsAsStrings)(testObj)).toEqual(oddObject); - }); - - it("should throw an exception if called without the main data argument", function () { - expect(lamb.pick).toThrow(); - expect(lamb.pickKeys(["a", "b"])).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` in place of the `whitelist`", function () { - expect(function () { lamb.pick({a: 1}, null); }).toThrow(); - expect(function () { lamb.pick({a: 1}, void 0); }).toThrow(); - expect(function () { lamb.pickKeys(null)({a: 1}); }).toThrow(); - expect(function () { lamb.pickKeys(void 0)({a: 1}); }).toThrow(); - expect(function () { lamb.pickKeys()({a: 1}); }).toThrow(); - }); - - it("should treat other values for the `whitelist` parameter as an empty array and return an empty object", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.pick({a: 1, b: 2}, value)).toEqual({}); - expect(lamb.pickKeys(value)({a: 1, b: 2})).toEqual({}); - }); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.pick(null, ["a", "b"]); }).toThrow(); - expect(function () { lamb.pick(void 0, ["a", "b"]); }).toThrow(); - expect(function () { lamb.pickKeys(["a", "b"])(null); }).toThrow(); - expect(function () { lamb.pickKeys(["a", "b"])(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.pick(v, ["a"])).toEqual({}); - expect(lamb.pickKeys(["a"])(v)).toEqual({}); - }); - - expect(lamb.pick(/foo/, ["lastIndex"])).toEqual({lastIndex: 0}); - expect(lamb.pickKeys(["lastIndex"])(/foo/)).toEqual({lastIndex: 0}); - }); - }); - - describe("pickIf", function () { - it("should pick object properties using a predicate", function () { - expect(lamb.pickIf(isNumber)(persons[0])).toEqual({age: 12}); - expect(persons.map(lamb.pickIf(isNameKey))).toEqual(names); - }); - - it("should include properties with `undefined` values in the result", function () { - expect(lamb.pickIf(lamb.isNil)({a: null, b: void 0})).toEqual({a: null, b: void 0}); - }); - - it("should accept array-like objects", function () { - var isEven = function (n) { return n % 2 === 0; }; - - expect(lamb.pickIf(isEven)([1, 2, 3, 4])).toEqual({1: 2, 3: 4}); - expect(lamb.pickIf(isEven)("1234")).toEqual({1: "2", 3: "4"}); - }); - - it("should not consider unassigned or deleted indexes in sparse arrays", function () { - // eslint-disable-next-line no-sparse-arrays - expect(lamb.pickIf(lamb.isUndefined)([1, , 3, void 0])).toEqual({3: void 0}); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(persons.map(lamb.pickIf(isNameKey2))).toEqual(names); - }); - - it("should throw an exception if the predicate isn't a function or if is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.pickIf(value)({a: 2}); }).toThrow(); - }); - - expect(function () { lamb.pickIf()({a: 2}); }).toThrow(); - }); - - it("should throw an exception if the `source` is `null` or `undefined` or if is missing", function () { - expect(lamb.pickIf(isNumber)).toThrow(); - expect(function () { lamb.pickIf(isNumber)(null); }).toThrow(); - expect(function () { lamb.pickIf(isNumber)(void 0); }).toThrow(); - }); - - it("should convert to object every other value received as `source`", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.pickIf(isNumber)(v)).toEqual({}); - }); - }); - }); - - describe("skip / skipKeys", function () { - it("should return a copy of the given object without the specified properties", function () { - expect(lamb.skip(simpleObj, ["bar", "baz"])).toEqual({foo: 1}); - expect(lamb.skipKeys(["bar", "baz"])(simpleObj)).toEqual({foo: 1}); - }); - - it("should include inherited properties", function () { - expect(lamb.skip(simpleObj, ["foo"])).toEqual({bar: 2, baz: 3}); - expect(lamb.skipKeys(["foo"])(simpleObj)).toEqual({bar: 2, baz: 3}); - }); - - it("should include properties with `undefined` values in the result", function () { - expect(lamb.skip({a: null, b: void 0}, ["c"])).toEqual({a: null, b: void 0}); - expect(lamb.skipKeys(["c"])({a: null, b: void 0})).toEqual({a: null, b: void 0}); - }); - - it("should accept arrays and array-like objects and integers as keys", function () { - var result = { - 0: {name: "Jane", surname: "Doe"}, - 2: {name: "Mario", surname: "Rossi"} - }; - - expect(lamb.skip(names, ["1", 3])).toEqual(result); - expect(lamb.skipKeys(["1", 3])(names)).toEqual(result); - expect(lamb.skip("bar", [0, 2])).toEqual({1: "a"}); - expect(lamb.skipKeys([0, 2])("bar")).toEqual({1: "a"}); - }); - - it("should see unassigned or deleted indexes in sparse arrays as non-existing keys", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.skip([1, , 3], [2])).toEqual({0: 1}); - expect(lamb.skipKeys([2])([1, , 3])).toEqual({0: 1}); - /* eslint-enable no-sparse-arrays */ - }); - - it("should see unassigned or deleted indexes in sparse arrays received as the `blacklist` as `undefined` values", function () { - /* eslint-disable comma-spacing, no-sparse-arrays */ - expect(lamb.skip({undefined: 1, a: 2, b: 3}, ["a", ,])).toEqual({b: 3}); - expect(lamb.skipKeys(["a", ,])({undefined: 1, a: 2, b: 3})).toEqual({b: 3}); - /* eslint-enable comma-spacing, no-sparse-arrays */ - }); - - it("should return a copy of the source object if supplied with an empty list of keys", function () { - var r1 = lamb.skip(simpleObj, []); - var r2 = lamb.skipKeys([])(simpleObj); - - expect(r1).toEqual({foo: 1, bar: 2, baz: 3}); - expect(r1).not.toBe(simpleObj); - expect(r2).toEqual({foo: 1, bar: 2, baz: 3}); - expect(r2).not.toBe(simpleObj); - }); - - it("should accept an array-like object as the `blacklist` parameter", function () { - expect(lamb.skip({a: 1, b: 2, c: 3}, "ac")).toEqual({b: 2}); - expect(lamb.skipKeys("ac")({a: 1, b: 2, c: 3})).toEqual({b: 2}); - }); - - it("should convert to string every value in the `blacklist` parameter", function () { - var testObj = lamb.merge({bar: "baz"}, oddObject); - - expect(lamb.skip(testObj, nonStringsAsStrings)).toEqual({bar: "baz"}); - expect(lamb.skipKeys(nonStringsAsStrings)(testObj)).toEqual({bar: "baz"}); - }); - - it("should throw an exception if called without the main data argument", function () { - expect(lamb.skip).toThrow(); - expect(lamb.skipKeys(["a", "b"])).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` in place of the `blacklist`", function () { - expect(function () { lamb.skip({a: 1}, null); }).toThrow(); - expect(function () { lamb.skip({a: 1}, void 0); }).toThrow(); - expect(function () { lamb.skipKeys(null)({a: 1}); }).toThrow(); - expect(function () { lamb.skipKeys(void 0)({a: 1}); }).toThrow(); - expect(function () { lamb.skipKeys()({a: 1}); }).toThrow(); - }); - - it("should treat other values for the `blacklist` parameter as an empty array and return a copy of the source object", function () { - wannabeEmptyArrays.forEach(function (value) { - var r1 = lamb.skip(simpleObj, value); - var r2 = lamb.skipKeys(value)(simpleObj); - - expect(r1).toEqual({foo: 1, bar: 2, baz: 3}); - expect(r1).not.toBe(simpleObj); - expect(r2).toEqual({foo: 1, bar: 2, baz: 3}); - expect(r2).not.toBe(simpleObj); - }); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.skip(null, ["a", "b"]); }).toThrow(); - expect(function () { lamb.skip(void 0, ["a", "b"]); }).toThrow(); - expect(function () { lamb.skipKeys(["a", "b"])(null); }).toThrow(); - expect(function () { lamb.skipKeys(["a", "b"])(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.skip(v, ["a"])).toEqual({}); - expect(lamb.skipKeys(["a"])(v)).toEqual({}); - }); - - expect(lamb.skip(/foo/, ["lastIndex"])).toEqual({}); - expect(lamb.skipKeys(["lastIndex"])(/foo/)).toEqual({}); - }); - }); - - describe("skipIf", function () { - it("should skip object properties using a predicate", function () { - expect(persons.map(lamb.skipIf(isNameKey))).toEqual(agesAndCities); - expect(lamb.skipIf(isNumber)(persons[0])).toEqual( - {name: "Jane", surname: "Doe", city: "New York"} - ); - }); - - it("should include properties with `undefined` values in the result", function () { - expect(lamb.skipIf(lamb.not(lamb.isNil))({a: null, b: void 0})).toEqual({a: null, b: void 0}); - }); - - it("should accept array-like objects", function () { - var isEven = function (n) { return n % 2 === 0; }; - - expect(lamb.skipIf(isEven)([1, 2, 3, 4])).toEqual({0: 1, 2: 3}); - expect(lamb.skipIf(isEven)("1234")).toEqual({0: "1", 2: "3"}); - }); - - it("should not consider unassigned or deleted indexes in sparse arrays", function () { - // eslint-disable-next-line no-sparse-arrays - expect(lamb.skipIf(lamb.not(lamb.isUndefined))([1, , 3, void 0])).toEqual({3: void 0}); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - expect(persons.map(lamb.skipIf(isNameKey2))).toEqual(agesAndCities); - }); - - it("should throw an exception if the predicate isn't a function or if is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.skipIf(value)({a: 2}); }).toThrow(); - }); - - expect(function () { lamb.skipIf()({a: 2}); }).toThrow(); - }); - - it("should throw an exception if the `source` is `null` or `undefined` or if is missing", function () { - expect(lamb.skipIf(isNumber)).toThrow(); - expect(function () { lamb.skipIf(isNumber)(null); }).toThrow(); - expect(function () { lamb.skipIf(isNumber)(void 0); }).toThrow(); - }); - - it("should convert to object every other value received as `source`", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.skipIf(isNumber)(v)).toEqual({}); - }); - }); - }); - }); - - describe("rename / renameKeys / renameWith", function () { - var baseObj = {"": 0, a: 1, b: 2, c: 3, d: 4}; - var obj = Object.create(baseObj, { - e: {value: 5, enumerable: true}, - f: {value: 6} - }); - var objEquivalent = {"": 0, a: 1, b: 2, c: 3, d: 4, e: 5}; - - // The "toEqual" matcher would have checked only own enumerable properties - afterEach(function () { - for (var key in objEquivalent) { - expect(objEquivalent[key]).toEqual(obj[key]); - } - - expect(obj.f).toBe(6); - }); - - describe("renameWith", function () { - it("should use the provided function to generate a keys' map for `rename`", function () { - var person = {NAME: "John", SURNAME: "Doe"}; - var makeLowerKeysMap = function (source) { - var sourceKeys = lamb.keys(source); - - return lamb.make(sourceKeys, sourceKeys.map(lamb.invoker("toLowerCase"))); - }; - var renameSpy = jasmine.createSpy().and.callFake(makeLowerKeysMap); - - expect(lamb.renameWith(renameSpy)(person)).toEqual({name: "John", surname: "Doe"}); - expect(renameSpy.calls.count()).toBe(1); - expect(renameSpy.calls.argsFor(0).length).toBe(1); - expect(renameSpy.calls.argsFor(0)[0]).toBe(person); - }); - - it("should build a function throwing an exception if `fn` isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.renameWith(value)).toThrow(); - }); - - expect(lamb.renameWith()).toThrow(); - }); - }); - - it("should rename the keys of the given object according to the provided keys' map", function () { - var keysMap = {a: "w", b: "x", c: "y", d: "z"}; - var result = {"": 0, w: 1, x: 2, y: 3, z: 4, e: 5}; - - expect(lamb.rename(obj, keysMap)).toEqual(result); - expect(lamb.renameKeys(keysMap)(obj)).toEqual(result); - expect(lamb.renameWith(lamb.always(keysMap))(obj)).toEqual(result); - }); - - it("should be possible to use existing key names", function () { - var keysMap = {c: "x", e: "b", b: "e"}; - var result = {"": 0, a: 1, e: 2, x: 3, d: 4, b: 5}; - - expect(lamb.rename(obj, keysMap)).toEqual(result); - expect(lamb.renameKeys(keysMap)(obj)).toEqual(result); - expect(lamb.renameWith(lamb.always(keysMap))(obj)).toEqual(result); - }); - - it("should not add non-existing keys", function () { - var keysMap = {z: "x", y: "c"}; - var r1 = lamb.rename(obj, keysMap); - var r2 = lamb.renameKeys(keysMap)(obj); - var r3 = lamb.renameWith(lamb.always(keysMap))(obj); - - expect(r1).toEqual(objEquivalent); - expect(r2).toEqual(objEquivalent); - expect(r3).toEqual(objEquivalent); - expect(r1).not.toBe(obj); - expect(r2).not.toBe(obj); - expect(r3).not.toBe(obj); - }); - - it("should give priority to the map and overwrite an existing key if necessary", function () { - var keysMap = {a: "", b: "c"}; - var result = {"": 1, c: 2, d: 4, e: 5}; - - expect(lamb.rename(obj, keysMap)).toEqual(result); - expect(lamb.renameKeys(keysMap)(obj)).toEqual(result); - expect(lamb.renameWith(lamb.always(keysMap))(obj)).toEqual(result); - }); - - it("should return a copy of the source if the keys' map is empty or contains only non-enumerable properties", function () { - var r1 = lamb.rename(obj, {}); - var r2 = lamb.renameKeys({})(obj); - var r3 = lamb.renameWith(lamb.always({}))(obj); - var r4 = lamb.rename(obj, {f: "z"}); - var r5 = lamb.renameKeys({f: "z"})(obj); - var r6 = lamb.renameWith(lamb.always({f: "z"}))(obj); - - expect(r1).toEqual(objEquivalent); - expect(r2).toEqual(objEquivalent); - expect(r3).toEqual(objEquivalent); - expect(r4).toEqual(objEquivalent); - expect(r5).toEqual(objEquivalent); - expect(r6).toEqual(objEquivalent); - expect(r1).not.toBe(obj); - expect(r2).not.toBe(obj); - expect(r3).not.toBe(obj); - expect(r4).not.toBe(obj); - expect(r5).not.toBe(obj); - expect(r6).not.toBe(obj); - }); - - it("should accept array-like objects as a source", function () { - var arr = [1, 2, 3]; - var s = "foo"; - var keysMap = {0: "a", 1: "b", 2: "c"}; - var r1 = {a: 1, b: 2, c: 3}; - var r2 = {a: "f", b: "o", c: "o"}; - - expect(lamb.rename(arr, keysMap)).toEqual(r1); - expect(lamb.rename(s, keysMap)).toEqual(r2); - expect(lamb.renameKeys(keysMap)(arr)).toEqual(r1); - expect(lamb.renameKeys(keysMap)(s)).toEqual(r2); - expect(lamb.renameWith(lamb.always(keysMap))(arr)).toEqual(r1); - expect(lamb.renameWith(lamb.always(keysMap))(s)).toEqual(r2); - }); - - it("should not consider unassigned or deleted indexes when the source is a sparse array", function () { - var arr = [1, , 3]; // eslint-disable-line no-sparse-arrays - var keysMap = {0: "a", 1: "b", 2: "c"}; - var result = {a: 1, c: 3}; - - expect(lamb.rename(arr, keysMap)).toEqual(result); - expect(lamb.renameKeys(keysMap)(arr)).toEqual(result); - expect(lamb.renameWith(lamb.always(keysMap))(arr)).toEqual(result); - }); - - it("should accept array-like objects as key maps", function () { - var arr = [1, 2, 3]; - var s = "bar"; - var someObj = {0: "a", 1: "b", 2: "c"}; - var r1 = {1: "a", 2: "b", 3: "c"}; - var r2 = {b: "a", a: "b", r: "c"}; - - expect(lamb.rename(someObj, arr)).toEqual(r1); - expect(lamb.rename(someObj, s)).toEqual(r2); - expect(lamb.renameKeys(arr)(someObj)).toEqual(r1); - expect(lamb.renameKeys(s)(someObj)).toEqual(r2); - expect(lamb.renameWith(lamb.always(arr))(someObj)).toEqual(r1); - expect(lamb.renameWith(lamb.always(s))(someObj)).toEqual(r2); - }); - - it("should not consider unassigned or deleted indexes when a sparse array is supplied as a key map", function () { - var someObj = {0: "a", 1: "b", 2: "c"}; - var arrKeyMap = [1, , 3]; // eslint-disable-line no-sparse-arrays - var result = {1: "a", 3: "c"}; - - expect(lamb.rename(someObj, arrKeyMap)).toEqual(result); - expect(lamb.renameKeys(arrKeyMap)(someObj)).toEqual(result); - expect(lamb.renameWith(lamb.always(arrKeyMap))(someObj)).toEqual(result); - }); - - it("should return a copy of the source object for any other value passed as the keys' map", function () { - wannabeEmptyObjects.forEach(function (value) { - var r1 = lamb.rename(obj, value); - var r2 = lamb.renameKeys(value)(obj); - var r3 = lamb.renameWith(lamb.always(value))(obj); - - expect(r1).toEqual(objEquivalent); - expect(r2).toEqual(objEquivalent); - expect(r3).toEqual(objEquivalent); - expect(r1).not.toBe(obj); - expect(r2).not.toBe(obj); - expect(r3).not.toBe(obj); - }); - }); - - it("should throw an exception if called without the source or the keys' map", function () { - expect(lamb.rename).toThrow(); - expect(lamb.renameKeys()).toThrow(); - expect(lamb.renameWith(lamb.always({}))).toThrow(); - }); - - it("should throw an exception if the source is `null` or `undefined`", function () { - expect(function () { lamb.rename(null, {}); }).toThrow(); - expect(function () { lamb.rename(void 0, {}); }).toThrow(); - expect(function () { lamb.renameKeys({})(null); }).toThrow(); - expect(function () { lamb.renameKeys({})(void 0); }).toThrow(); - expect(function () { lamb.renameWith(lamb.always({}))(null); }).toThrow(); - expect(function () { lamb.renameWith(lamb.always({}))(void 0); }).toThrow(); - }); - - it("should return an empty object for any other value passed as the source object", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.rename(value, {0: 9})).toEqual({}); - expect(lamb.renameKeys({0: 9})(value)).toEqual({}); - expect(lamb.renameWith(lamb.always({0: 9}))(value)).toEqual({}); - }); - }); - }); - - describe("tear / tearOwn", function () { - var baseFoo = Object.create({a: 1}, {b: {value: 2}}); - var foo = Object.create(baseFoo, { - c: {value: 3}, - d: {value: 4, enumerable: true} - }); - - it("should transform an object in two lists, one containing its keys, the other containing the corresponding values", function () { - expect(lamb.tear({a: 1, b: 2, c: 3})).toEqual([["a", "b", "c"], [1, 2, 3]]); - expect(lamb.tearOwn({a: 1, b: 2, c: 3})).toEqual([["a", "b", "c"], [1, 2, 3]]); - }); - - it("should work with array-like objects", function () { - var r1 = [["0", "1", "2"], [1, 2, 3]]; - var r2 = [["0", "1", "2"], ["a", "b", "c"]]; - - expect(lamb.tear([1, 2, 3])).toEqual(r1); - expect(lamb.tear("abc")).toEqual(r2); - expect(lamb.tearOwn([1, 2, 3])).toEqual(r1); - expect(lamb.tearOwn("abc")).toEqual(r2); - }); - - it("should keep `undefined` values in the result", function () { - var source = {a: null, b: void 0}; - var result = [["a", "b"], [null, void 0]]; - - expect(lamb.tear(source)).toEqual(result); - expect(lamb.tearOwn(source)).toEqual(result); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.tear).toThrow(); - expect(lamb.tearOwn).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined`", function () { - expect(function () { lamb.tear(null); }).toThrow(); - expect(function () { lamb.tear(void 0); }).toThrow(); - expect(function () { lamb.tearOwn(null); }).toThrow(); - expect(function () { lamb.tearOwn(void 0); }).toThrow(); - }); - - it("should consider other values as empty objects", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.tear(value)).toEqual([[], []]); - expect(lamb.tearOwn(value)).toEqual([[], []]); - }); - }); - - describe("tear", function () { - it("should use all the enumerable properties of the source object, inherited or not", function () { - expect(lamb.tear(foo)).toEqual([["d", "a"], [4, 1]]); - }); - }); - - describe("tearOwn", function () { - it("should use only the own enumerable properties of the source object", function () { - expect(lamb.tearOwn(foo)).toEqual([["d"], [4]]); - }); - }); - }); -}); diff --git a/test/spec/object_checkingSpec.js b/test/spec/object_checkingSpec.js deleted file mode 100644 index 52a7a32..0000000 --- a/test/spec/object_checkingSpec.js +++ /dev/null @@ -1,928 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var nonStrings = commons.vars.nonStrings; -var nonStringsAsStrings = commons.vars.nonStringsAsStrings; -var nonFunctions = commons.vars.nonFunctions; -var wannabeEmptyObjects = commons.vars.wannabeEmptyObjects; - -describe("lamb.object_checking", function () { - describe("Property checking", function () { - var obj = {foo: "bar"}; - - describe("has / hasKey", function () { - it("should check the existence of the property in an object", function () { - expect(lamb.has(obj, "toString")).toBe(true); - expect(lamb.has(obj, "foo")).toBe(true); - expect(lamb.hasKey("toString")(obj)).toBe(true); - expect(lamb.hasKey("foo")(obj)).toBe(true); - }); - - it("should return `false` for a non-existent property", function () { - expect(lamb.has(obj, "baz")).toBe(false); - expect(lamb.hasKey("baz")(obj)).toBe(false); - }); - - it("should accept integers as keys and accept array-like objects", function () { - var o = {1: "a", 2: "b"}; - var arr = [1, 2, 3, 4]; - var s = "abcd"; - - expect(lamb.has(o, 2)).toBe(true); - expect(lamb.has(arr, 2)).toBe(true); - expect(lamb.has(s, 2)).toBe(true); - expect(lamb.hasKey(2)(o)).toBe(true); - expect(lamb.hasKey(2)(arr)).toBe(true); - expect(lamb.hasKey(2)(s)).toBe(true); - }); - - it("should consider only defined indexes in sparse arrays", function () { - var arr = [1, , 3]; // eslint-disable-line no-sparse-arrays - - expect(lamb.has(arr, 1)).toBe(false); - expect(lamb.hasKey(1)(arr)).toBe(false); - }); - - it("should convert other values for the `key` parameter to string", function () { - var testObj = lamb.make(nonStringsAsStrings, []); - - nonStrings.forEach(function (key) { - expect(lamb.has(testObj, key)).toBe(true); - expect(lamb.hasKey(key)(testObj)).toBe(true); - }); - - expect(lamb.has({undefined: void 0})).toBe(true); - expect(lamb.hasKey()({undefined: void 0})).toBe(true); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.has).toThrow(); - expect(lamb.hasKey("foo")).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.has(null, "a"); }).toThrow(); - expect(function () { lamb.has(void 0, "a"); }).toThrow(); - expect(function () { lamb.hasKey("a")(null); }).toThrow(); - expect(function () { lamb.hasKey("a")(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.has(v, "a")).toBe(false); - expect(lamb.hasKey("a")(v)).toBe(false); - }); - - expect(lamb.has(/foo/, "lastIndex")).toBe(true); - expect(lamb.hasKey("lastIndex")(/foo/)).toBe(true); - }); - }); - - describe("hasOwn / hasOwnKey", function () { - it("should check the existence of an owned property in an object", function () { - expect(lamb.hasOwn(obj, "toString")).toBe(false); - expect(lamb.hasOwn(obj, "foo")).toBe(true); - expect(lamb.hasOwnKey("toString")(obj)).toBe(false); - expect(lamb.hasOwnKey("foo")(obj)).toBe(true); - }); - - it("should return `false` for a non-existent property", function () { - expect(lamb.hasOwn(obj, "baz")).toBe(false); - expect(lamb.hasOwnKey("baz")(obj)).toBe(false); - }); - - it("should accept integers as keys and accept array-like objects", function () { - var o = {1: "a", 2: "b"}; - var arr = [1, 2, 3, 4]; - var s = "abcd"; - - expect(lamb.hasOwn(o, 2)).toBe(true); - expect(lamb.hasOwn(arr, 2)).toBe(true); - expect(lamb.hasOwn(s, 2)).toBe(true); - expect(lamb.hasOwnKey(2)(o)).toBe(true); - expect(lamb.hasOwnKey(2)(arr)).toBe(true); - expect(lamb.hasOwnKey(2)(s)).toBe(true); - }); - - it("should consider only defined indexes in sparse arrays", function () { - var arr = [1, , 3]; // eslint-disable-line no-sparse-arrays - - expect(lamb.hasOwn(arr, 1)).toBe(false); - expect(lamb.hasOwnKey(1)(arr)).toBe(false); - }); - - it("should convert other values for the `key` parameter to string", function () { - var testObj = lamb.make(nonStringsAsStrings, []); - - nonStrings.forEach(function (key) { - expect(lamb.hasOwn(testObj, key)).toBe(true); - expect(lamb.hasOwnKey(key)(testObj)).toBe(true); - }); - - expect(lamb.hasOwn({undefined: void 0})).toBe(true); - expect(lamb.hasOwnKey()({undefined: void 0})).toBe(true); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.hasOwn).toThrow(); - expect(lamb.hasOwnKey("foo")).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.hasOwn(null, "a"); }).toThrow(); - expect(function () { lamb.hasOwn(void 0, "a"); }).toThrow(); - expect(function () { lamb.hasOwnKey("a")(null); }).toThrow(); - expect(function () { lamb.hasOwnKey("a")(void 0); }).toThrow(); - }); - - it("should return convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.hasOwn(v, "a")).toBe(false); - expect(lamb.hasOwnKey("a")(v)).toBe(false); - }); - - expect(lamb.hasOwn(/foo/, "lastIndex")).toBe(true); - expect(lamb.hasOwnKey("lastIndex")(/foo/)).toBe(true); - }); - }); - }); - - describe("hasKeyValue", function () { - var persons = [ - {name: "Jane", surname: "Doe"}, - {name: "John", surname: "Doe"}, - {name: "Mario", surname: "Rossi"} - ]; - - var isDoe = lamb.hasKeyValue("surname", "Doe"); - - it("should build a function that checks if an object holds the desired key / value pair", function () { - expect(lamb.hasKeyValue("a", 45)({a: 45})).toBe(true); - expect(lamb.hasKeyValue("a", [45])({a: 45})).toBe(false); - expect(persons.map(isDoe)).toEqual([true, true, false]); - }); - - it("should return `false` for a non-existent property", function () { - expect(lamb.hasKeyValue("z", void 0)(persons[0])).toBe(false); - }); - - it("should be able to check for `undefined` values in existing keys", function () { - var obj = {a: void 0, b: 5}; - - expect(lamb.hasKeyValue("a", void 0)(obj)).toBe(true); - expect(lamb.hasKeyValue("a")(obj)).toBe(true); - expect(lamb.hasKeyValue("b", void 0)(obj)).toBe(false); - expect(lamb.hasKeyValue("b")(obj)).toBe(false); - }); - - it("should use the \"SameValueZero\" comparison", function () { - var obj = {a: NaN, b: 0, c: -0}; - - expect(lamb.hasKeyValue("a", NaN)(obj)).toBe(true); - expect(lamb.hasKeyValue("b", 0)(obj)).toBe(true); - expect(lamb.hasKeyValue("b", -0)(obj)).toBe(true); - expect(lamb.hasKeyValue("c", -0)(obj)).toBe(true); - expect(lamb.hasKeyValue("c", 0)(obj)).toBe(true); - }); - - it("should accept integers as keys and accept array-like objects", function () { - var o = {1: "a", 2: "b"}; - var arr = [1, 2, 3, 4]; - var s = "abcd"; - - expect(lamb.hasKeyValue(2, "b")(o)).toBe(true); - expect(lamb.hasKeyValue(2, 3)(arr)).toBe(true); - expect(lamb.hasKeyValue(2, "c")(s)).toBe(true); - }); - - it("should consider only defined indexes in sparse arrays", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.hasKeyValue("1", void 0)([1, , 3])).toBe(false); - expect(lamb.hasKeyValue("-2", void 0)([1, , 3])).toBe(false); - /* eslint-enable no-sparse-arrays */ - }); - - it("should convert other values for the `key` parameter to string", function () { - var testObj = lamb.make( - nonStringsAsStrings, - lamb.range(0, nonStringsAsStrings.length, 1) - ); - - nonStrings.forEach(function (key) { - var value = nonStringsAsStrings.indexOf(String(key)); - - expect(lamb.hasKeyValue(key, value)(testObj)).toBe(true); - }); - - expect(lamb.hasKeyValue()({undefined: void 0})).toBe(true); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.hasKeyValue("foo", 2)).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.hasKeyValue("a", 2)(null); }).toThrow(); - expect(function () { lamb.hasKeyValue("a", 2)(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.hasKeyValue("a", 2)(v)).toBe(false); - }); - - expect(lamb.hasKeyValue("lastIndex", 0)(/foo/)).toBe(true); - }); - }); - - describe("hasPathValue", function () { - var obj = { - a: 2, - b: { - a: 3, - b: [4, 5], - c: "foo", - e: {a: 45, b: void 0} - }, - "c.d": {"e.f": 6}, - c: {a: -0, b: NaN} - }; - - obj.b.d = Array(3); - obj.b.d[1] = 99; - - Object.defineProperty(obj, "e", {value: 10}); - obj.f = Object.create({}, {g: {value: 20}}); - - it("should verify if the given path points to the desired value", function () { - expect(lamb.hasPathValue("a", 2)(obj)).toBe(true); - expect(lamb.hasPathValue("b.a", 3)(obj)).toBe(true); - expect(lamb.hasPathValue("b.b", obj.b.b)(obj)).toBe(true); - expect(lamb.hasPathValue("b.e.a", 45)(obj)).toBe(true); - expect(lamb.hasPathValue("a", "2")(obj)).toBe(false); - expect(lamb.hasPathValue("b.a", -3)(obj)).toBe(false); - expect(lamb.hasPathValue("b.b", [4, 5])(obj)).toBe(false); - expect(lamb.hasPathValue("b.e.a", [45])(obj)).toBe(false); - }); - - it("should use the SameValueZero comparison", function () { - expect(lamb.hasPathValue("c.a", 0)(obj)).toBe(true); - expect(lamb.hasPathValue("c.a", -0)(obj)).toBe(true); - expect(lamb.hasPathValue("c.b", NaN)(obj)).toBe(true); - }); - - it("should be able to verify non-enumerable properties", function () { - expect(lamb.hasPathValue("e", 10)(obj)).toBe(true); - expect(lamb.hasPathValue("f.g", 20)(obj)).toBe(true); - }); - - it("should be able to verify values in arrays and array-like objects", function () { - expect(lamb.hasPathValue("b.b.0", 4)(obj)).toBe(true); - expect(lamb.hasPathValue("b.c.0", "f")(obj)).toBe(true); - }); - - it("should allow negative indexes in paths", function () { - expect(lamb.hasPathValue("b.b.-1", 5)(obj)).toBe(true); - expect(lamb.hasPathValue("b.c.-3", "f")(obj)).toBe(true); - }); - - it("should return `false` for a non-existent property in a valid source", function () { - expect(lamb.hasPathValue("b.a.z", void 0)(obj)).toBe(false); - expect(lamb.hasPathValue("b.z.a", void 0)(obj)).toBe(false); - expect(lamb.hasPathValue("b.b.2", void 0)(obj)).toBe(false); - expect(lamb.hasPathValue("b.b.-3", void 0)(obj)).toBe(false); - expect(lamb.hasPathValue("b.c.3", void 0)(obj)).toBe(false); - expect(lamb.hasPathValue("b.c.-4", void 0)(obj)).toBe(false); - expect(lamb.hasPathValue("b.e.z", void 0)(obj)).toBe(false); - }); - - it("should be able to check for `undefined` values in existing paths", function () { - expect(lamb.hasPathValue("b.e.b", void 0)(obj)).toBe(true); - expect(lamb.hasPathValue("b.e.b")(obj)).toBe(true); - expect(lamb.hasPathValue("b.e.a", void 0)(obj)).toBe(false); - expect(lamb.hasPathValue("b.e.a")(obj)).toBe(false); - }); - - it("should work with sparse arrays", function () { - expect(lamb.hasPathValue("b.d.0", void 0)(obj)).toBe(true); - expect(lamb.hasPathValue("b.d.-3", void 0)(obj)).toBe(true); - expect(lamb.hasPathValue("b.d.1", 99)(obj)).toBe(true); - expect(lamb.hasPathValue("b.d.-2", 99)(obj)).toBe(true); - }); - - it("should be able to verify values nested in arrays", function () { - var o = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 20}, - {id: 3, value: 30} - ] - }; - - expect(lamb.hasPathValue("data.1.value", 20)(o)).toBe(true); - expect(lamb.hasPathValue("data.-1.value", 30)(o)).toBe(true); - }); - - it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { - var o = {a: ["abc", new String("def"), "ghi"]}; - - o.a["-1"] = "foo"; - o.a[1]["-2"] = "bar"; - Object.defineProperty(o.a, "-2", {value: 99}); - - expect(lamb.hasPathValue("a.-1", "foo")(o)).toBe(true); - expect(lamb.hasPathValue("a.1.-2", "bar")(o)).toBe(true); - expect(lamb.hasPathValue("a.-2", 99)(o)).toBe(true); - }); - - it("should accept a custom path separator", function () { - expect(lamb.hasPathValue("b->b->0", 4, "->")(obj)).toBe(true); - expect(lamb.hasPathValue("c.d/e.f", 6, "/")(obj)).toBe(true); - }); - - it("should accept integers as paths containing a single key", function () { - expect(lamb.hasPathValue(1, 2)([1, 2])).toBe(true); - expect(lamb.hasPathValue(-1, 2)([1, 2])).toBe(true); - expect(lamb.hasPathValue(1, "a")({1: "a"})).toBe(true); - }); - - it("should convert other values for the `path` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - var value = values[nonStringsAsStrings.indexOf(String(key))]; - - expect(lamb.hasPathValue(key, value, "_")(testObj)).toBe(true); - }); - - var fooObj = {a: 2, 1: {5: 3}}; - - expect(lamb.hasPathValue(1.5, 3)(fooObj)).toBe(true); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.hasPathValue()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.hasPathValue("a", 99)(null); }).toThrow(); - expect(function () { lamb.hasPathValue("a", 99)(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.hasPathValue("a", 99)(value)).toBe(false); - }); - - expect(lamb.hasPathValue("lastIndex", 0)(/foo/)).toBe(true); - }); - }); - - describe("keySatisfies", function () { - var users = [ - {name: "Jane", age: 12, active: false}, - {name: "John", age: 40, active: false}, - {name: "Mario", age: 18, active: true}, - {name: "Paolo", age: 15, active: true} - ]; - - var isValue = lamb.curry(lamb.is); - - it("should use a predicate and a property name to build a new predicate that will be applied on an object's key", function () { - var isAdult = lamb.keySatisfies(lamb.isGTE(18), "age"); - - expect(users.map(isAdult)).toEqual([false, true, true, false]); - }); - - it("should pass an `undefined` value to the predicate if the given property doesn't exist", function () { - var isGreaterThan17 = function (n) { - expect(arguments.length).toBe(1); - expect(n).toBeUndefined(); - - return n > 17; - }; - - expect(lamb.keySatisfies(isGreaterThan17, "foo")(users[0])).toBe(false); - expect(lamb.keySatisfies(isGreaterThan17, "bar")(users[2])).toBe(false); - }); - - it("should apply the function's calling context to the predicate", function () { - var ageValidator = { - minAllowedAge: 15, - maxAllowedAge: 50, - hasValidAge: lamb.keySatisfies(function (age) { - return age >= this.minAllowedAge && age <= this.maxAllowedAge; - }, "age") - }; - - expect(ageValidator.hasValidAge({age: 20})).toBe(true); - expect(ageValidator.hasValidAge({age: 55})).toBe(false); - - var ageValidator2 = Object.create(ageValidator, { - minAllowedAge: {value: 21}, - maxAllowedAge: {value: 55} - }); - - expect(ageValidator2.hasValidAge({age: 20})).toBe(false); - expect(ageValidator2.hasValidAge({age: 55})).toBe(true); - }); - - it("should accept integers as keys and accept array-like objects", function () { - var o = {1: "a", 2: "b"}; - var arr = [1, 2, 3, 4]; - var s = "abcd"; - - expect(lamb.keySatisfies(isValue("a"), 1)(o)).toBe(true); - expect(lamb.keySatisfies(isValue(2), 1)(arr)).toBe(true); - expect(lamb.keySatisfies(isValue("c"), 2)(s)).toBe(true); - }); - - it("should pass an `undefined` value to the predicate for unassigned or deleted indexes in sparse arrays", function () { - /* eslint-disable no-sparse-arrays */ - expect(lamb.keySatisfies(lamb.isUndefined, "1")([1, , 3])).toBe(true); - expect(lamb.keySatisfies(lamb.isUndefined, "-2")([1, , 3])).toBe(true); - /* eslint-enable no-sparse-arrays */ - }); - - it("should convert other values for the `key` parameter to string", function () { - var testObj = lamb.make( - nonStringsAsStrings, - lamb.range(0, nonStringsAsStrings.length, 1) - ); - - nonStrings.forEach(function (key) { - var value = nonStringsAsStrings.indexOf(String(key)); - - expect(lamb.keySatisfies(isValue(value), key)(testObj)).toBe(true); - }); - - expect(lamb.keySatisfies(isValue(99))({undefined: 99})).toBe(true); - }); - - it("should throw an exception if the predicate isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(function () { lamb.keySatisfies(value, "foo")({}); }).toThrow(); - }); - - expect(function () { lamb.keySatisfies()({}); }).toThrow(); - }); - - it("should throw an exception if called without the data argument", function () { - expect(lamb.keySatisfies(lamb.contains, "foo")).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.keySatisfies(lamb.contains(99), "foo")(null); }).toThrow(); - expect(function () { lamb.keySatisfies(lamb.contains(99), "foo")(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - var isZero = lamb.isSVZ(0); - - wannabeEmptyObjects.forEach(function (v) { - expect(lamb.keySatisfies(isZero, "foo")(v)).toBe(false); - }); - - expect(lamb.keySatisfies(isZero, "lastIndex")(/foo/)).toBe(true); - }); - }); - - describe("pathExists / pathExistsIn", function () { - var obj = { - a: 2, - b: { - a: 3, - b: [4, 5], - c: "foo", - e: {a: 45, b: void 0} - }, - "c.d": {"e.f": 6}, - c: {a: -0, b: NaN} - }; - - obj.b.d = Array(3); - obj.b.d[1] = 99; - - Object.defineProperty(obj, "e", {value: 10}); - obj.f = Object.create({}, {g: {value: 20}}); - - it("should verify if the provided path exists in the given object", function () { - expect(lamb.pathExists("a")(obj)).toBe(true); - expect(lamb.pathExists("b.a")(obj)).toBe(true); - expect(lamb.pathExists("b.b")(obj)).toBe(true); - expect(lamb.pathExists("b.e.a")(obj)).toBe(true); - expect(lamb.pathExists("z")(obj)).toBe(false); - expect(lamb.pathExists("a.z")(obj)).toBe(false); - expect(lamb.pathExists("b.a.z")(obj)).toBe(false); - - expect(lamb.pathExistsIn(obj, "a")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.a")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.b")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.e.a")).toBe(true); - expect(lamb.pathExistsIn(obj, "z")).toBe(false); - expect(lamb.pathExistsIn(obj, "a.z")).toBe(false); - expect(lamb.pathExistsIn(obj, "b.a.z")).toBe(false); - }); - - it("should see existent paths when checking properties holding `undefined` values", function () { - expect(lamb.pathExists("b.e.b")(obj)).toBe(true); - expect(lamb.pathExistsIn(obj, "b.e.b")).toBe(true); - }); - - it("should be able to check paths with non-enumerable properties", function () { - expect(lamb.pathExists("e")(obj)).toBe(true); - expect(lamb.pathExists("f.g")(obj)).toBe(true); - - expect(lamb.pathExistsIn(obj, "e")).toBe(true); - expect(lamb.pathExistsIn(obj, "f.g")).toBe(true); - }); - - it("should be able to check arrays and array-like objects", function () { - expect(lamb.pathExists("b.b.0")(obj)).toBe(true); - expect(lamb.pathExists("b.c.0")(obj)).toBe(true); - expect(lamb.pathExists("b.b.2")(obj)).toBe(false); - expect(lamb.pathExists("b.c.3")(obj)).toBe(false); - - expect(lamb.pathExistsIn(obj, "b.b.0")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.c.0")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.b.2")).toBe(false); - expect(lamb.pathExistsIn(obj, "b.c.3")).toBe(false); - }); - - it("should allow negative indexes in paths", function () { - expect(lamb.pathExists("b.b.-1")(obj)).toBe(true); - expect(lamb.pathExists("b.c.-3")(obj)).toBe(true); - expect(lamb.pathExists("b.b.-3")(obj)).toBe(false); - expect(lamb.pathExists("b.c.-4")(obj)).toBe(false); - - expect(lamb.pathExistsIn(obj, "b.b.-1")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.c.-3")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.b.-3")).toBe(false); - expect(lamb.pathExistsIn(obj, "b.c.-4")).toBe(false); - }); - - it("should work with sparse arrays", function () { - expect(lamb.pathExists("b.d.0")(obj)).toBe(true); - expect(lamb.pathExists("b.d.1")(obj)).toBe(true); - expect(lamb.pathExists("b.d.-2")(obj)).toBe(true); - - expect(lamb.pathExistsIn(obj, "b.d.0")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.d.1")).toBe(true); - expect(lamb.pathExistsIn(obj, "b.d.-2")).toBe(true); - }); - - it("should be able to check objects nested in arrays", function () { - var o = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 20}, - {id: 3, value: 30} - ] - }; - - expect(lamb.pathExists("data.1.value")(o)).toBe(true); - expect(lamb.pathExists("data.-1.value")(o)).toBe(true); - expect(lamb.pathExists("data.3.value")(o)).toBe(false); - expect(lamb.pathExists("data.-4.value")(o)).toBe(false); - - expect(lamb.pathExistsIn(o, "data.1.value")).toBe(true); - expect(lamb.pathExistsIn(o, "data.-1.value")).toBe(true); - expect(lamb.pathExistsIn(o, "data.3.value")).toBe(false); - expect(lamb.pathExistsIn(o, "data.-4.value")).toBe(false); - }); - - it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { - var o = {a: ["abc", new String("def"), "ghi"]}; - - o.a["-1"] = "foo"; - o.a[1]["-2"] = "bar"; - Object.defineProperty(o.a, "-2", {value: 99}); - - expect(lamb.pathExists("a.-1")(o)).toBe(true); - expect(lamb.pathExists("a.1.-2")(o)).toBe(true); - expect(lamb.pathExists("a.-2")(o)).toBe(true); - - expect(lamb.pathExistsIn(o, "a.-1")).toBe(true); - expect(lamb.pathExistsIn(o, "a.1.-2")).toBe(true); - expect(lamb.pathExistsIn(o, "a.-2")).toBe(true); - }); - - it("should accept a custom path separator", function () { - expect(lamb.pathExists("b->b->0", "->")(obj)).toBe(true); - expect(lamb.pathExists("c.d/e.f", "/")(obj)).toBe(true); - - expect(lamb.pathExistsIn(obj, "b->b->0", "->")).toBe(true); - expect(lamb.pathExistsIn(obj, "c.d/e.f", "/")).toBe(true); - }); - - it("should accept integers as paths containing a single key", function () { - expect(lamb.pathExists(1)([1, 2])).toBe(true); - expect(lamb.pathExists(-1)([1, 2])).toBe(true); - expect(lamb.pathExists(1)({1: "a"})).toBe(true); - - expect(lamb.pathExistsIn([1, 2], 1)).toBe(true); - expect(lamb.pathExistsIn([1, 2], -1)).toBe(true); - expect(lamb.pathExistsIn({1: "a"}, 1)).toBe(true); - }); - - it("should convert other values for the `path` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key) { - expect(lamb.pathExists(key, "_")(testObj)).toBe(true); - expect(lamb.pathExistsIn(testObj, key, "_")).toBe(true); - }); - - var fooObj = {a: 2, 1: {5: 3}}; - - expect(lamb.pathExists(1.5)(fooObj)).toBe(true); - expect(lamb.pathExistsIn(fooObj, 1.5)).toBe(true); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.pathExists()).toThrow(); - expect(function () { lamb.pathExistsIn(); }).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.pathExists("a")(null); }).toThrow(); - expect(function () { lamb.pathExists("a")(void 0); }).toThrow(); - - expect(function () { lamb.pathExistsIn(null, "a"); }).toThrow(); - expect(function () { lamb.pathExistsIn(void 0, "a"); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.pathExists("a")(value)).toBe(false); - expect(lamb.pathExistsIn(value, "a")).toBe(false); - }); - - expect(lamb.pathExists("lastIndex")(/foo/)).toBe(true); - expect(lamb.pathExistsIn(/foo/, "lastIndex")).toBe(true); - }); - }); - - describe("pathSatisfies", function () { - var obj = { - a: 2, - b: { - a: 3, - b: [4, 5], - c: "foo", - e: {a: 45, b: void 0} - }, - "c.d": {"e.f": 6}, - c: {a: -0, b: NaN} - }; - - obj.b.d = Array(3); - obj.b.d[1] = 99; - - Object.defineProperty(obj, "e", {value: 10}); - obj.f = Object.create({}, {g: {value: 20}}); - - var isValue = lamb.curry(lamb.is); - var isDefined = lamb.not(lamb.isUndefined); - - it("should verify if the provided path satisfies a predicate in the given object", function () { - expect(lamb.pathSatisfies(isValue(2), "a")(obj)).toBe(true); - expect(lamb.pathSatisfies(isValue(3), "b.a")(obj)).toBe(true); - expect(lamb.pathSatisfies(Array.isArray, "b.b")(obj)).toBe(true); - expect(lamb.pathSatisfies(isValue(99), "z")(obj)).toBe(false); - expect(lamb.pathSatisfies(isDefined, "a.z")(obj)).toBe(false); - expect(lamb.pathSatisfies(isDefined, "b.a.z")(obj)).toBe(false); - }); - - it("should pass an `undefined` value to the predicate if the path doesn't exist", function () { - var isDefinedCheck = function (v) { - expect(arguments.length).toBe(1); - expect(v).toBeUndefined(); - - return !lamb.isUndefined(v); - }; - - expect(lamb.pathSatisfies(isDefinedCheck, "a.z")(obj)).toBe(false); - expect(lamb.pathSatisfies(isDefinedCheck, "b.a.z")(obj)).toBe(false); - }); - - it("should apply the function's calling context to the predicate", function () { - var validator = { - minAllowedValue: 3, - maxAllowedValue: 10, - hasValidValue: lamb.pathSatisfies(function (value) { - return value >= this.minAllowedValue && value <= this.maxAllowedValue; - }, "b.a") - }; - - expect(validator.hasValidValue(obj)).toBe(true); - expect(validator.hasValidValue({b: {a: 1}})).toBe(false); - }); - - it("should be able to check paths with non-enumerable properties", function () { - expect(lamb.pathSatisfies(isValue(10), "e")(obj)).toBe(true); - expect(lamb.pathSatisfies(isValue(20), "f.g")(obj)).toBe(true); - }); - - it("should be able to check arrays and array-like objects", function () { - expect(lamb.pathSatisfies(isValue(4), "b.b.0")(obj)).toBe(true); - expect(lamb.pathSatisfies(isValue("f"), "b.c.0")(obj)).toBe(true); - }); - - it("should allow negative indexes in paths", function () { - expect(lamb.pathSatisfies(isValue(5), "b.b.-1")(obj)).toBe(true); - expect(lamb.pathSatisfies(isValue("f"), "b.c.-3")(obj)).toBe(true); - }); - - it("should pass an `undefined` value to the predicate for unassigned or deleted indexes in sparse arrays", function () { - expect(lamb.pathSatisfies(lamb.isUndefined, "b.d.0")(obj)).toBe(true); - expect(lamb.pathSatisfies(lamb.isUndefined, "b.d.-3")(obj)).toBe(true); - - /* eslint-disable no-sparse-arrays */ - expect(lamb.pathSatisfies(lamb.isUndefined, "1")([1, , 3])).toBe(true); - expect(lamb.pathSatisfies(lamb.isUndefined, "-2")([1, , 3])).toBe(true); - /* eslint-enable no-sparse-arrays */ - }); - - it("should be able to check objects nested in arrays", function () { - var o = { - data: [ - {id: 1, value: 10}, - {id: 2, value: 20}, - {id: 3, value: 30} - ] - }; - - expect(lamb.pathSatisfies(isValue(20), "data.1.value")(o)).toBe(true); - expect(lamb.pathSatisfies(isValue(30), "data.-1.value")(o)).toBe(true); - }); - - it("should give priority to object keys over array-like indexes when a negative index is encountered", function () { - var o = {a: ["abc", new String("def"), "ghi"]}; - - o.a["-1"] = "foo"; - o.a[1]["-2"] = "bar"; - Object.defineProperty(o.a, "-2", {value: 99}); - - expect(lamb.pathSatisfies(isValue("foo"), "a.-1")(o)).toBe(true); - expect(lamb.pathSatisfies(isValue("bar"), "a.1.-2")(o)).toBe(true); - expect(lamb.pathSatisfies(isValue(99), "a.-2")(o)).toBe(true); - }); - - it("should accept a custom path separator", function () { - expect(lamb.pathSatisfies(isValue(4), "b->b->0", "->")(obj)).toBe(true); - expect(lamb.pathSatisfies(isValue(6), "c.d/e.f", "/")(obj)).toBe(true); - }); - - it("should accept integers as paths containing a single key", function () { - expect(lamb.pathSatisfies(isValue(2), 1)([1, 2])).toBe(true); - expect(lamb.pathSatisfies(isValue(2), -1)([1, 2])).toBe(true); - expect(lamb.pathSatisfies(isValue("a"), 1)({1: "a"})).toBe(true); - }); - - it("should convert other values for the `path` parameter to string", function () { - var values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var testObj = lamb.make(nonStringsAsStrings, values); - - nonStrings.forEach(function (key, idx) { - expect(lamb.pathSatisfies(isValue(values[idx]), key, "_")(testObj)).toBe(true); - }); - - var fooObj = {a: 2, 1: {5: 3}}; - - expect(lamb.pathSatisfies(isValue(3), 1.5)(fooObj)).toBe(true); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.pathSatisfies()).toThrow(); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an object", function () { - expect(function () { lamb.pathSatisfies(isValue(99), "a")(null); }).toThrow(); - expect(function () { lamb.pathSatisfies(isValue(99), "a")(void 0); }).toThrow(); - }); - - it("should convert to object every other value", function () { - wannabeEmptyObjects.forEach(function (value) { - expect(lamb.pathSatisfies(isDefined, "a")(value)).toBe(false); - }); - - expect(lamb.pathSatisfies(isDefined, "lastIndex")(/foo/)).toBe(true); - }); - }); - - describe("Object validation", function () { - var persons = [ - {name: "Jane", surname: "Doe", age: 12, city: "New York", email: "jane@doe"}, - {name: "John", surname: "Doe", age: 40, city: "London", email: "john@doe"}, - {name: "Mario", surname: "Rossi", age: 18, city: "Rome", email: "mario@rossi.it"}, - {name: "Paolo", surname: "Bianchi", age: 15, city: "Amsterdam", email: "paolo@bianchi.nl"} - ]; - - persons[0].login = {"user.name": "", password: "jane", passwordConfirm: "janE"}; - - var isAdult = function (age) { return age >= 18; }; - var isRequired = function (v) { return v.length > 0; }; - var isValidMail = function (mail) { - // eslint-disable-next-line max-len - return /^[A-Za-z0-9](([_.-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([.-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$/.test(mail); - }; - var isValidPassword = function (pwd) { return pwd.length > 5; }; - - var mailCheck = lamb.checker(isValidMail, "Must have a valid mail", ["email"]); - var ageCheck = lamb.checker(isAdult, "Must be at least 18 years old", ["age"]); - var pwdCheck = lamb.checker( - isValidPassword, - "Passwords must have at least six characters", - ["login.password"] - ); - var userNameCheck = lamb.checker( - isRequired, - "The username is a required field", - ["login/user.name"], - "/" - ); - var pwdConfirmCheck = lamb.checker( - lamb.areSame, - "Passwords don't match", - ["login.password", "login.passwordConfirm"] - ); - - describe("checker", function () { - it("should build a function to validate the given properties of an object", function () { - expect(mailCheck(persons[0])).toEqual(["Must have a valid mail", ["email"]]); - expect(mailCheck(persons[2])).toEqual([]); - }); - - it("should accept string paths as property names", function () { - expect(pwdCheck(persons[0])).toEqual( - ["Passwords must have at least six characters", ["login.password"]] - ); - expect(userNameCheck(persons[0])).toEqual( - ["The username is a required field", ["login/user.name"]] - ); - }); - - it("should be possible to make a checker involving more than one property", function () { - expect(pwdConfirmCheck(persons[0])).toEqual( - ["Passwords don't match", ["login.password", "login.passwordConfirm"]] - ); - }); - - it("should treat \"truthy\" and \"falsy\" values returned by predicates as booleans", function () { - var isEven = function (n) { return n % 2 === 0; }; - var hasEvens = function (array) { return ~lamb.findIndex(array, isEven); }; - var isVowel = function (char) { return ~"aeiouAEIOU".indexOf(char); }; - var o = {a: [1, 3, 5, 6, 7], b: [1, 3, 5, 7], c: "a", d: "b"}; - - var checker1 = lamb.checker(hasEvens, "error", ["a"]); - var checker2 = lamb.checker(hasEvens, "error", ["b"]); - var checker3 = lamb.checker(isVowel, "error", ["c"]); - var checker4 = lamb.checker(isVowel, "error", ["d"]); - - expect(checker1(o)).toEqual([]); - expect(checker2(o)).toEqual(["error", ["b"]]); - expect(checker3(o)).toEqual([]); - expect(checker4(o)).toEqual(["error", ["d"]]); - }); - }); - - describe("validate", function () { - it("should validate an object with the given set of checkers", function () { - expect(lamb.validate(persons[0], [mailCheck, ageCheck])).toEqual([ - ["Must have a valid mail", ["email"]], - ["Must be at least 18 years old", ["age"]] - ]); - expect(lamb.validate(persons[1], [mailCheck, ageCheck])).toEqual([ - ["Must have a valid mail", ["email"]] - ]); - expect(lamb.validate(persons[2], [mailCheck, ageCheck])).toEqual([]); - }); - }); - - describe("validateWith", function () { - it("should build a validator to be reused with different objects", function () { - var personValidator = lamb.validateWith([mailCheck, ageCheck]); - - expect(persons.map(personValidator)).toEqual([ - [ - ["Must have a valid mail", ["email"]], - ["Must be at least 18 years old", ["age"]] - ], - [ - ["Must have a valid mail", ["email"]] - ], - [], - [ - ["Must be at least 18 years old", ["age"]] - ] - ]); - }); - }); - }); -}); diff --git a/test/spec/sortSpec.js b/test/spec/sortSpec.js deleted file mode 100644 index 121f548..0000000 --- a/test/spec/sortSpec.js +++ /dev/null @@ -1,393 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; -var sparseArrayEquality = commons.equalities.sparseArrayEquality; - -var nonFunctions = commons.vars.nonFunctions; -var wannabeEmptyArrays = commons.vars.wannabeEmptyArrays; - -describe("lamb.sort", function () { - var descSorter = lamb.sorterDesc(); - - beforeEach(function () { - jasmine.addCustomEqualityTester(sparseArrayEquality); - }); - - describe("sort / sortWith", function () { - var persons = [ - {name: "John", surname: "Doe"}, - {name: "Mario", surname: "Rossi"}, - {name: "John", surname: "Moe"}, - {name: "Jane", surname: "Foe"} - ]; - - var personsByNameAsc = [ - {name: "Jane", surname: "Foe"}, - {name: "John", surname: "Doe"}, - {name: "John", surname: "Moe"}, - {name: "Mario", surname: "Rossi"} - ]; - - // eslint-disable-next-line id-length - var personsByNameAscSurnameDesc = [ - {name: "Jane", surname: "Foe"}, - {name: "John", surname: "Moe"}, - {name: "John", surname: "Doe"}, - {name: "Mario", surname: "Rossi"} - ]; - - var personsByNameReversed = personsByNameAsc.slice().reverse(); - - var numbers = [6, -0, 4, 5, 7, 4, 3, 4, 0, 1, 2]; - - var mixed = ["1", NaN, null, "10", null, {}, 1, void 0, NaN, false, [], "20", "15", -100]; - var mixedAsNumbersAsc = [-100, null, null, false, [], "1", 1, "10", "15", "20", NaN, {}, void 0, NaN]; - var mixedAsNumbersDesc = mixedAsNumbersAsc.slice().reverse(); - - var nameAsc = lamb.sorter(lamb.getKey("name")); - var nameDesc = lamb.sorterDesc(lamb.getKey("name")); - var surnameDesc = lamb.sorterDesc(lamb.getKey("surname")); - - it("should build a sorted copy of the provided array-like object", function () { - var sortedNumbersA = lamb.sort(numbers, [descSorter]); - var sortedNumbersB = lamb.sortWith([descSorter])(numbers); - var numbersResult = [7, 6, 5, 4, 4, 4, 3, 2, 1, 0, -0]; - - expect(sortedNumbersA).toEqual(numbersResult); - expect(sortedNumbersA).not.toBe(numbers); - expect(sortedNumbersB).toEqual(numbersResult); - expect(sortedNumbersB).not.toBe(numbers); - }); - - it("should perform a stable ascending sort", function () { - var myPersonsA = lamb.sort(persons, [nameAsc]); - var myPersonsB = lamb.sortWith([nameAsc])(persons); - - expect(myPersonsA).toEqual(personsByNameAsc); - expect(myPersonsB).toEqual(personsByNameAsc); - }); - - it("should perform descending sort as the reverse of an ascending one", function () { - var myPersonsA = lamb.sort(persons, [nameDesc]); - var myPersonsB = lamb.sortWith([nameDesc])(persons); - var mixedDescA = lamb.sort(mixed, [lamb.sorterDesc(Number)]); - var mixedDescB = lamb.sortWith([lamb.sorterDesc(Number)])(mixed); - - expect(myPersonsA).toEqual(personsByNameReversed); - expect(myPersonsB).toEqual(personsByNameReversed); - expect(mixedDescA).toEqual(mixedAsNumbersDesc); - expect(mixedDescB).toEqual(mixedAsNumbersDesc); - }); - - it("should be able to perform multi sorting with the specified criteria", function () { - var myPersonsA = lamb.sort(persons, [nameAsc, surnameDesc]); - var myPersonsB = lamb.sortWith([nameAsc, surnameDesc])(persons); - - expect(myPersonsA).toEqual(personsByNameAscSurnameDesc); - expect(myPersonsB).toEqual(personsByNameAscSurnameDesc); - }); - - it("should automatically build a default sorting criterion if supplied only with a reader", function () { - var stringNumbers = ["2", "1", "10", "5"]; - var stringNumbersAsc = ["1", "2", "5", "10"]; - var mixedAsObject = [NaN, null, null, {}, void 0, NaN, -100, false, [], "1", 1, "10", "15", "20"]; - - expect(lamb.sort(stringNumbers, [Number])).toEqual(stringNumbersAsc); - expect(lamb.sortWith([Number])(stringNumbers)).toEqual(stringNumbersAsc); - expect(lamb.sort(mixed, [Number])).toEqual(mixedAsNumbersAsc); - expect(lamb.sortWith([Number])(mixed)).toEqual(mixedAsNumbersAsc); - expect(lamb.sort(mixed, [Object])).toEqual(mixedAsObject); - expect(lamb.sortWith([Object])(mixed)).toEqual(mixedAsObject); - }); - - it("should treat values as strings if different types are received by the default comparer", function () { - expect(lamb.sort(mixed)).toEqual([ - [], -100, "1", 1, "10", "15", "20", NaN, NaN, false, null, null, {}, void 0 - ]); - }); - - it("should be able to use custom comparers", function () { - var localeSorter = new Intl.Collator("it"); - var localeSorterDesc = lamb.sorterDesc(lamb.identity, localeSorter.compare); - var chars = ["a", "è", "à", "é", "c", "b", "e"]; - var charsAsc = ["a", "à", "b", "c", "e", "é", "è"]; - var charsDesc = charsAsc.concat().reverse(); - - expect(lamb.sort(chars, [localeSorter])).toEqual(charsAsc); - expect(lamb.sortWith([localeSorter])(chars)).toEqual(charsAsc); - expect(lamb.sort(chars, [localeSorterDesc])).toEqual(charsDesc); - expect(lamb.sortWith([localeSorterDesc])(chars)).toEqual(charsDesc); - }); - - it("should use a default ascending sorter if no sorters are supplied", function () { - var sortedNumbersA = lamb.sort(numbers); - var sortedNumbersB = lamb.sortWith()(numbers); - var numbersResult = [-0, 0, 1, 2, 3, 4, 4, 4, 5, 6, 7]; - var stringResult = ["a", "b", "c", "d"]; - - expect(sortedNumbersA).toEqual(numbersResult); - expect(sortedNumbersB).toEqual(numbersResult); - expect(lamb.sort("cadb")).toEqual(stringResult); - expect(lamb.sortWith()("cadb")).toEqual(stringResult); - }); - - it("should use a default ascending sorter if the sorters parameter isn't an array or is an empty array", function () { - var numbersResult = [-0, 0, 1, 2, 3, 4, 4, 4, 5, 6, 7]; - var stringResult = ["a", "b", "c", "d"]; - - expect(lamb.sort(numbers, [])).toEqual(numbersResult); - expect(lamb.sortWith([])(numbers)).toEqual(numbersResult); - expect(lamb.sort("cadb", [])).toEqual(stringResult); - expect(lamb.sortWith([])("cadb")).toEqual(stringResult); - - nonFunctions.forEach(function (value) { - expect(lamb.sort(numbers, value)).toEqual(numbersResult); - expect(lamb.sortWith(value)(numbers)).toEqual(numbersResult); - expect(lamb.sort("cadb", value)).toEqual(stringResult); - expect(lamb.sortWith(value)("cadb")).toEqual(stringResult); - }); - }); - - it("should treat non-function values received as sorters as ascending sorters", function () { - var numbersResult = [-0, 0, 1, 2, 3, 4, 4, 4, 5, 6, 7]; - var numbersResult2 = [7, 6, 5, 4, 4, 4, 3, 2, 1, -0, 0]; // a descending sort with zeroes swapped - - nonFunctions.forEach(function (value) { - expect(lamb.sort(numbers, [value])).toEqual(numbersResult); - expect(lamb.sort(numbers, [descSorter, value])).toEqual(numbersResult2); - expect(lamb.sortWith([value])(numbers)).toEqual(numbersResult); - expect(lamb.sortWith([descSorter, value])(numbers)).toEqual(numbersResult2); - }); - }); - - it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { - var arr = ["b", , "c", void 0, "a"]; // eslint-disable-line no-sparse-arrays - var result = ["a", "b", "c", void 0, void 0]; - - expect(lamb.sort(arr, [String])).toEqual(result); - expect(lamb.sortWith([String])(arr)).toEqual(result); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () { lamb.sort(null); }).toThrow(); - expect(function () { lamb.sort(void 0); }).toThrow(); - expect(function () { lamb.sortWith()(null); }).toThrow(); - expect(function () { lamb.sortWith()(void 0); }).toThrow(); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.sort).toThrow(); - expect(lamb.sortWith()).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.sort(value)).toEqual([]); - expect(lamb.sortWith()(value)).toEqual([]); - }); - }); - }); - - describe("sortedInsert", function () { - // eslint-disable-next-line id-length - var personsByCaseInsensitiveNameAsc = [ - {name: "jane", surname: "doe"}, - {name: "John", surname: "Doe"}, - {name: "john", surname: "doe"}, - {name: "John", surname: "Moe"}, - {name: "Mario", surname: "Rossi"} - ]; - - var toLowerCase = lamb.invoker("toLowerCase"); - var getLowerCaseName = lamb.compose(toLowerCase, lamb.getKey("name")); - - it("should insert an element in a copy of a sorted array respecting the order", function () { - var expectedResult = [ - {name: "jane", surname: "doe"}, - {name: "John", surname: "Doe"}, - {name: "john", surname: "doe"}, - {name: "John", surname: "Moe"}, - {name: "marco", surname: "Rossi"}, - {name: "Mario", surname: "Rossi"} - ]; - - var result = lamb.sortedInsert( - personsByCaseInsensitiveNameAsc, - {name: "marco", surname: "Rossi"}, - [lamb.sorter(getLowerCaseName)] - ); - - expect(result).toEqual(expectedResult); - expect(result).not.toBe(personsByCaseInsensitiveNameAsc); - expect(personsByCaseInsensitiveNameAsc.length).toBe(5); - - // references are not broken - expect(personsByCaseInsensitiveNameAsc[0]).toBe(result[0]); - }); - - it("should be able to insert an element in a multi-sorted array", function () { - var expectedResult = [ - {name: "jane", surname: "doe"}, - {name: "John", surname: "Doe"}, - {name: "john", surname: "doe"}, - {name: "John", surname: "Foe"}, - {name: "John", surname: "Moe"}, - {name: "Mario", surname: "Rossi"} - ]; - - var getLowerCaseSurname = lamb.compose(toLowerCase, lamb.getKey("surname")); - - var result = lamb.sortedInsert( - personsByCaseInsensitiveNameAsc, - {name: "John", surname: "Foe"}, - [getLowerCaseName, getLowerCaseSurname] - ); - - expect(result).toEqual(expectedResult); - expect(result).not.toBe(personsByCaseInsensitiveNameAsc); - expect(personsByCaseInsensitiveNameAsc.length).toBe(5); - - // references are not broken - expect(personsByCaseInsensitiveNameAsc[0]).toBe(result[0]); - }); - - it("should allow inserting in a descending sorted array", function () { - expect(lamb.sortedInsert([3, 2, 1], 1.5, [descSorter])).toEqual([3, 2, 1.5, 1]); - expect(lamb.sortedInsert([3, 2, 1], 2, [descSorter])).toEqual([3, 2, 2, 1]); - }); - - it("should be able to insert values at the beginning and at the end of the array", function () { - var arr = [1, 2, 3]; - - expect(lamb.sortedInsert(arr, 0)).toEqual([0, 1, 2, 3]); - expect(lamb.sortedInsert(arr, 4)).toEqual([1, 2, 3, 4]); - }); - - it("should accept an empty list", function () { - expect(lamb.sortedInsert([], 1)).toEqual([1]); - }); - - it("should accept array-like objects", function () { - var s = "abdefg"; - var result = ["a", "b", "c", "d", "e", "f", "g"]; - - expect(lamb.sortedInsert(s, "c", [lamb.sorter()])).toEqual(result); - }); - - it("should automatically build a default sorting criterion if supplied only with a reader", function () { - expect(lamb.sortedInsert([1, 2, 3], "2.5", [Number])).toEqual([1, 2, "2.5", 3]); - }); - - it("should use a default ascending sorter if no sorters are supplied", function () { - expect(lamb.sortedInsert([1, 2, 3], 2.5)).toEqual([1, 2, 2.5, 3]); - }); - - it("should use a default ascending sorter if any of the received criteria isn't a function or a Sorter", function () { - nonFunctions.forEach(function (value) { - expect(lamb.sortedInsert([1, 2, 3], 2.5, [value])).toEqual([1, 2, 2.5, 3]); - }); - }); - - it("should return an array copy of the array-like if there is no element to insert", function () { - expect(lamb.sortedInsert([1, 2, 3])).toEqual([1, 2, 3]); - expect(lamb.sortedInsert("abc")).toEqual(["a", "b", "c"]); - }); - - it("should allow to insert `nil` values if the value is passed explicitly", function () { - expect(lamb.sortedInsert([1, 2, 3], null)).toEqual([1, 2, 3, null]); - expect(lamb.sortedInsert([1, 2, 3], void 0)).toEqual([1, 2, 3, void 0]); - }); - - it("should consider deleted or unassigned indexes in sparse arrays as `undefined` values", function () { - // eslint-disable-next-line comma-spacing, no-sparse-arrays - var arr = ["a", "b", "c", , ,]; - var result = ["a", "b", "c", void 0, void 0, "z"]; - - expect(lamb.sortedInsert(arr, "z", String)).toEqual(result); - }); - - it("should throw an exception if supplied with `null` or `undefined` instead of an array-like", function () { - expect(function () {lamb.sortedInsert(null, 99); }).toThrow(); - expect(function () {lamb.sortedInsert(void 0, 99); }).toThrow(); - }); - - it("should throw an exception if called without arguments", function () { - expect(lamb.sortedInsert).toThrow(); - }); - - it("should treat every other value as an empty array", function () { - wannabeEmptyArrays.forEach(function (value) { - expect(lamb.sortedInsert(value, 99)).toEqual([99]); - }); - }); - }); - - describe("sorter / sorterDesc", function () { - var myComparer = jasmine.createSpy().and.returnValue("foo"); - var myReader = jasmine.createSpy().and.callFake(lamb.getKey("a")); - var foo = {a: 1}; - var bar = {a: 2}; - - afterEach(function () { - myComparer.calls.reset(); - myReader.calls.reset(); - }); - - it("should build a sorting criterion", function () { - var sorterAsc = lamb.sorter(); - var sorterDesc = lamb.sorterDesc(); - - expect(sorterAsc.isDescending).toBe(false); - expect(sorterDesc.isDescending).toBe(true); - expect(typeof sorterAsc.compare).toBe("function"); - expect(typeof sorterDesc.compare).toBe("function"); - expect(sorterAsc.compare.length).toBe(2); - expect(sorterDesc.compare.length).toBe(2); - expect(sorterAsc.compare("a", "b")).toBe(-1); - expect(sorterDesc.compare("a", "b")).toBe(-1); - }); - - it("should use a custom comparer if supplied with one", function () { - expect(lamb.sorter(null, myComparer).compare(foo, bar)).toBe("foo"); - }); - - it("should use a custom reader if supplied with one", function () { - lamb.sorter(myReader, myComparer).compare(foo, bar); - - expect(myReader).toHaveBeenCalledTimes(2); - expect(myReader.calls.argsFor(0)[0]).toBe(foo); - expect(myReader.calls.argsFor(1)[0]).toBe(bar); - expect(myComparer).toHaveBeenCalledTimes(1); - expect(myComparer).toHaveBeenCalledWith(1, 2); - }); - - it("should pass values directly to the comparer if there's no reader function or if the reader is the identity function", function () { - lamb.sorter(lamb.identity, myComparer).compare(foo, bar); - lamb.sorterDesc(null, myComparer).compare(foo, bar); - - expect(myComparer).toHaveBeenCalledTimes(2); - expect(myComparer.calls.argsFor(0)[0]).toBe(foo); - expect(myComparer.calls.argsFor(0)[1]).toBe(bar); - expect(myComparer.calls.argsFor(1)[0]).toBe(foo); - expect(myComparer.calls.argsFor(1)[1]).toBe(bar); - }); - - it("should build a default sorting criterion if the comparer isn't a function", function () { - nonFunctions.forEach(function (value) { - var sorterAsc = lamb.sorter(lamb.identity, value); - var sorterDesc = lamb.sorterDesc(lamb.identity, value); - - expect(sorterAsc.isDescending).toBe(false); - expect(sorterDesc.isDescending).toBe(true); - expect(typeof sorterAsc.compare).toBe("function"); - expect(typeof sorterDesc.compare).toBe("function"); - expect(sorterAsc.compare.length).toBe(2); - expect(sorterDesc.compare.length).toBe(2); - expect(sorterAsc.compare("a", "b")).toBe(-1); - expect(sorterDesc.compare("a", "b")).toBe(-1); - }); - }); - }); -}); diff --git a/test/spec/stringSpec.js b/test/spec/stringSpec.js deleted file mode 100644 index 628891a..0000000 --- a/test/spec/stringSpec.js +++ /dev/null @@ -1,216 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var zeroesAsIntegers = commons.vars.zeroesAsIntegers; - -describe("lamb.string", function () { - describe("padLeft / padRight", function () { - var source = "foo"; - - it("should pad a string to the given length with the provided char", function () { - expect(lamb.padLeft(source, "-", 5)).toBe("--foo"); - expect(lamb.padLeft("", "-", 5)).toBe("-----"); - expect(lamb.padRight(source, "-", 5)).toBe("foo--"); - expect(lamb.padRight("", "-", 5)).toBe("-----"); - }); - - it("should return a copy of the original string if the given length is lesser or equal to the string's length or if it's not a number", function () { - expect(lamb.padLeft(source, "-", 0)).toBe("foo"); - expect(lamb.padLeft(source, "-", -1)).toBe("foo"); - expect(lamb.padLeft(source, "-", 3)).toBe("foo"); - expect(lamb.padRight(source, "-", 0)).toBe("foo"); - expect(lamb.padRight(source, "-", -1)).toBe("foo"); - expect(lamb.padRight(source, "-", 3)).toBe("foo"); - - zeroesAsIntegers.forEach(function (value) { - expect(lamb.padLeft(source, "-", value)).toBe("foo"); - expect(lamb.padRight(source, "-", value)).toBe("foo"); - }); - }); - - it("should use only the first character of the string passed as padding char", function () { - expect(lamb.padLeft(source, "ab", 7)).toBe("aaaafoo"); - expect(lamb.padRight(source, "ab", 7)).toBe("fooaaaa"); - }); - - it("should not do any padding if the padding char is an empty string", function () { - expect(lamb.padLeft(source, "", 5)).toBe("foo"); - expect(lamb.padRight(source, "", 5)).toBe("foo"); - }); - - it("should convert to string values received as padding characters", function () { - var d = new Date(); - var dChar = String(d)[0]; - var values = [null, void 0, [7, 8], /foo/, 1, function () {}, NaN, true, d, {a: 2}]; - var resultsA = [ - "nnfoo", "uufoo", "77foo", "//foo", "11foo", "fffoo", - "NNfoo", "ttfoo", dChar + dChar + "foo", "[[foo" - ]; - var resultsB = [ - "foonn", "foouu", "foo77", "foo//", "foo11", "fooff", - "fooNN", "foott", "foo" + dChar + dChar, "foo[[" - ]; - - values.forEach(function (value, idx) { - expect(lamb.padLeft(source, value, 5)).toBe(resultsA[idx]); - expect(lamb.padRight(source, value, 5)).toBe(resultsB[idx]); - }); - }); - - it("should throw an exception when the source is `null` or `undefined` and when it's called without arguments", function () { - expect(function () { lamb.padLeft(null, "-", 10); }).toThrow(); - expect(function () { lamb.padLeft(void 0, "-", 10); }).toThrow(); - expect(function () { lamb.padRight(null, "-", 10); }).toThrow(); - expect(function () { lamb.padRight(void 0, "-", 10); }).toThrow(); - expect(lamb.padLeft).toThrow(); - expect(lamb.padRight).toThrow(); - }); - - it("should convert to string every other value passed as source", function () { - var d = new Date(); - var values = [{a: 2}, [1, 2], /foo/, 1, function () {}, NaN, true, d]; - var resultsA = [ - "[object Object]", "----1,2", "--/foo/", "------1", - "function () {}", "----NaN", "---true", String(d) - ]; - var resultsB = [ - "[object Object]", "1,2----", "/foo/--", "1------", - "function () {}", "NaN----", "true---", String(d) - ]; - - values.forEach(function (value, idx) { - expect(lamb.padLeft(value, "-", 7)).toBe(resultsA[idx]); - expect(lamb.padRight(value, "-", 7)).toBe(resultsB[idx]); - }); - }); - }); - - describe("repeat", function () { - var source = "foo"; - - it("should build a new string by repeating the source string the desired amount of times", function () { - expect(lamb.repeat(source, 0)).toBe(""); - expect(lamb.repeat(source, 1)).toBe("foo"); - expect(lamb.repeat(source, 3)).toBe("foofoofoo"); - }); - - it("should consider negative values in the `times` parameter as zeroes", function () { - expect(lamb.repeat(source, -1)).toBe(""); - expect(lamb.repeat(source, -99)).toBe(""); - }); - - it("should floor floating point values passed as the `times` parameter", function () { - expect(lamb.repeat(source, 3.2)).toBe("foofoofoo"); - expect(lamb.repeat(source, 3.8)).toBe("foofoofoo"); - }); - - it("should convert to integer every other value passed as the `times` parameter", function () { - zeroesAsIntegers.forEach(function (value) { - expect(lamb.repeat("foo", value)).toBe(""); - }); - - expect(lamb.repeat("foo")).toBe(""); - expect(lamb.repeat("foo", true)).toBe("foo"); - expect(lamb.repeat("foo", "2.5")).toBe("foofoo"); - expect(lamb.repeat("foo", new Date(3))).toBe("foofoofoo"); - }); - - it("should throw an exception when the source is `null` or `undefined` and when it's called without arguments", function () { - expect(function () { lamb.repeat(null, 10); }).toThrow(); - expect(function () { lamb.repeat(void 0, 10); }).toThrow(); - expect(lamb.repeat).toThrow(); - }); - - it("should convert to string every other value passed as source", function () { - var d = new Date(); - var values = [{a: 2}, [1, 2], /foo/, 1, function () {}, NaN, true, d]; - var results = [ - "[object Object][object Object][object Object]", - "1,21,21,2", - "/foo//foo//foo/", - "111", - "function () {}function () {}function () {}", - "NaNNaNNaN", - "truetruetrue", - String(d) + String(d) + String(d) - ]; - - values.forEach(function (value, idx) { - expect(lamb.repeat(value, 3)).toBe(results[idx]); - }); - }); - }); - - describe("testWith", function () { - it("should build a predicate accepting a string value to be tested against the pattern", function () { - var hasNumbersOnly = lamb.testWith(/^\d+$/); - var hasOnlyLowerCaseLetters = lamb.testWith(new RegExp("^[a-z]+$")); - - expect(hasNumbersOnly("123a")).toBe(false); - expect(hasNumbersOnly("123")).toBe(true); - expect(hasOnlyLowerCaseLetters("aBc")).toBe(false); - expect(hasOnlyLowerCaseLetters("abc")).toBe(true); - }); - - it("should be safe to reuse the function even when the \"global\" flag is used in the pattern", function () { - var re = /^\d+$/g; - var hasNumbersOnly = lamb.testWith(re); - - expect(hasNumbersOnly("123")).toBe(true); - expect(re.lastIndex).toBe(0); - expect(hasNumbersOnly("123")).toBe(true); - expect(re.lastIndex).toBe(0); - }); - - it("should convert every value passed as `pattern` to a RegExp", function () { - var d = new Date(); - var values = [null, void 0, {a: 2}, [1, 2], 1, function () {}, NaN, true, d]; - var matches = [ - "null", - "", - "[object Object]", - "1,2", - "1", - "function {}", - "NaN", - "true", - RegExp(d).source.replace(/[+()]/g, "") - ]; - - values.forEach(function (value, idx) { - expect(lamb.testWith(value)(matches[idx])).toBe(true); - }); - - expect(lamb.testWith()("")).toBe(true); - }); - - it("should throw an exception when the source string is `nil` or is missing", function () { - expect(function () { lamb.testWith(/asd/)(null); }).toThrow(); - expect(function () { lamb.testWith(/asd/)(void 0); }).toThrow(); - expect(lamb.testWith(/asd/)).toThrow(); - expect(lamb.testWith()).toThrow(); - }); - - it("should convert to string every other value passed as `source`", function () { - var d = new Date(); - var values = [{a: 2}, [1, 2], /foo/, 1, function () {}, NaN, true, d]; - var patterns = [ - /\[object Object\]/, - /1,2/, - /foo/, - /1/, - /function \(\) \{\}/, - /NaN/, - /true/, - RegExp(String(d).replace(/([+()])/g, "\\$1")) - ]; - - values.forEach(function (value, idx) { - expect(lamb.testWith(patterns[idx])(value)).toBe(true); - }); - }); - }); -}); diff --git a/test/spec/typeSpec.js b/test/spec/typeSpec.js deleted file mode 100644 index de073ba..0000000 --- a/test/spec/typeSpec.js +++ /dev/null @@ -1,142 +0,0 @@ -"use strict"; - -var commons = require("../commons.js"); - -var lamb = commons.lamb; - -var nonFunctions = commons.vars.nonFunctions; -var nonNils = commons.vars.nonNils; -var nonNulls = commons.vars.nonNulls; -var nonUndefineds = commons.vars.nonUndefineds; -var valuesList = commons.vars.valuesList; - -describe("lamb.type", function () { - describe("isInstanceOf", function () { - it("should build a predicate to check if an object is an instance of the given constructor", function () { - function SomeObjA () {} - function SomeObjB () {} - - var a = new SomeObjA(); - var b = new SomeObjB(); - - expect(lamb.isInstanceOf(SomeObjA)(a)).toBe(true); - expect(lamb.isInstanceOf(Object)(a)).toBe(true); - expect(lamb.isInstanceOf(SomeObjB)(a)).toBe(false); - - expect(lamb.isInstanceOf(SomeObjA)(b)).toBe(false); - - SomeObjB.prototype = new SomeObjA(); - var c = new SomeObjB(); - - expect(lamb.isInstanceOf(SomeObjA)(b)).toBe(false); - expect(lamb.isInstanceOf(SomeObjA)(c)).toBe(true); - - expect(lamb.isInstanceOf(Object)({})).toBe(true); - expect(lamb.isInstanceOf(Object)(Object.create(null))).toBe(false); - expect(lamb.isInstanceOf(String)("foo")).toBe(false); - expect(lamb.isInstanceOf(Object)("foo")).toBe(false); - expect(lamb.isInstanceOf(String)(new String("foo"))).toBe(true); - expect(lamb.isInstanceOf(Object)(new String("foo"))).toBe(true); - }); - - it("should build a predicate throwing an exception if the constructor isn't a function or is missing", function () { - nonFunctions.forEach(function (value) { - expect(lamb.isInstanceOf(value)).toThrow(); - }); - - expect(lamb.isInstanceOf()).toThrow(); - }); - }); - - describe("isNil", function () { - it("should verify if the given value is null or undefined", function () { - expect(lamb.isNil(null)).toBe(true); - expect(lamb.isNil(void 0)).toBe(true); - }); - - it("should return true if called without arguments", function () { - expect(lamb.isNil()).toBe(true); - }); - - it("should return false for every other value", function () { - nonNils.forEach(function (value) { - expect(lamb.isNil(value)).toBe(false); - }); - }); - }); - - describe("isNull", function () { - it("should verify if the given value is null", function () { - expect(lamb.isNull(null)).toBe(true); - }); - - it("should return false if called without arguments", function () { - expect(lamb.isNull()).toBe(false); - }); - - it("should return false for every other value", function () { - nonNulls.forEach(function (value) { - expect(lamb.isNull(value)).toBe(false); - }); - }); - }); - - describe("isType", function () { - it("should build a predicate that expects a value to check against the specified type", function () { - var isArray = lamb.isType("Array"); - var isFunction = lamb.isType("Function"); - - expect(isArray([1, 2, 3])).toBe(true); - expect(isFunction(function () {})).toBe(true); - expect(isArray({})).toBe(false); - expect(isFunction(new Date())).toBe(false); - }); - - it("should build a function returning false for every value if called without arguments", function () { - valuesList.forEach(function (value) { - expect(lamb.isType()(value)).toBe(false); - }); - }); - }); - - describe("isUndefined", function () { - it("should verify if the given value is undefined", function () { - expect(lamb.isUndefined(void 0)).toBe(true); - }); - - it("should return true if called without arguments", function () { - expect(lamb.isUndefined()).toBe(true); - }); - - it("should return false for every other value", function () { - nonUndefineds.forEach(function (value) { - expect(lamb.isUndefined(value)).toBe(false); - }); - }); - }); - - describe("type", function () { - it("should extract the \"type tag\" from the given value", function () { - var getArgsType = function () { return lamb.type(arguments); }; - - expect(getArgsType()).toBe("Arguments"); - expect(lamb.type(new getArgsType())).toBe("Object"); // eslint-disable-line new-cap - expect(lamb.type(void 0)).toBe("Undefined"); - expect(lamb.type(null)).toBe("Null"); - expect(lamb.type(NaN)).toBe("Number"); - expect(lamb.type(0)).toBe("Number"); - expect(lamb.type(new Number())).toBe("Number"); - expect(lamb.type("")).toBe("String"); - expect(lamb.type(new String("foo"))).toBe("String"); - expect(lamb.type(/a/)).toBe("RegExp"); - expect(lamb.type(new RegExp())).toBe("RegExp"); - expect(lamb.type(new Date())).toBe("Date"); - expect(lamb.type(function () {})).toBe("Function"); - expect(lamb.type(new Function())).toBe("Function"); - expect(lamb.type([1, 2, 3])).toBe("Array"); - expect(lamb.type(new Array())).toBe("Array"); - expect(lamb.type(Object.create(null))).toBe("Object"); - expect(lamb.type({})).toBe("Object"); - }); - }); -});