// Underscore-contrib (underscore.object.builders.js 0.3.0) // (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors // Underscore-contrib may be freely distributed under the MIT license. // Helpers // ------- // Create quick reference variables for speed access to core prototypes. var slice = Array.prototype.slice, concat = Array.prototype.concat; var existy = function(x) { return x != null; }; var truthy = function(x) { return (x !== false) && existy(x); }; var isAssociative = function(x) { return _.isArray(x) || _.isObject(x); }; var curry2 = function(fun) { return function(last) { return function(first) { return fun(first, last); }; }; }; // Mixing in the object builders // ---------------------------- _.mixin({ // Merges two or more objects starting with the left-most and // applying the keys right-word // {any:any}* -> {any:any} merge: function(/* objs */){ var dest = _.some(arguments) ? {} : null; if (truthy(dest)) { _.extend.apply(null, concat.call([dest], _.toArray(arguments))); } return dest; }, // Takes an object and another object of strings to strings where the second // object describes the key renaming to occur in the first object. renameKeys: function(obj, kobj) { return _.reduce(kobj, function(o, nu, old) { if (existy(obj[old])) { o[nu] = obj[old]; return o; } else return o; }, _.omit.apply(null, concat.call([obj], _.keys(kobj)))); }, // Snapshots an object deeply. Based on the version by // [Keith Devens](http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone) // until we can find a more efficient and robust way to do it. snapshot: function(obj) { if(obj == null || typeof(obj) != 'object') { return obj; } var temp = new obj.constructor(); for(var key in obj) { if (obj.hasOwnProperty(key)) { temp[key] = _.snapshot(obj[key]); } } return temp; }, // Updates the value at any depth in a nested object based on the // path described by the keys given. The function provided is supplied // the current value and is expected to return a value for use as the // new value. If no keys are provided, then the object itself is presented // to the given function. updatePath: function(obj, fun, ks, defaultValue) { if (!isAssociative(obj)) throw new TypeError("Attempted to update a non-associative object."); if (!existy(ks)) return fun(obj); var deepness = _.isArray(ks); var keys = deepness ? ks : [ks]; var ret = deepness ? _.snapshot(obj) : _.clone(obj); var lastKey = _.last(keys); var target = ret; _.each(_.initial(keys), function(key) { if (defaultValue && !_.has(target, key)) { target[key] = _.clone(defaultValue); } target = target[key]; }); target[lastKey] = fun(target[lastKey]); return ret; }, // Sets the value at any depth in a nested object based on the // path described by the keys given. setPath: function(obj, value, ks, defaultValue) { if (!existy(ks)) throw new TypeError("Attempted to set a property at a null path."); return _.updatePath(obj, function() { return value; }, ks, defaultValue); }, // Returns an object where each element of an array is keyed to // the number of times that it occurred in said array. frequencies: curry2(_.countBy)(_.identity) });