(function () {
'use strict'
const root = this,
previous_xo = root.xo,
xo = {}
/**
* @namespace xo
* @version 2.3.0
*/
xo.VERSION = '3.0.0'
const id = x => x
const is = type => {
const fastTypes = ['undefined', 'boolean', 'number', 'string', 'symbol', 'function']
if (fastTypes.indexOf(type.toLowerCase()) >= 0) {
type = type.toLowerCase()
return test => typeof test === type
}
type = '[object ' + type + ']'
return test => Object.prototype.toString.call(test) === type
}
/**
* Type check for Boolean
*
* @example
* xo.isBoolean(true) // true
* xo.isBoolean('true') // false
*
* @function
* @name xo.isBoolean
* @param {*} test - The argument to be checked
* @return {Boolean}
*/
xo.isBoolean = is('Boolean')
/**
* Type check for Number
*
* @example
* xo.isNumber(42) // true
* xo.isNumber('true') // false
*
* @function
* @name xo.isNumber
* @param {*} test - The argument to be checked
* @return {Boolean}
*/
xo.isNumber = is('Number')
/**
* Type check for String
*
* @example
* xo.isString('true') // true
* xo.isString(true) // false
*
* @function
* @name xo.isString
* @param {*} test - The argument to be checked
* @return {Boolean}
*/
xo.isString = is('String')
/**
* Type check for Object
*
* @example
* xo.isObject({ a: true }) // true
* xo.isObject(true) // false
*
* @function
* @name xo.isObject
* @param {*} test - The argument to be checked
* @return {Boolean}
*/
xo.isObject = is('Object')
/**
* Type check for Array
*
* @example
* xo.isArray([1, 2, 3]) // true
* xo.isArray(true) // false
*
* @function
* @name xo.isArray
* @param {*} test - The argument to be checked
* @return {Boolean}
*/
xo.isArray = is('Array')
/**
* Type check for Function
*
* @example
* xo.isFunction(function(){ return true }) // true
* xo.isFunction(true) // false
*
* @function
* @name xo.isFunction
* @param {*} test - The argument to be checked
* @return {Boolean}
*/
xo.isFunction = is('Function')
/**
* Allows users to avoid conflicts over the xo name
*
* @example
* const ox = xo.noConflict()
*
* @function
* @name xo.noConflict
* @return {Object}
*/
xo.noConflict = () => {
root.xo = previous_xo
return xo
}
/**
* Takes a function with zero or more arguments.
* Returns a function that can be invoked with the remaining arguments at a later time
*
* @example
* const greet = (greeting, name) => [greeting, name].join(' ')
*
* const sayHi = xo.partial(greet, 'Hi')
* sayHi('Bob') // "Hi Bob"
*
* @function
* @name xo.partial
* @param {Function} fn - Partially apply this function prefilling some arguments
* @param {*} [args] - Initial arguments that the partially applied function will be applied to.
* @return {Function}
*/
xo.partial = function (fn, ...initialArgs) {
return function (...args) {
return fn.apply(this, initialArgs.concat(args))
}
}
/**
* Takes a function with zero or more arguments.
* Returns a function that can be invoked with remaining arguments at a later time
*
* @example
* const greet = (greeting, name) => [greeting, name].join(' ')
*
* const sayHi = xo.curry(greet, 'Hi')
* sayHi('Bob') // "Hi Bob"
*
* @function
* @name xo.curry
* @param {Function} fn - Partially apply this function prefilling some arguments
* @param {*} [args] - Initial arguments that the partially applied function will be applied to.
* @return {Function}
*/
xo.curry = function (fn, ...initialArgs) {
return function (...suppliedArgs) {
const args = initialArgs.concat(suppliedArgs)
return (args.length < fn.length) ? xo.curry.apply(this, [fn].concat(args)) : fn.apply(this, args)
}
}
/**
* Takes an array and a function.
* Returns an array that is the result of having the function applied
* to each term of the supplied array.
*
* @example
* const arr = [1, 2, 3, 4]
* const square = a => a * a
*
* const out = xo.map(square, arr) // => [1, 4, 9, 16]
*
* @function
* @name xo.map
* @param {Function} callback - The function to be applied to each term of the supplied array
* @param {Array} collection - The array that we're operating on
* @return {Array}
*/
xo.map = (callback, collection) => collection.map(callback)
/**
* Takes an array, an initial value and a function.
* Returns a single value that is the result of having the function applied
* to each term of the supplied array.
*
* @example
* const arr = [1, 2, 3, 4]
* const sum = (a, b) => a + b
*
* const out = xo.reduce(sum, 0, arr) // => 10
*
* @function
* @name xo.reduce
* @param {Function} callback - The function to be applied to each term of the supplied array
* @param {*} initialValue - The value to use as the first argument to the first call of the callback
* @param {Array} collection - The array that we're operating on
* @return {*}
*/
xo.reduce = (callback, initialValue, collection) => collection.reduce(callback, initialValue)
const combine = fns => arg => xo.reduce((a, fn) => fn(a), arg, fns)
/**
* Takes functions and returns a function.
* The returned function when invoked will invoke each function
* that was supplied as an argument to compose passing the result of
* each invocation as the argument to the next function. The functions
* supplied as arguments are invoked in reverse order, with the last
* argument being called first
*
* @example
* const increment = a => a + 1
* const square = a => a * a
*
* const squarePlusOne = xo.compose(increment, square)
* squarePlusOne(3) // 10
*
* @function
* @name xo.compose
* @param {Function} [fns] - The functions to be composed
* @return {Function}
*/
xo.compose = (...fns) => combine(fns.reverse())
/**
* Takes functions and returns a function.
* The returned function when invoked will invoke each function
* that was supplied as an argument to compose passing the result of
* each invocation as the argument to the next function
*
* @example
* const increment = a => a + 1
* const square = a => a * a
*
* const plusOneSquare = xo.pipe(increment, square)
* plusOneSquare(3) // 16
*
* @function
* @name xo.pipe
* @param {Function} [fns] - The functions to be composed
* @return {Function}
*/
xo.pipe = (...fns) => combine(fns)
/**
* Takes an n-dimensional nested array.
* Returns a flattened 1-dimensional array.
*
* @example
* const test = [0, 1, [2, 3], [4, [5, 6]], 7, [8, [9]]]
* xo.flatten(test) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
*
* @function
* @name xo.flatten
* @param {Array} arr - The array that will be recursively flattened
* @return {Array}
*/
xo.flatten = arr => {
let output = []
arr.forEach(val => output = output.concat(Array.isArray(val) ? xo.flatten(val) : val))
return output
}
/**
* Takes an array and a predicate function.
* Returns an array with only those terms that pass the predicate
*
* @example
* function compare(id, obj) {
* return id === obj.id
* }
* const objArr = [
* { name: 'a', id: '001' },
* { name: 'b', id: '003' },
* { name: 'c', id: '003' },
* { name: 'd', id: '004' }
* ]
* xo.filter(xo.curry(compare, '003'), objArr) // [{ name: 'b', id: '003'},{name: 'c', id: '003'}]
*
* @function
* @name xo.filter
* @param {Function} predicate - The function against which each element of the array will be tested
* @param {Array} arr - The array containing the elements to test
* @return {Array}
*/
xo.filter = (predicate, arr) => {
let result = []
arr.forEach(item => predicate(item) && result.push(item))
return result
}
/**
* Takes an array.
* Returns an array with all falsy values removed
*
* @example
* const test = [1, , false, 2, 3, false]
* xo.compact(test) // [1, 2, 3]
*
* @function
* @name xo.compact
* @param {Array} arr - The array containing the elements to test
* @return {Array}
*/
xo.compact = arr => {
return xo.filter(id, arr)
}
/**
* Takes an array and a predicate function.
* Returns the index of the first term that passes the predicate
*
* @example
* function compare(id, obj) {
* return id === obj.id
* }
* const objArr = [
* { name: 'a', id: '001' },
* { name: 'b', id: '002' },
* { name: 'c', id: '003' },
* { name: 'd', id: '004' }
* ]
* xo.findIndex(xo.curry(compare, '003'), objArr) // 2
*
* @function
* @name xo.findIndex
* @param {Function} predicate - The function against which each element of the array will be tested
* @param {Array} arr - The array containing the elements to test
* @return {Number}
*/
xo.findIndex = (predicate, arr) => arr.findIndex(predicate)
/**
* Takes an object and a predicate function.
* Returns the key of the first term that passes the predicate
*
* @example
* function compare(id, obj) {
* return id === obj.id
* }
* const obj = {
* hello: { name: 'a', id: '001' },
* goodbye: { name: 'b', id: '002' },
* yes: { name: 'c', id: '003' },
* no: { name: 'd', id: '004' }
* }
* xo.findKey(xo.curry(compare, '003'), obj) // yes
*
* @function
* @name xo.findKey
* @param {Function} predicate - The function against which each property of the object will be tested
* @param {Object} obj - The object containing the elements to test
* @return {String}
*/
xo.findKey = (predicate, obj) => {
for (let prop in obj) {
if (obj.hasOwnProperty(prop) && predicate(obj[prop])) {
return prop
}
}
return null
}
/**
* Takes an object or an array and a predicate function.
* Returns the value of the first term that passes the predicate
*
* @example
* function compare(id, obj) {
* return id === obj.id
* }
* const obj = {
* hello: { name: 'a', id: '001' },
* goodbye: { name: 'b', id: '002' },
* yes: { name: 'c', id: '003' },
* no: { name: 'd', id: '004' }
* }
* xo.find(xo.curry(compare, '003'), obj) // {yes: { name: 'a', id: '003' }}
*
* @function
* @name xo.find
* @param {Function} predicate - The function against which each property of the collection will be tested
* @param {Object} {Array} collection - The object or array containing the elements to test
* @return {String} {Number}
*/
xo.find = (predicate, collection) => {
if (xo.isArray(collection)) {
return collection[xo.findIndex(predicate, collection)]
}
if (xo.isObject(collection)) {
return collection[xo.findKey(predicate, collection)]
}
}
/**
* Takes a function and returns a function.
* Invoking the returned function will return cached results if the same
* arguments have been provided during previous invocations.
*
* @example
* const upper = str => str.toUpperCase()
*
* const memoUpper = xo.memoize(upper)
* memoUpper('foo') // "FOO"
* memoUpper('foo') // "FOO" (cached version)
*
* @function
* @name xo.memoize
* @param {Function} fn - The (expensive) function that will have it's return values cached
* @return {Function}
*/
xo.memoize = fn => {
let cache = {}
return function () {
const key = JSON.stringify(arguments)
return cache[key] || (cache[key] = fn.apply(this, arguments))
}
}
/**
* Takes a function and returns a function.
* The returned function will not be called if supplied with null
* or undefined arguments
*
* @example
* const sum = (a, b) => a + b
*
* const maybeSum = xo.maybe(sum)
* maybeSum(2, 3) // 5
* maybeSum(null, 3) // doesn't invoke sum
*
* @function
* @name xo.maybe
* @param {Function} fn - The function to be invoked
* @return {Function}
*/
xo.maybe = fn => {
return function (...args) {
if (!args.length || args.some(val => val == null)) {
return
}
return fn.apply(this, args)
}
}
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = xo // jshint ignore:line
}
exports.xo = xo
} else {
root.xo = xo
}
}).call(this)