node-vitals
Version:
Do more with less. A simple, high-performing, functional JavaScript library.
826 lines (741 loc) • 23.6 kB
JavaScript
/**
* -----------------------------------------------------------------------------
* VITALS METHOD: roll
* -----------------------------------------------------------------------------
* @section base
* @version 4.1.3
* @see [vitals.roll]{@link https://github.com/imaginate/vitals/wiki/vitals.roll}
*
* @author Adam Smith <adam@imaginate.life> (https://github.com/imaginate)
* @copyright 2017 Adam A Smith <adam@imaginate.life> (https://github.com/imaginate)
*
* Annotations:
* @see [JSDoc3](http://usejsdoc.org)
* @see [Closure Compiler JSDoc Syntax](https://developers.google.com/closure/compiler/docs/js-for-compiler)
*/
'use strict';
var newErrorMaker = require('./helpers/new-error-maker.js');
var own = require('./helpers/own.js');
var copy = require('./copy.js');
var _is = require('./helpers/is.js');
////////////////////////////////////////////////////////////////////////////////
// VITALS METHOD: roll
////////////////////////////////////////////////////////////////////////////////
var roll = (function rollPrivateScope() {
//////////////////////////////////////////////////////////
// PUBLIC METHODS
// - roll
// - roll.up
// - roll.down
//////////////////////////////////////////////////////////
/**
* A shortcut for deriving a result by iterating over object maps, arrays, or
* cycles.
*
* @public
* @param {*=} base - If defined it is the base value. Note that for number
* sources (i.e. cycles) a base is required.
* @param {!(Object|function|Array|number)} source - Details per type:
* - object source: Iterates over all properties in random order.
* - array source: Iterates over all indexed properties from 0 to length.
* - number source: Iterates over all cycles.
* @param {function(*=, *=, (string|number)=, !(Object|function)=)} iteratee -
* It has the optional params - previousValue, currentValue, key/index, and
* source. Note this method lazily clones the source based on the iteratee's
* [length property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length)
* (i.e. if you alter the source object within the iteratee ensure to define
* the iteratee's fourth param so you can safely assume all references to
* the source are its original values).
* @param {Object=} thisArg - If defined the iteratee is bound to this value.
* @return {*}
*/
function roll(base, source, iteratee, thisArg) {
/** @type {boolean} */
var hasBase;
if (arguments.length < 2) throw _error('No source or iteratee defined');
if (arguments.length === 2) {
iteratee = source;
source = base;
}
else if ( arguments.length === 3 && !_is.func(iteratee) ) {
thisArg = iteratee;
iteratee = source;
source = base;
}
else hasBase = true;
if ( !_is.func(iteratee) ) throw _error.type('iteratee');
if ( !_is.nil.un.obj(thisArg) ) throw _error.type('thisArg');
if ( _is.num(source) ) {
if (!hasBase) throw _error('No base defined');
return _rollCycle(base, source, iteratee, thisArg);
}
if ( !_is._obj(source) ) throw _error.type('source');
return _is._arr(source)
? hasBase
? _rollBaseArr(base, source, iteratee, thisArg)
: _rollArr(source, iteratee, thisArg)
: hasBase
? _rollBaseObj(base, source, iteratee, thisArg)
: _rollObj(source, iteratee, thisArg);
}
/**
* A shortcut for deriving a sum by iterating over object maps, arrays, or
* cycles.
*
* @public
* @param {*=} base - If defined it is the base value. Note that for number
* sources (i.e. cycles) a base is required.
* @param {!(Object|function|Array|number)} source - Details per type:
* - object source: Iterates over all properties in random order.
* - array source: Iterates over all indexed properties from 0 to length.
* - number source: Iterates over all cycles.
* @param {function(*=, (string|number)=, !(Object|function)=)} iteratee - It
* has the optional params - value, key/index, source. Note this method
* lazily clones the source based on the iteratee's [length property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length)
* (i.e. if you alter the source object within the iteratee ensure to define
* the iteratee's third param so you can safely assume all references to the
* source are its original values).
* @param {Object=} thisArg - If defined the iteratee is bound to this value.
* @return {*}
*/
roll.up = function rollUp(base, source, iteratee, thisArg) {
/** @type {boolean} */
var hasBase;
if (arguments.length < 2) throw _error('No source or iteratee defined','up');
if (arguments.length === 2) {
iteratee = source;
source = base;
}
else if ( arguments.length === 3 && !_is.func(iteratee) ) {
thisArg = iteratee;
iteratee = source;
source = base;
}
else hasBase = true;
if ( !_is.func(iteratee) ) throw _error.type('iteratee', 'up');
if ( !_is.nil.un.obj(thisArg) ) throw _error.type('thisArg', 'up');
if ( _is.num(source) ) {
if (!hasBase) throw _error('No base defined', 'up');
return _rollCycleUp(base, source, iteratee, thisArg);
}
if ( !_is._obj(source) ) throw _error.type('source', 'up');
return _is._arr(source)
? hasBase
? _rollBaseArrUp(base, source, iteratee, thisArg)
: _rollArrUp(source, iteratee, thisArg)
: hasBase
? _rollBaseObjUp(base, source, iteratee, thisArg)
: _rollObjUp(source, iteratee, thisArg);
};
/**
* A shortcut for deriving a difference by iterating over object maps, arrays,
* or cycles.
*
* @public
* @param {*=} base - If defined it is the base value. Note that for number
* sources (i.e. cycles) a base is required.
* @param {!(Object|function|Array|number)} source - Details per type:
* - object source: Iterates over all properties in random order.
* - array source: Iterates over all indexed properties from 0 to length.
* - number source: Iterates over all cycles.
* @param {function(*=, (string|number)=, !(Object|function)=)} iteratee - It
* has the optional params - value, key/index, source. Note this method
* lazily clones the source based on the iteratee's [length property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length)
* (i.e. if you alter the source object within the iteratee ensure to define
* the iteratee's third param so you can safely assume all references to the
* source are its original values).
* @param {Object=} thisArg - If defined the iteratee is bound to this value.
* @return {*}
*/
roll.down = function rollDown(base, source, iteratee, thisArg) {
/** @type {boolean} */
var hasBase;
if (arguments.length < 2) throw _error('No source or iteratee defined','down');
if (arguments.length === 2) {
iteratee = source;
source = base;
}
else if ( arguments.length === 3 && !_is.func(iteratee) ) {
thisArg = iteratee;
iteratee = source;
source = base;
}
else hasBase = true;
if ( !_is.func(iteratee) ) throw _error.type('iteratee', 'down');
if ( !_is.nil.un.obj(thisArg) ) throw _error.type('thisArg', 'down');
if ( _is.num(source) ) {
if (!hasBase) throw _error('No base defined', 'down');
return _rollCycleDown(base, source, iteratee, thisArg);
}
if ( !_is._obj(source) ) throw _error.type('source', 'down');
return _is._arr(source)
? hasBase
? _rollBaseArrDown(base, source, iteratee, thisArg)
: _rollArrDown(source, iteratee, thisArg)
: hasBase
? _rollBaseObjDown(base, source, iteratee, thisArg)
: _rollObjDown(source, iteratee, thisArg);
};
//////////////////////////////////////////////////////////
// PRIVATE METHODS - ROLL OBJ
//////////////////////////////////////////////////////////
/**
* @private
* @param {!(Object|function)} obj
* @param {function(*, *, string=, !(Object|function)=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollObj(obj, iteratee, thisArg) {
/** @type {*} */
var result;
/** @type {string} */
var key;
/** @type {boolean} */
var z;
obj = iteratee.length > 3 ? copy(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
switch (iteratee.length) {
case 0:
case 1:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result = iteratee(result);
else {
result = obj[key];
z = true;
}
}
}
break;
case 2:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result = iteratee(result, obj[key]);
else {
result = obj[key];
z = true;
}
}
}
break;
case 3:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result = iteratee(result, obj[key], key);
else {
result = obj[key];
z = true;
}
}
}
break;
default:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result = iteratee(result, obj[key], key, obj);
else {
result = obj[key];
z = true;
}
}
}
}
return result;
}
/**
* @private
* @param {*} result
* @param {!(Object|function)} obj
* @param {function(*, *, string=, !(Object|function)=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollBaseObj(result, obj, iteratee, thisArg) {
/** @type {string} */
var key;
obj = iteratee.length > 3 ? copy(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
switch (iteratee.length) {
case 0:
case 1:
for (key in obj) {
if ( own(obj, key) ) result = iteratee(result);
}
break;
case 2:
for (key in obj) {
if ( own(obj, key) ) result = iteratee(result, obj[key]);
}
break;
case 3:
for (key in obj) {
if ( own(obj, key) ) result = iteratee(result, obj[key], key);
}
break;
default:
for (key in obj) {
if ( own(obj, key) ) result = iteratee(result, obj[key], key, obj);
}
}
return result;
}
/**
* @private
* @param {!(Object|function)} obj
* @param {function(*, string=, !(Object|function)=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollObjUp(obj, iteratee, thisArg) {
/** @type {*} */
var result;
/** @type {string} */
var key;
/** @type {boolean} */
var z;
obj = iteratee.length > 2 ? copy(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
switch (iteratee.length) {
case 0:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result += iteratee();
else {
result = obj[key];
z = true;
}
}
}
break;
case 1:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result += iteratee(obj[key]);
else {
result = obj[key];
z = true;
}
}
}
break;
case 2:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result += iteratee(obj[key], key);
else {
result = obj[key];
z = true;
}
}
}
break;
default:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result += iteratee(obj[key], key, obj);
else {
result = obj[key];
z = true;
}
}
}
}
return result;
}
/**
* @private
* @param {*} result
* @param {!(Object|function)} obj
* @param {function(*, string=, !(Object|function)=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollBaseObjUp(result, obj, iteratee, thisArg) {
/** @type {string} */
var key;
obj = iteratee.length > 2 ? copy(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
switch (iteratee.length) {
case 0:
for (key in obj) {
if ( own(obj, key) ) result += iteratee();
}
break;
case 1:
for (key in obj) {
if ( own(obj, key) ) result += iteratee(obj[key]);
}
break;
case 2:
for (key in obj) {
if ( own(obj, key) ) result += iteratee(obj[key], key);
}
break;
default:
for (key in obj) {
if ( own(obj, key) ) result += iteratee(obj[key], key, obj);
}
}
return result;
}
/**
* @private
* @param {!(Object|function)} obj
* @param {function(*, string=, !(Object|function)=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollObjDown(obj, iteratee, thisArg) {
/** @type {*} */
var result;
/** @type {string} */
var key;
/** @type {boolean} */
var z;
obj = iteratee.length > 2 ? copy(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
switch (iteratee.length) {
case 0:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result -= iteratee();
else {
result = obj[key];
z = true;
}
}
}
break;
case 1:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result -= iteratee(obj[key]);
else {
result = obj[key];
z = true;
}
}
}
break;
case 2:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result -= iteratee(obj[key], key);
else {
result = obj[key];
z = true;
}
}
}
break;
default:
for (key in obj) {
if ( own(obj, key) ) {
if (z) result -= iteratee(obj[key], key, obj);
else {
result = obj[key];
z = true;
}
}
}
}
return result;
}
/**
* @private
* @param {*} result
* @param {!(Object|function)} obj
* @param {function(*, string=, !(Object|function)=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollBaseObjDown(result, obj, iteratee, thisArg) {
/** @type {string} */
var key;
obj = iteratee.length > 2 ? copy(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
switch (iteratee.length) {
case 0:
for (key in obj) {
if ( own(obj, key) ) result -= iteratee();
}
break;
case 1:
for (key in obj) {
if ( own(obj, key) ) result -= iteratee(obj[key]);
}
break;
case 2:
for (key in obj) {
if ( own(obj, key) ) result -= iteratee(obj[key], key);
}
break;
default:
for (key in obj) {
if ( own(obj, key) ) result -= iteratee(obj[key], key, obj);
}
}
return result;
}
//////////////////////////////////////////////////////////
// PRIVATE METHODS - ROLL ARR
//////////////////////////////////////////////////////////
/**
* @private
* @param {!(Object|function)} obj
* @param {function(*, *, number=, !Array=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollArr(obj, iteratee, thisArg) {
/** @type {*} */
var result;
/** @type {number} */
var len;
/** @type {number} */
var i;
obj = iteratee.length > 3 ? copy.arr(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
result = obj[0];
len = obj.length;
i = 0;
switch (iteratee.length) {
case 0:
case 1: while (++i < len) result = iteratee(result); break;
case 2: while (++i < len) result = iteratee(result, obj[i]); break;
case 3: while (++i < len) result = iteratee(result, obj[i], i); break;
default: while (++i < len) result = iteratee(result, obj[i], i, obj);
}
return result;
}
/**
* @private
* @param {*} result
* @param {!(Object|function)} obj
* @param {function(*, number=, !Array=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollBaseArr(result, obj, iteratee, thisArg) {
/** @type {number} */
var len;
/** @type {number} */
var i;
obj = iteratee.length > 3 ? copy.arr(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
len = obj.length;
i = -1;
switch (iteratee.length) {
case 0:
case 1: while (++i < len) result = iteratee(result); break;
case 2: while (++i < len) result = iteratee(result, obj[i]); break;
case 3: while (++i < len) result = iteratee(result, obj[i], i); break;
default: while (++i < len) result = iteratee(result, obj[i], i, obj);
}
return result;
}
/**
* @private
* @param {!(Object|function)} obj
* @param {function(*, *, number=, !Array=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollArrUp(obj, iteratee, thisArg) {
/** @type {*} */
var result;
/** @type {number} */
var len;
/** @type {number} */
var i;
obj = iteratee.length > 2 ? copy.arr(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
result = obj[0];
len = obj.length;
i = 0;
switch (iteratee.length) {
case 0: while (++i < len) result += iteratee(); break;
case 1: while (++i < len) result += iteratee(obj[i]); break;
case 2: while (++i < len) result += iteratee(obj[i], i); break;
default: while (++i < len) result += iteratee(obj[i], i, obj);
}
return result;
}
/**
* @private
* @param {*} result
* @param {!(Object|function)} obj
* @param {function(*, number=, !Array=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollBaseArrUp(result, obj, iteratee, thisArg) {
/** @type {number} */
var len;
/** @type {number} */
var i;
obj = iteratee.length > 2 ? copy.arr(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
len = obj.length;
i = -1;
switch (iteratee.length) {
case 0: while (++i < len) result += iteratee(); break;
case 1: while (++i < len) result += iteratee(obj[i]); break;
case 2: while (++i < len) result += iteratee(obj[i], i); break;
default: while (++i < len) result += iteratee(obj[i], i, obj);
}
return result;
}
/**
* @private
* @param {!(Object|function)} obj
* @param {function(*, *, number=, !Array=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollArrDown(obj, iteratee, thisArg) {
/** @type {*} */
var result;
/** @type {number} */
var len;
/** @type {number} */
var i;
obj = iteratee.length > 2 ? copy.arr(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
result = obj[0];
len = obj.length;
i = 0;
switch (iteratee.length) {
case 0: while (++i < len) result -= iteratee(); break;
case 1: while (++i < len) result -= iteratee(obj[i]); break;
case 2: while (++i < len) result -= iteratee(obj[i], i); break;
default: while (++i < len) result -= iteratee(obj[i], i, obj);
}
return result;
}
/**
* @private
* @param {*} result
* @param {!(Object|function)} obj
* @param {function(*, number=, !Array=)} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollBaseArrDown(result, obj, iteratee, thisArg) {
/** @type {number} */
var len;
/** @type {number} */
var i;
obj = iteratee.length > 2 ? copy.arr(obj) : obj;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
len = obj.length;
i = -1;
switch (iteratee.length) {
case 0: while (++i < len) result -= iteratee(); break;
case 1: while (++i < len) result -= iteratee(obj[i]); break;
case 2: while (++i < len) result -= iteratee(obj[i], i); break;
default: while (++i < len) result -= iteratee(obj[i], i, obj);
}
return result;
}
//////////////////////////////////////////////////////////
// PRIVATE METHODS - ROLL CYCLE
//////////////////////////////////////////////////////////
/**
* @private
* @param {*} result
* @param {number} count
* @param {function} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollCycle(result, count, iteratee, thisArg) {
/** @type {number} */
var i;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
if (iteratee.length > 1) {
i = 0;
while(count--) result = iteratee(result, i++);
}
else {
while(count--) result = iteratee(result);
}
return result;
}
/**
* @private
* @param {*} result
* @param {number} count
* @param {function} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollCycleUp(result, count, iteratee, thisArg) {
/** @type {number} */
var i;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
if (iteratee.length) {
i = 0;
while(count--) result += iteratee(i++);
}
else {
while(count--) result += iteratee();
}
return result;
}
/**
* @private
* @param {*} result
* @param {number} count
* @param {function} iteratee
* @param {Object=} thisArg
* @return {*}
*/
function _rollCycleDown(result, count, iteratee, thisArg) {
/** @type {number} */
var i;
iteratee = _is.undefined(thisArg) ? iteratee : _bind(iteratee, thisArg);
if (iteratee.length) {
i = 0;
while(count--) result -= iteratee(i++);
}
else {
while(count--) result -= iteratee();
}
return result;
}
//////////////////////////////////////////////////////////
// PRIVATE METHODS - GENERAL
//////////////////////////////////////////////////////////
/**
* @private
* @param {function} func
* @param {Object} thisArg
* @return {function}
*/
function _bind(func, thisArg) {
switch (func.length) {
case 0: return function iteratee() { return func.call(thisArg); };
case 1: return function iteratee(val) { return func.call(thisArg, val); };
case 2: return function iteratee(val1, val2) {
return func.call(thisArg, val1, val2);
};
case 3: return function iteratee(val1, val2, val3) {
return func.call(thisArg, val1, val2, val3);
};
}
return function iteratee(prev, curr, key, obj) {
return func.call(thisArg, prev, curr, key, obj);
};
}
/**
* @private
* @type {!ErrorAid}
*/
var _error = newErrorMaker('roll');
//////////////////////////////////////////////////////////
// END OF PRIVATE SCOPE FOR ROLL
return roll;
})();
module.exports = roll;