pax_global_header00006660000000000000000000000064121422627750014522gustar00rootroot0000000000000052 comment=34dfe43d41a4ae33a84d91bba0bd7bd3e44fecab LICENSE000066400000000000000000000020611214226277500120720ustar00rootroot00000000000000This software is released under the MIT license: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.markdown000066400000000000000000000077121214226277500135760ustar00rootroot00000000000000Hashish ======= Hashish is a node.js library for manipulating hash data structures. It is distilled from the finest that ruby, perl, and haskell have to offer by way of hash/map interfaces. Hashish provides a chaining interface, where you can do: var Hash = require('hashish'); Hash({ a : 1, b : 2, c : 3, d : 4 }) .map(function (x) { return x * 10 }) .filter(function (x) { return x < 30 }) .forEach(function (x, key) { console.log(key + ' => ' + x); }) ; Output: a => 10 b => 20 Some functions and attributes in the chaining interface are terminal, like `.items` or `.detect()`. They return values of their own instead of the chain context. Each function in the chainable interface is also attached to `Hash` in chainless form: var Hash = require('hashish'); var obj = { a : 1, b : 2, c : 3, d : 4 }; var mapped = Hash.map(obj, function (x) { return x * 10 }); console.dir(mapped); Output: { a: 10, b: 20, c: 30, d: 40 } In either case, the 'this' context of the function calls is the same object that the chained functions return, so you can make nested chains. Methods ======= forEach(cb) ----------- For each key/value in the hash, calls `cb(value, key)`. map(cb) ------- For each key/value in the hash, calls `cb(value, key)`. The return value of `cb` is the new value at `key` in the resulting hash. filter(cb) ---------- For each key/value in the hash, calls `cb(value, key)`. The resulting hash omits key/value pairs where `cb` returned a falsy value. detect(cb) ---------- Returns the first value in the hash for which `cb(value, key)` is non-falsy. Order of hashes is not well-defined so watch out for that. reduce(cb) ---------- Returns the accumulated value of a left-fold over the key/value pairs. some(cb) -------- Returns a boolean: whether or not `cb(value, key)` ever returned a non-falsy value. update(obj1, [obj2, obj3, ...]) ----------- Mutate the context hash, merging the key/value pairs from the passed objects and overwriting keys from the context hash if the current `obj` has keys of the same name. Falsy arguments are silently ignored. updateAll([ obj1, obj2, ... ]) ------------------------------ Like multi-argument `update()` but operate on an array directly. merge(obj1, [obj2, obj3, ...]) ---------- Merge the key/value pairs from the passed objects into the resultant hash without modifying the context hash. Falsy arguments are silently ignored. mergeAll([ obj1, obj2, ... ]) ------------------------------ Like multi-argument `merge()` but operate on an array directly. has(key) -------- Return whether the hash has a key, `key`. valuesAt(keys) -------------- Return an Array with the values at the keys from `keys`. tap(cb) ------- Call `cb` with the present raw hash. This function is chainable. extract(keys) ------------- Filter by including only those keys in `keys` in the resulting hash. exclude(keys) ------------- Filter by excluding those keys in `keys` in the resulting hash. Attributes ========== These are attributes in the chaining interface and functions in the `Hash.xxx` interface. keys ---- Return all the enumerable attribute keys in the hash. values ------ Return all the enumerable attribute values in the hash. compact ------- Filter out values which are `=== undefined`. clone ----- Make a deep copy of the hash. copy ---- Make a shallow copy of the hash. length ------ Return the number of key/value pairs in the hash. Note: use `Hash.size()` for non-chain mode. size ---- Alias for `length` since `Hash.length` is masked by `Function.prototype`. See Also ======== See also [creationix's pattern/hash](http://github.com/creationix/pattern), which does a similar thing except with hash inputs and array outputs. Installation ============ To install with [npm](http://github.com/isaacs/npm): npm install hashish To run the tests with [expresso](http://github.com/visionmedia/expresso): expresso examples/000077500000000000000000000000001214226277500127045ustar00rootroot00000000000000examples/chain.js000066400000000000000000000003531214226277500143250ustar00rootroot00000000000000var Hash = require('hashish'); Hash({ a : 1, b : 2, c : 3, d : 4 }) .map(function (x) { return x * 10 }) .filter(function (x) { return x < 30 }) .forEach(function (x, key) { console.log(key + ' => ' + x); }) ; examples/map.js000066400000000000000000000002371214226277500140210ustar00rootroot00000000000000var Hash = require('hashish'); var obj = { a : 1, b : 2, c : 3, d : 4 }; var mapped = Hash.map(obj, function (x) { return x * 10 }); console.dir(mapped); index.js000066400000000000000000000154251214226277500125420ustar00rootroot00000000000000module.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; }; package.json000066400000000000000000000013701214226277500133550ustar00rootroot00000000000000{ "name" : "hashish", "version" : "0.0.4", "description" : "Hash data structure manipulation functions", "main" : "./index.js", "repository" : { "type" : "git", "url" : "http://github.com/substack/node-hashish.git" }, "keywords": [ "hash", "object", "convenience", "manipulation", "data structure" ], "author" : { "name" : "James Halliday", "email" : "mail@substack.net", "url" : "http://substack.net" }, "dependencies" : { "traverse" : ">=0.2.4" }, "devDependencies" : { "expresso" : ">=0.6.0" }, "scripts" : { "test" : "expresso" }, "license" : "MIT", "engine" : ["node >=0.2.0"] } test/000077500000000000000000000000001214226277500120455ustar00rootroot00000000000000test/hash.js000066400000000000000000000161701214226277500133330ustar00rootroot00000000000000var Hash = require('hashish'); var assert = require('assert'); exports.map = function () { var ref = { a : 1, b : 2 }; var items = Hash(ref).map(function (v) { return v + 1 }).items; var hash = Hash.map(ref, function (v) { return v + 1 }); assert.deepEqual(ref, { a : 1, b : 2 }); assert.deepEqual(items, { a : 2, b : 3 }); assert.deepEqual(hash, { a : 2, b : 3 }); }; exports['cloned map'] = function () { var ref = { foo : [1,2], bar : [4,5] }; var hash = Hash(ref).clone.map( function (v) { v.unshift(v[0] - 1); return v } ).items; assert.deepEqual(ref.foo, [1,2]); assert.deepEqual(ref.bar, [4,5]); assert.deepEqual(hash.foo, [0,1,2]); assert.deepEqual(hash.bar, [3,4,5]); }; exports.forEach = function () { var ref = { a : 5, b : 2, c : 7, 1337 : 'leet' }; var xs = []; Hash(ref).forEach(function (x, i) { xs.push([ i, x ]); }); assert.eql( xs.map(function (x) { return x[0] }).sort(), [ '1337', 'a', 'b', 'c' ] ); assert.eql( xs.map(function (x) { return x[1] }).sort(), [ 2, 5, 7, 'leet' ] ); var ys = []; Hash.forEach(ref, function (x, i) { ys.push([ i, x ]); }); assert.eql(xs.sort(), ys.sort()); }; exports.filter_items = function () { var ref = { a : 5, b : 2, c : 7, 1337 : 'leet' }; var items = Hash(ref).filter(function (v, k) { return v > 5 || k > 5 }).items; var hash = Hash.filter(ref, function (v, k) { return v > 5 || k > 5 }); assert.deepEqual(items, { 1337 : 'leet', c : 7 }); assert.deepEqual(hash, { 1337 : 'leet', c : 7 }); assert.deepEqual(ref, { a : 5, b : 2, c : 7, 1337 : 'leet' }); assert.equal(Hash(ref).length, 4); }; exports.detect = function () { var h = { a : 5, b : 6, c : 7, d : 8 }; var hh = Hash(h); var gt6hh = hh.detect(function (x) { return x > 6 }); assert.ok(gt6hh == 7 || gt6hh == 8); var gt6h = Hash.detect(h, function (x) { return x > 6 }); assert.ok(gt6h == 7 || gt6h == 8); assert.equal(hh.detect(function (x) { return x > 100 }), undefined); }; exports.reduce = function () { var ref = { foo : [1,2], bar : [4,5] }; var sum1 = Hash(ref).reduce(function (acc, v) { return acc + v.length }, 0); assert.equal(sum1, 4); var sum2 = Hash.reduce(ref, function (acc, v) { return acc + v.length }, 0); assert.equal(sum2, 4); }; exports.some = function () { var h = { a : 5, b : 6, c : 7, d : 8 }; var hh = Hash(h); assert.ok(Hash.some(h, function (x) { return x > 7 })); assert.ok(Hash.some(h, function (x) { return x < 6 })); assert.ok(!Hash.some(h, function (x) { return x > 10 })); assert.ok(!Hash.some(h, function (x) { return x < 0 })); assert.ok(hh.some(function (x) { return x > 7 })); assert.ok(hh.some(function (x) { return x < 6 })); assert.ok(!hh.some(function (x) { return x > 10 })); assert.ok(!hh.some(function (x) { return x < 0 })); }; exports.update = function () { var ref = { a : 1, b : 2 }; var items = Hash(ref).clone.update({ c : 3, a : 0 }).items; assert.deepEqual(ref, { a : 1, b : 2 }); assert.deepEqual(items, { a : 0, b : 2, c : 3 }); var hash = Hash.update(ref, { c : 3, a : 0 }); assert.deepEqual(ref, hash); assert.deepEqual(hash, { a : 0, b : 2, c : 3 }); var ref2 = {a: 1}; var hash2 = Hash.update(ref2, { b: 2, c: 3 }, undefined, { d: 4 }); assert.deepEqual(ref2, { a: 1, b: 2, c: 3, d: 4 }); }; exports.merge = function () { var ref = { a : 1, b : 2 }; var items = Hash(ref).merge({ b : 3, c : 3.14 }).items; var hash = Hash.merge(ref, { b : 3, c : 3.14 }); assert.deepEqual(ref, { a : 1, b : 2 }); assert.deepEqual(items, { a : 1, b : 3, c : 3.14 }); assert.deepEqual(hash, { a : 1, b : 3, c : 3.14 }); var ref2 = { a : 1 }; var hash2 = Hash.merge(ref, { b: 2, c: 3 }, undefined, { d: 4 }); assert.deepEqual(hash2, { a: 1, b: 2, c: 3, d: 4 }); }; exports.has = function () { var h = { a : 4, b : 5 }; var hh = Hash(h); assert.ok(hh.has('a')); assert.equal(hh.has('c'), false); assert.ok(hh.has(['a','b'])); assert.equal(hh.has(['a','b','c']), false); assert.ok(Hash.has(h, 'a')); assert.equal(Hash.has(h, 'c'), false); assert.ok(Hash.has(h, ['a','b'])); assert.equal(Hash.has(h, ['a','b','c']), false); }; exports.valuesAt = function () { var h = { a : 4, b : 5, c : 6 }; assert.equal(Hash(h).valuesAt('a'), 4); assert.equal(Hash(h).valuesAt(['a'])[0], 4); assert.deepEqual(Hash(h).valuesAt(['a','b']), [4,5]); assert.equal(Hash.valuesAt(h, 'a'), 4); assert.deepEqual(Hash.valuesAt(h, ['a']), [4]); assert.deepEqual(Hash.valuesAt(h, ['a','b']), [4,5]); }; exports.tap = function () { var h = { a : 4, b : 5, c : 6 }; var hh = Hash(h); hh.tap(function (x) { assert.ok(this === hh) assert.eql(x, h); }); Hash.tap(h, function (x) { assert.eql( Object.keys(this).sort(), Object.keys(hh).sort() ); assert.eql(x, h); }); }; exports.extract = function () { var hash = Hash({ a : 1, b : 2, c : 3 }).clone; var extracted = hash.extract(['a','b']); assert.equal(extracted.length, 2); assert.deepEqual(extracted.items, { a : 1, b : 2 }); }; exports.exclude = function () { var hash = Hash({ a : 1, b : 2, c : 3 }).clone; var extracted = hash.exclude(['a','b']); assert.equal(extracted.length, 1); assert.deepEqual(extracted.items, { c : 3 }); }; exports.concat = function () { var ref1 = { a : 1, b : 2 }; var ref2 = { foo : 100, bar : 200 }; var ref3 = { b : 3, c : 4, bar : 300 }; assert.deepEqual( Hash.concat([ ref1, ref2 ]), { a : 1, b : 2, foo : 100, bar : 200 } ); assert.deepEqual( Hash.concat([ ref1, ref2, ref3 ]), { a : 1, b : 3, c : 4, foo : 100, bar : 300 } ); }; exports.zip = function () { var xs = ['a','b','c']; var ys = [1,2,3,4]; var h = Hash(xs,ys); assert.equal(h.length, 3); assert.deepEqual(h.items, { a : 1, b : 2, c : 3 }); var zipped = Hash.zip(xs,ys); assert.deepEqual(zipped, { a : 1, b : 2, c : 3 }); }; exports.length = function () { assert.equal(Hash({ a : 1, b : [2,3], c : 4 }).length, 3); assert.equal(Hash({ a : 1, b : [2,3], c : 4 }).size, 3); assert.equal(Hash.size({ a : 1, b : [2,3], c : 4 }), 3); }; exports.compact = function () { var hash = { a : 1, b : undefined, c : false, d : 4, e : [ undefined, 4 ], f : null }; var compacted = Hash(hash).compact; assert.deepEqual( { a : 1, b : undefined, c : false, d : 4, e : [ undefined, 4 ], f : null }, hash, 'compact modified the hash' ); assert.deepEqual( compacted.items, { a : 1, c : false, d : 4, e : [ undefined, 4 ], f : null } ); var h = Hash.compact(hash); assert.deepEqual(h, compacted.items); }; test/property.js000066400000000000000000000035201214226277500142670ustar00rootroot00000000000000var Hash = require('hashish'); var assert = require('assert'); var vm = require('vm'); var fs = require('fs'); var src = fs.readFileSync(__dirname + '/../index.js', 'utf8'); exports.defineGetter = function () { var context = { module : { exports : {} }, Object : { keys : Object.keys, defineProperty : undefined, }, require : require, }; context.exports = context.module.exports; vm.runInNewContext('(function () {' + src + '})()', context); var Hash_ = context.module.exports; var times = 0; Hash_.__proto__.__proto__.__defineGetter__ = function () { times ++; return Object.__defineGetter__.apply(this, arguments); }; assert.equal(vm.runInNewContext('Object.defineProperty', context), null); assert.deepEqual( Hash_({ a : 1, b : 2, c : 3 }).values, [ 1, 2, 3 ] ); assert.ok(times > 5); }; exports.defineProperty = function () { var times = 0; var context = { module : { exports : {} }, Object : { keys : Object.keys, defineProperty : function (prop) { times ++; if (prop.get) throw new TypeError('engine does not support') assert.fail('should have asserted by now'); }, }, require : require }; context.exports = context.module.exports; vm.runInNewContext('(function () {' + src + '})()', context); var Hash_ = context.module.exports; Hash_.__proto__.__proto__.__defineGetter__ = function () { assert.fail('getter called when a perfectly good' + ' defineProperty was available' ); }; assert.deepEqual( Hash_({ a : 1, b : 2, c : 3 }).values, [ 1, 2, 3 ] ); assert.equal(times, 1); };