module.exports = Hash; var Traverse = require('traverse'); function Hash (hash, xs) { if (Array.isArray(hash) && Array.isArray(xs)) { var to = Math.min(hash.length, xs.length); var acc = {}; for (var i = 0; i < to; i++) { acc[hash[i]] = xs[i]; } return Hash(acc); } if (hash === undefined) return Hash({}); var self = { map : function (f) { var acc = { __proto__ : hash.__proto__ }; Object.keys(hash).forEach(function (key) { acc[key] = f.call(self, hash[key], key); }); return Hash(acc); }, forEach : function (f) { Object.keys(hash).forEach(function (key) { f.call(self, hash[key], key); }); return self; }, filter : function (f) { var acc = { __proto__ : hash.__proto__ }; Object.keys(hash).forEach(function (key) { if (f.call(self, hash[key], key)) { acc[key] = hash[key]; } }); return Hash(acc); }, detect : function (f) { for (var key in hash) { if (f.call(self, hash[key], key)) { return hash[key]; } } return undefined; }, reduce : function (f, acc) { var keys = Object.keys(hash); if (acc === undefined) acc = keys.shift(); keys.forEach(function (key) { acc = f.call(self, acc, hash[key], key); }); return acc; }, some : function (f) { for (var key in hash) { if (f.call(self, hash[key], key)) return true; } return false; }, update : function (obj) { if (arguments.length > 1) { self.updateAll([].slice.call(arguments)); } else { Object.keys(obj).forEach(function (key) { hash[key] = obj[key]; }); } return self; }, updateAll : function (xs) { xs.filter(Boolean).forEach(function (x) { self.update(x); }); return self; }, merge : function (obj) { if (arguments.length > 1) { return self.copy.updateAll([].slice.call(arguments)); } else { return self.copy.update(obj); } }, mergeAll : function (xs) { return self.copy.updateAll(xs); }, has : function (key) { // only operates on enumerables return Array.isArray(key) ? key.every(function (k) { return self.has(k) }) : self.keys.indexOf(key.toString()) >= 0; }, valuesAt : function (keys) { return Array.isArray(keys) ? keys.map(function (key) { return hash[key] }) : hash[keys] ; }, tap : function (f) { f.call(self, hash); return self; }, extract : function (keys) { var acc = {}; keys.forEach(function (key) { acc[key] = hash[key]; }); return Hash(acc); }, exclude : function (keys) { return self.filter(function (_, key) { return keys.indexOf(key) < 0 }); }, end : hash, items : hash }; var props = { keys : function () { return Object.keys(hash) }, values : function () { return Object.keys(hash).map(function (key) { return hash[key] }); }, compact : function () { return self.filter(function (x) { return x !== undefined }); }, clone : function () { return Hash(Hash.clone(hash)) }, copy : function () { return Hash(Hash.copy(hash)) }, length : function () { return Object.keys(hash).length }, size : function () { return self.length } }; if (Object.defineProperty) { // es5-shim has an Object.defineProperty but it throws for getters try { for (var key in props) { Object.defineProperty(self, key, { get : props[key] }); } } catch (err) { for (var key in props) { if (key !== 'clone' && key !== 'copy' && key !== 'compact') { // ^ those keys use Hash() so can't call them without // a stack overflow self[key] = props[key](); } } } } else if (self.__defineGetter__) { for (var key in props) { self.__defineGetter__(key, props[key]); } } else { // non-lazy version for browsers that suck >_< for (var key in props) { self[key] = props[key](); } } return self; }; // deep copy Hash.clone = function (ref) { return Traverse.clone(ref); }; // shallow copy Hash.copy = function (ref) { var hash = { __proto__ : ref.__proto__ }; Object.keys(ref).forEach(function (key) { hash[key] = ref[key]; }); return hash; }; Hash.map = function (ref, f) { return Hash(ref).map(f).items; }; Hash.forEach = function (ref, f) { Hash(ref).forEach(f); }; Hash.filter = function (ref, f) { return Hash(ref).filter(f).items; }; Hash.detect = function (ref, f) { return Hash(ref).detect(f); }; Hash.reduce = function (ref, f, acc) { return Hash(ref).reduce(f, acc); }; Hash.some = function (ref, f) { return Hash(ref).some(f); }; Hash.update = function (a /*, b, c, ... */) { var args = Array.prototype.slice.call(arguments, 1); var hash = Hash(a); return hash.update.apply(hash, args).items; }; Hash.merge = function (a /*, b, c, ... */) { var args = Array.prototype.slice.call(arguments, 1); var hash = Hash(a); return hash.merge.apply(hash, args).items; }; Hash.has = function (ref, key) { return Hash(ref).has(key); }; Hash.valuesAt = function (ref, keys) { return Hash(ref).valuesAt(keys); }; Hash.tap = function (ref, f) { return Hash(ref).tap(f).items; }; Hash.extract = function (ref, keys) { return Hash(ref).extract(keys).items; }; Hash.exclude = function (ref, keys) { return Hash(ref).exclude(keys).items; }; Hash.concat = function (xs) { var hash = Hash({}); xs.forEach(function (x) { hash.update(x) }); return hash.items; }; Hash.zip = function (xs, ys) { return Hash(xs, ys).items; }; // .length is already defined for function prototypes Hash.size = function (ref) { return Hash(ref).size; }; Hash.compact = function (ref) { return Hash(ref).compact.items; };