racer-util
Version:
Utility functions for racer
196 lines (171 loc) • 4.83 kB
JavaScript
module.exports = {
merge: merge
, deepCopy: deepCopy
, extract: extract
, deepEqual: deepEqual
, only: objectWithOnly
, filter: filter
, assign: assign
, matchingVal: matchingVal
};
function merge () {
var merged = {};
for (var i = 0, l = arguments.length; i < l; i++) {
var obj = arguments[i];
for (var k in obj) {
merged[k] = obj[k];
}
}
return merged;
}
// TODO Test this
function deepCopy (obj) {
if (obj === null) return null;
if (typeof obj === 'object') {
var copy;
if (Array.isArray(obj)) {
copy = [];
for (var i = obj.length; i--; ) copy[i] = deepCopy(obj[i]);
return copy;
}
copy = {}
for (var k in obj) copy[k] = deepCopy(obj[k]);
return copy;
}
return obj;
}
function extract (key, obj) {
return obj[key];
}
/**
* Modified from node's assert.js
*/
function deepEqual (actual, expected, ignore) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) return true;
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
if (actual instanceof Date && expected instanceof Date)
return actual.getTime() === expected.getTime();
if (typeof actual === 'function' && typeof expected === 'function')
return actual === expected || actual.toString() === expected.toString();
// 7.3. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
if (typeof actual !== 'object' && typeof expected !== 'object')
return actual === expected;
// 7.4. For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
if (ignore) {
var ignoreMap = {}
, i = ignore.length
while (i--) {
ignoreMap[ignore[i]] = true;
}
}
return objEquiv(actual, expected, ignoreMap);
}
/** Private Functions **/
/**
* Modified from node's assert.js
*/
function objEquiv (a, b, ignoreMap) {
var i, key, ka, kb;
if (a == null || b == null) return false;
// an identical 'prototype' property.
if (a.prototype !== b.prototype) return false;
//~~~I've managed to break Object.keys through screwy arguments passing.
// Converting to array solves the problem.
if (isArguments(a)) {
if (! isArguments(b)) return false;
a = pSlice.call(a);
b = pSlice.call(b);
return deepEqual(a, b);
}
try {
if (ignoreMap) {
ka = keysWithout(a, ignoreMap);
kb = keysWithout(b, ignoreMap);
} else {
ka = Object.keys(a);
kb = Object.keys(b);
}
} catch (e) {
// happens when one is a string literal and the other isn't
return false;
}
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length !== kb.length) return false;
// the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
i = ka.length;
while (i--) {
if (ka[i] !== kb[i]) return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
i = ka.length;
while (i--) {
key = ka[i];
if (! deepEqual(a[key], b[key])) return false;
}
return true;
}
function isArguments (obj) {
return toString.call(obj) === '[object Arguments]';
}
function objectWithOnly (obj, paths) {
var projectedDoc = {};
for (var i = 0, l = paths.length; i < l; i++) {
var path = paths[i];
assign(projectedDoc, path, lookup(path, obj));
}
return projectedDoc;
}
function filter (obj, fn) {
var filtered = {};
for (var k in obj) {
var curr = obj[k];
if (fn(curr, k)) filtered[k] = curr;
}
return filtered;
}
function assign (obj, path, val) {
var parts = path.split('.')
, lastIndex = parts.length - 1;
for (var i = 0, l = parts.length; i < l; i++) {
var prop = parts[i];
if(/^\d+$/.test(prop)) {
prop = parseInt(prop, 10);
}
if (i === lastIndex) {
obj[prop] = val;
} else {
obj = obj[prop] || (obj[prop] = {});
}
}
}
function lookup (path, obj) {
if (!obj) return;
if (path.indexOf('.') === -1) return obj[path];
var parts = path.split('.');
for (var i = 0, l = parts.length; i < l; i++) {
if (!obj) return obj;
var prop = parts[i];
obj = obj[prop];
}
return obj;
}
function matchingVal (obj, predicate) {
var val;
for (var k in obj) {
val = obj[k];
if (predicate(k, val)) return val;
}
}