rambda
Version:
Lightweight faster alternative to Ramda
1,053 lines (947 loc) • 24.9 kB
JavaScript
/**Used as the `TypeError` message for "Functions" methods. */
const FUNC_ERROR_TEXT = 'Expected a function'
/**Used to stand-in for `undefined` hash values. */
const HASH_UNDEFINED = '__lodash_hash_undefined__'
/**Used as references for various `Number` constants. */
const INFINITY = 1 / 0,
MAX_SAFE_INTEGER = 9007199254740991
/**`Object#toString` result references. */
const funcTag = '[object Function]',
genTag = '[object GeneratorFunction]',
symbolTag = '[object Symbol]'
/**Used to match property names within property paths. */
const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
reIsPlainProp = /^\w*$/,
reLeadingDot = /^\./,
rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g
/**
* Used to match `RegExp`
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
*/
const reRegExpChar = /[\\^$.*+?()[\]{}|]/g
/**Used to match backslashes in property paths. */
const reEscapeChar = /\\(\\)?/g
/**Used to detect host constructors (Safari). */
const reIsHostCtor = /^\[object .+?Constructor\]$/
/**Used to detect unsigned integer values. */
const reIsUint = /^(?:0|[1-9]\d*)$/
/**Detect free variable `global` from Node.js. */
const freeGlobal =
typeof global === 'object' && global && global.Object === Object && global
/**Detect free variable `self`. */
const freeSelf =
typeof self === 'object' && self && self.Object === Object && self
/**Used as a reference to the global object. */
const root = freeGlobal || freeSelf || Function('return this')()
/**
* Gets the value at `key` of `object`.
*
* @private
* @param {Object} [object] The object to query.
* @param {string} key The key of the property to get.
* @returns {*} Returns the property value.
*/
function getValue(object, key){
return object == null ? undefined : object[ key ]
}
/**
* Checks if `value` is a host object in IE < 9.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
*/
function isHostObject(value){
//Many host objects are `Object` objects that can coerce to strings
//despite having improperly defined `toString` methods.
let result = false
if (value != null && typeof value.toString !== 'function'){
try {
result = Boolean(String(value))
} catch (e){}
}
return result
}
/**Used for built-in method references. */
const arrayProto = Array.prototype,
funcProto = Function.prototype,
objectProto = Object.prototype
/**Used to detect overreaching core-js shims. */
const coreJsData = root[ '__core-js_shared__' ]
/**Used to detect methods masquerading as native. */
const maskSrcKey = (function (){
const uid = (/[^.]+$/).exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '')
return uid ? 'Symbol(src)_1.' + uid : ''
})()
/**Used to resolve the decompiled source of functions. */
const funcToString = funcProto.toString
/**Used to check objects for own properties. */
const { hasOwnProperty } = objectProto
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
const objectToString = objectProto.toString
/**Used to detect if a method is native. */
const reIsNative = RegExp('^' +
funcToString
.call(hasOwnProperty)
.replace(reRegExpChar, '\\$&')
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,
'$1.*?') +
'$')
/**Built-in value references. */
const { Symbol } = root,
{ splice } = arrayProto
/*Built-in method references that are verified to be native. */
const Map = getNative(root, 'Map'),
nativeCreate = getNative(Object, 'create')
/**Used to convert symbols to primitives and strings. */
const symbolProto = Symbol ? Symbol.prototype : undefined,
symbolToString = symbolProto ? symbolProto.toString : undefined
/**
* Creates a hash object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function Hash(entries){
let index = -1,
length = entries ? entries.length : 0
this.clear()
while (++index < length){
const entry = entries[ index ]
this.set(entry[ 0 ], entry[ 1 ])
}
}
/**
* Removes all key-value entries from the hash.
*
* @private
* @name clear
* @memberOf Hash
*/
function hashClear(){
this.__data__ = nativeCreate ? nativeCreate(null) : {}
}
/**
* Removes `key` and its value from the hash.
*
* @private
* @name delete
* @memberOf Hash
* @param {Object} hash The hash to modify.
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function hashDelete(key){
return this.has(key) && delete this.__data__[ key ]
}
/**
* Gets the hash value for `key`.
*
* @private
* @name get
* @memberOf Hash
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function hashGet(key){
const data = this.__data__
if (nativeCreate){
const result = data[ key ]
return result === HASH_UNDEFINED ? undefined : result
}
return hasOwnProperty.call(data, key) ? data[ key ] : undefined
}
/**
* Checks if a hash value for `key` exists.
*
* @private
* @name has
* @memberOf Hash
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function hashHas(key){
const data = this.__data__
return nativeCreate ?
data[ key ] !== undefined :
hasOwnProperty.call(data, key)
}
/**
* Sets the hash `key` to `value`.
*
* @private
* @name set
* @memberOf Hash
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the hash instance.
*/
function hashSet(key, value){
const data = this.__data__
data[ key ] = nativeCreate && value === undefined ? HASH_UNDEFINED : value
return this
}
//Add methods to `Hash`.
Hash.prototype.clear = hashClear
Hash.prototype.delete = hashDelete
Hash.prototype.get = hashGet
Hash.prototype.has = hashHas
Hash.prototype.set = hashSet
/**
* Creates an list cache object.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function ListCache(entries){
let index = -1
const length = entries ? entries.length : 0
this.clear()
while (++index < length){
const entry = entries[ index ]
this.set(entry[ 0 ], entry[ 1 ])
}
}
/**
* Removes all key-value entries from the list cache.
*
* @private
* @name clear
* @memberOf ListCache
*/
function listCacheClear(){
this.__data__ = []
}
/**
* Removes `key` and its value from the list cache.
*
* @private
* @name delete
* @memberOf ListCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function listCacheDelete(key){
const data = this.__data__,
index = assocIndexOf(data, key)
if (index < 0){
return false
}
const lastIndex = data.length - 1
if (index == lastIndex){
data.pop()
} else {
splice.call(
data, index, 1
)
}
return true
}
/**
* Gets the list cache value for `key`.
*
* @private
* @name get
* @memberOf ListCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function listCacheGet(key){
const data = this.__data__,
index = assocIndexOf(data, key)
return index < 0 ? undefined : data[ index ][ 1 ]
}
/**
* Checks if a list cache value for `key` exists.
*
* @private
* @name has
* @memberOf ListCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function listCacheHas(key){
return assocIndexOf(this.__data__, key) > -1
}
/**
* Sets the list cache `key` to `value`.
*
* @private
* @name set
* @memberOf ListCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the list cache instance.
*/
function listCacheSet(key, value){
const data = this.__data__,
index = assocIndexOf(data, key)
if (index < 0){
data.push([ key, value ])
} else {
data[ index ][ 1 ] = value
}
return this
}
//Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear
ListCache.prototype.delete = listCacheDelete
ListCache.prototype.get = listCacheGet
ListCache.prototype.has = listCacheHas
ListCache.prototype.set = listCacheSet
/**
* Creates a map cache object to store key-value pairs.
*
* @private
* @constructor
* @param {Array} [entries] The key-value pairs to cache.
*/
function MapCache(entries){
let index = -1
const length = entries ? entries.length : 0
this.clear()
while (++index < length){
const entry = entries[ index ]
this.set(entry[ 0 ], entry[ 1 ])
}
}
/**
* Removes all key-value entries from the map.
*
* @private
* @name clear
* @memberOf MapCache
*/
function mapCacheClear(){
this.__data__ = {
hash : new Hash(),
map : new (Map || ListCache)(),
string : new Hash(),
}
}
/**
* Removes `key` and its value from the map.
*
* @private
* @name delete
* @memberOf MapCache
* @param {string} key The key of the value to remove.
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
*/
function mapCacheDelete(key){
return getMapData(this, key).delete(key)
}
/**
* Gets the map value for `key`.
*
* @private
* @name get
* @memberOf MapCache
* @param {string} key The key of the value to get.
* @returns {*} Returns the entry value.
*/
function mapCacheGet(key){
return getMapData(this, key).get(key)
}
/**
* Checks if a map value for `key` exists.
*
* @private
* @name has
* @memberOf MapCache
* @param {string} key The key of the entry to check.
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
*/
function mapCacheHas(key){
return getMapData(this, key).has(key)
}
/**
* Sets the map `key` to `value`.
*
* @private
* @name set
* @memberOf MapCache
* @param {string} key The key of the value to set.
* @param {*} value The value to set.
* @returns {Object} Returns the map cache instance.
*/
function mapCacheSet(key, value){
getMapData(this, key).set(key, value)
return this
}
//Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear
MapCache.prototype.delete = mapCacheDelete
MapCache.prototype.get = mapCacheGet
MapCache.prototype.has = mapCacheHas
MapCache.prototype.set = mapCacheSet
/**
* Assigns `value` to `key` of `object` if the existing value is not equivalent
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* for equality comparisons.
*
* @private
* @param {Object} object The object to modify.
* @param {string} key The key of the property to assign.
* @param {*} value The value to assign.
*/
function assignValue(
object, key, value
){
const objValue = object[ key ]
if (
!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
value === undefined && !(key in object)
){
object[ key ] = value
}
}
/**
* Gets the index at which the `key` is found in `array` of key-value pairs.
*
* @private
* @param {Array} array The array to inspect.
* @param {*} key The key to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
*/
function assocIndexOf(array, key){
let { length } = array
while (length--){
if (eq(array[ length ][ 0 ], key)){
return length
}
}
return -1
}
/**
* The base implementation of `_.isNative` without bad shim checks.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a native function,
* else `false`.
*/
function baseIsNative(value){
if (!isObject(value) || isMasked(value)){
return false
}
const pattern =
isFunction(value) || isHostObject(value) ? reIsNative : reIsHostCtor
return pattern.test(toSource(value))
}
/**
* The base implementation of `_.set`.
*
* @private
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @param {Function} [customizer] The function to customize path creation.
* @returns {Object} Returns `object`.
*/
function baseSet(
object, path, value, customizer
){
if (!isObject(object)){
return object
}
path = isKey(path, object) ? [ path ] : castPath(path)
let index = -1
let nested = object
const { length } = path
const lastIndex = length - 1
while (nested != null && ++index < length){
let key = toKey(path[ index ]),
newValue = value
if (index != lastIndex){
const objValue = nested[ key ]
newValue = customizer ? customizer(
objValue, key, nested
) : undefined
if (newValue === undefined){
newValue = isObject(objValue) ?
objValue :
isIndex(path[ index + 1 ]) ?
[] :
{}
}
}
assignValue(
nested, key, newValue
)
nested = nested[ key ]
}
return object
}
/**
* The base implementation of `_.toString` which doesn't convert nullish
* values to empty strings.
*
* @private
* @param {*} value The value to process.
* @returns {string} Returns the string.
*/
function baseToString(value){
//Exit early for strings to avoid a performance hit in some environments.
if (typeof value === 'string'){
return value
}
if (isSymbol(value)){
return symbolToString ? symbolToString.call(value) : ''
}
const result = String(value)
return result == '0' && 1 / value == -INFINITY ? '-0' : result
}
/**
* Casts `value` to a path array if it's not one.
*
* @private
* @param {*} value The value to inspect.
* @returns {Array} Returns the cast property path array.
*/
function castPath(value){
return isArray(value) ? value : stringToPath(value)
}
/**
* Gets the data for `map`.
*
* @private
* @param {Object} map The map to query.
* @param {string} key The reference key.
* @returns {*} Returns the map data.
*/
function getMapData(map, key){
const data = map.__data__
return isKeyable(key) ?
data[ typeof key === 'string' ? 'string' : 'hash' ] :
data.map
}
/**
* Gets the native function at `key` of `object`.
*
* @private
* @param {Object} object The object to query.
* @param {string} key The key of the method to get.
* @returns {*} Returns the function if it's native, else `undefined`.
*/
function getNative(object, key){
const value = getValue(object, key)
return baseIsNative(value) ? value : undefined
}
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/
function isIndex(value, length){
length = length == null ? MAX_SAFE_INTEGER : length
return (
Boolean(length) &&
(typeof value === 'number' || reIsUint.test(value)) &&
value > -1 &&
value % 1 == 0 &&
value < length
)
}
/**
* Checks if `value` is a property name and not a property path.
*
* @private
* @param {*} value The value to check.
* @param {Object} [object] The object to query keys on.
* @returns {boolean} Returns `true` if `value` is a property name, else `false`.
*/
function isKey(value, object){
if (isArray(value)){
return false
}
const type = typeof value
if (
type == 'number' ||
type == 'symbol' ||
type == 'boolean' ||
value == null ||
isSymbol(value)
){
return true
}
return (
reIsPlainProp.test(value) ||
!reIsDeepProp.test(value) ||
object != null && value in Object(object)
)
}
/**
* Checks if `value` is suitable for use as unique object key.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
*/
function isKeyable(value){
const type = typeof value
return type == 'string' ||
type == 'number' ||
type == 'symbol' ||
type == 'boolean' ?
value !== '__proto__' :
value === null
}
/**
* Checks if `func` has its source masked.
*
* @private
* @param {Function} func The function to check.
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
*/
function isMasked(func){
return Boolean(maskSrcKey) && maskSrcKey in func
}
/**
* Converts `string` to a property path array.
*
* @private
* @param {string} string The string to convert.
* @returns {Array} Returns the property path array.
*/
var stringToPath = memoize(string => {
string = toString(string)
const result = []
if (reLeadingDot.test(string)){
result.push('')
}
string.replace(rePropName, (
match, number, quote, string
) => {
result.push(quote ? string.replace(reEscapeChar, '$1') : number || match)
})
return result
})
/**
* Converts `value` to a string key if it's not a string or symbol.
*
* @private
* @param {*} value The value to inspect.
* @returns {string|symbol} Returns the key.
*/
function toKey(value){
if (typeof value === 'string' || isSymbol(value)){
return value
}
const result = String(value)
return result == '0' && 1 / value == -INFINITY ? '-0' : result
}
/**
* Converts `func` to its source code.
*
* @private
* @param {Function} func The function to process.
* @returns {string} Returns the source code.
*/
function toSource(func){
if (func != null){
try {
return funcToString.call(func)
} catch (e){}
try {
return String(func)
} catch (e){}
}
return ''
}
/**
* Creates a function that memoizes the result of `func`. If `resolver` is
* provided, it determines the cache key for storing the result based on the
* arguments provided to the memoized function. By default, the first argument
* provided to the memoized function is used as the map cache key. The `func`
* is invoked with the `this` binding of the memoized function.
*
* **Note:** The cache is exposed as the `cache` property on the memoized
* function. Its creation may be customized by replacing the `_.memoize.Cache`
* constructor with one whose instances implement the
* [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
* method interface of `delete`, `get`, `has`, and `set`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to have its output memoized.
* @param {Function} [resolver] The function to resolve the cache key.
* @returns {Function} Returns the new memoized function.
* @example
*
* var object = { 'a': 1, 'b': 2 };
* var other = { 'c': 3, 'd': 4 };
*
* var values = _.memoize(_.values);
* values(object);
* // => [1, 2]
*
* values(other);
* // => [3, 4]
*
* object.a = 2;
* values(object);
* // => [1, 2]
*
* // Modify the result cache.
* values.cache.set(object, ['a', 'b']);
* values(object);
* // => ['a', 'b']
*
* // Replace `_.memoize.Cache`.
* _.memoize.Cache = WeakMap;
*/
function memoize(func, resolver){
if (
typeof func !== 'function' ||
resolver && typeof resolver !== 'function'
){
throw new TypeError(FUNC_ERROR_TEXT)
}
var memoized = function (){
const args = arguments,
key = resolver ? resolver.apply(this, args) : args[ 0 ],
{ cache } = memoized
if (cache.has(key)){
return cache.get(key)
}
const result = func.apply(this, args)
memoized.cache = cache.set(key, result)
return result
}
memoized.cache = new (memoize.Cache || MapCache)()
return memoized
}
//Assign cache to `_.memoize`.
memoize.Cache = MapCache
/**
* Performs a
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
* comparison between two values to determine if they are equivalent.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* var object = { 'a': 1 };
* var other = { 'a': 1 };
*
* _.eq(object, object);
* // => true
*
* _.eq(object, other);
* // => false
*
* _.eq('a', 'a');
* // => true
*
* _.eq('a', Object('a'));
* // => false
*
* _.eq(NaN, NaN);
* // => true
*/
function eq(value, other){
return value === other || value !== value && other !== other
}
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/
var { isArray } = Array
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/
function isFunction(value){
//The use of `Object#toString` avoids issues with the `typeof` operator
//in Safari 8-9 which returns 'object' for typed array and other constructors.
const tag = isObject(value) ? objectToString.call(value) : ''
return tag == funcTag || tag == genTag
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value){
const type = typeof value
return Boolean(value) && (type == 'object' || type == 'function')
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value){
return Boolean(value) && typeof value === 'object'
}
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* _.isSymbol(Symbol.iterator);
* // => true
*
* _.isSymbol('abc');
* // => false
*/
function isSymbol(value){
return (
typeof value === 'symbol' ||
isObjectLike(value) && objectToString.call(value) == symbolTag
)
}
/**
* Converts `value` to a string. An empty string is returned for `null`
* and `undefined` values. The sign of `-0` is preserved.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to process.
* @returns {string} Returns the string.
* @example
*
* _.toString(null);
* // => ''
*
* _.toString(-0);
* // => '-0'
*
* _.toString([1, 2, 3]);
* // => '1,2,3'
*/
function toString(value){
return value == null ? '' : baseToString(value)
}
/**
* Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
* it's created. Arrays are created for missing index properties while objects
* are created for all other missing properties. Use `_.setWith` to customize
* `path` creation.
*
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @since 3.7.0
* @category Object
* @param {Object} object The object to modify.
* @param {Array|string} path The path of the property to set.
* @param {*} value The value to set.
* @returns {Object} Returns `object`.
* @example
*
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
*
* _.set(object, 'a[0].b.c', 4);
* console.log(object.a[0].b.c);
* // => 4
*
* _.set(object, ['x', '0', 'y', 'z'], 5);
* console.log(object.x[0].y.z);
* // => 5
*/
export function set(
object, path, value
){
return object == null ? object : baseSet(
object, path, value
)
}