diff --git a/examples/negative.js b/examples/negative.js index e0d0fd8..f6f5c3b 100755 --- a/examples/negative.js +++ b/examples/negative.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -var sys = require('sys'); var Traverse = require('traverse'); var fixed = Traverse([ @@ -7,7 +6,8 @@ var fixed = Traverse([ ]).modify(function (x) { if (x < 0) this.update(x + 128); }).get() -sys.puts(sys.inspect(fixed)); + +console.dir(fixed); /* Output: [ 5, 6, 125, [ 7, 8, 126, 1 ], { f: 10, g: 115 } ] diff --git a/examples/stringify.js b/examples/stringify.js index a9a0b2f..0cef7ec 100755 --- a/examples/stringify.js +++ b/examples/stringify.js @@ -1,6 +1,5 @@ #!/usr/bin/env node var Traverse = require('traverse'); -var sys = require('sys'); var obj = [ 'five', 6, -3, [ 7, 8, -2, 1 ], { f : 10, g : -13 } ]; diff --git a/index.js b/index.js new file mode 100644 index 0000000..756830d --- /dev/null +++ b/index.js @@ -0,0 +1,153 @@ +module.exports = Traverse; +function Traverse (obj) { + if (!(this instanceof Traverse)) return new Traverse(obj); + this.value = obj; +} + +Traverse.prototype.map = function (cb) { + var obj = Traverse.clone(this.value); + walk(obj, cb); + return obj; +}; + +Traverse.prototype.forEach = function (cb) { + walk(this.value, cb); + return this.value; +}; + +Traverse.prototype.paths = function () { + var acc = []; + this.forEach(function (x) { + acc.push(this.path); + }); + return acc; +}; + +Traverse.prototype.nodes = function () { + var acc = []; + this.forEach(function (x) { + acc.push(this.node); + }); + return acc; +}; + +Traverse.prototype.clone = function () { + // clone refObj for a properly immutable interface: + var refs = []; + var nodes = []; + + return (function clone (ref) { + if (typeof ref == 'object' && ref !== null) { + var node = Array.isArray(ref) ? [] : {}; + refs.push(ref); + nodes.push(node); + + Object.keys(ref).forEach(function (key) { + var i = refs.indexOf(ref[key]); + if (i >= 0) { + node[key] = nodes[i]; + } + else { + node[key] = clone(ref[key]); + } + + }); + + refs.pop(); + nodes.pop(); + + // To make instanceof work: + if (!Array.isArray(ref)) node.__proto__ = ref.__proto__; + + // Probably there are other attributes worth copying + return node; + } + else { + return ref; + } + })(this.value); +}; + +function walk (root, cb) { + var path = []; + var parents = []; + + return (function walker (node) { + var modifiers = {}; + + var state = { + node : node, + path : [].concat(path), + parent : parents.slice(-1)[0], + key : path.slice(-1)[0], + isRoot : node === root, + level : path.length, + circular : null, + update : function (x) { + if (state.isRoot) { + root = x; + } + else { + state.parent.node[state.key] = x; + } + state.node = x; + }, + before : function (f) { modifiers.before = f }, + after : function (f) { modifiers.after = f }, + pre : function (f) { modifiers.pre = f }, + post : function (f) { modifiers.post = f } + }; + + if (typeof node == 'object' && node !== null) { + state.isLeaf = Object.keys(node).length == 0 + var circs = parents.filter(function (p) { + return node == p.node + }); + if (circs.length) state.circular = circs[0]; + } + else { + state.isLeaf = true; + } + + state.notLeaf = !state.isLeaf; + state.notRoot = !state.isRoot; + + // use return values to update if defined + var ret = cb.call(state, node); + if (ret !== undefined && state.update) state.update(ret); + if (modifiers.before) modifiers.before.call(state, state.node); + + if (typeof state.node == 'object' + && state.node !== null && !state.circular) { + parents.push(state); + + var keys = Object.keys(state.node); + keys.forEach(function (key, i) { + path.push(key); + + if (modifiers.pre) modifiers.pre.call(state, state.node[key], key); + + var child = walker(state.node[key]); + child.isLast = i == keys.length - 1; + child.isFirst = i == 0; + + if (modifiers.post) modifiers.post.call(state, child); + + path.pop(); + }); + parents.pop(); + } + + if (modifiers.after) modifiers.after.call(state, state.node); + + return state; + })(root); +} + +Object.keys(Traverse.prototype).forEach(function (key) { + Traverse[key] = function (obj) { + var args = [].slice.call(arguments, 1); + var t = Traverse(obj); + return t[key].apply(t, args); + }; +}); diff --git a/lib/hash.js b/lib/hash.js deleted file mode 100644 index ed2b8be..0000000 --- a/lib/hash.js +++ /dev/null @@ -1,218 +0,0 @@ -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 (h) { - Object.keys(h).forEach(function (key) { - hash[key] = h[key]; - }); - return self; - }, - merge : function (h) { - return self.copy.update(h); - }, - 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 (self.__defineGetter__) { - for (var key in props) { - self.__defineGetter__(key, props[key]); - } - } - else if (Object.defineProperty) { - for (var key in props) { - Object.defineProperty(self, key, { get : props[key] }); - } - } - else { - // non-lazy version for browsers that suck >_< - for (var key in props) { - self[key] = prop(); - } - } - - 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) { - return Hash(a).update(b).items; -}; - -Hash.merge = function (a, b) { - return Hash(a).merge(b).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; -}; diff --git a/lib/traverse.js b/lib/traverse.js deleted file mode 100644 index 614d94e..0000000 --- a/lib/traverse.js +++ /dev/null @@ -1,183 +0,0 @@ -module.exports = Traverse; -module.exports.Traverse = Traverse; - -function Traverse (refObj) { - if (!(this instanceof Traverse)) return new Traverse(refObj); - - // clone refObj for a properly immutable interface: - clone.refs = []; - clone.nodes = []; - function clone(ref) { - if (typeof ref == 'object' && ref !== null) { - var node = Array.isArray(ref) ? [] : {}; - clone.refs.push(ref); - clone.nodes.push(node); - - Object.keys(ref).forEach(function (key) { - var i = clone.refs.indexOf(ref[key]); - if (i >= 0) { - node[key] = clone.nodes[i]; - } - else { - node[key] = clone(ref[key]); - } - - }); - - clone.refs.pop(); - clone.nodes.pop(); - - // To make instanceof work: - if (!Array.isArray(ref)) node.__proto__ = ref.__proto__; - - // Probably there are other attributes worth copying - return node; - } - else { - return ref; - } - } - this.value = clone(refObj); - - // get() is deprecated, use .value - this.get = function () { return this.value }; - - this.map = function (cb) { - walk({ - node : this.value, - root : this.value, - callback : cb - }); - return this; - - }; - - this.modify = this.map; // deprecated .modify() - - this.forEach = function (f) { - this.map(function (node) { - delete this.update; - f.call(this, node); - }); - return this; - }; - - this.paths = function () { - var acc = []; - this.forEach(function (x) { - acc.push(this.path); - }); - return acc; - }; - - this.nodes = function () { - var acc = []; - this.forEach(function (x) { - acc.push(this.node); - }); - return acc; - }; -} - -Traverse.clone = function (obj) { - return Traverse(obj).get(); -}; - -Traverse.map = function (obj, f) { - return Traverse(obj).map(f).get(); -}; - -Traverse.forEach = function (obj, f) { - Traverse(obj).forEach(f); -}; - -Traverse.paths = function (obj) { - return Traverse(obj).paths(); -}; - -Traverse.nodes = function (obj) { - return Traverse(obj).nodes(); -}; - -function walk (params) { - var node = params.node; - var root = params.root; - var path = params.path || []; - var parents = params.parents || []; - var cb = params.callback; - - var modifiers = {}; - - var state = { - node : node, - path : [].concat(path), - parent : parents.slice(-1)[0], - key : path.slice(-1)[0], - isRoot : node === root, - level : path.length, - circular : null, - update : function (x) { - if (state.isRoot) { - root = x; - } - else { - state.parent.node[state.key] = x; - } - state.node = x; - }, - before : function (f) { modifiers.before = f }, - after : function (f) { modifiers.after = f }, - pre : function (f) { modifiers.pre = f }, - post : function (f) { modifiers.post = f } - }; - - if (typeof node == 'object' && node !== null) { - state.isLeaf = Object.keys(node).length == 0 - var circs = parents.filter(function (p) { - return node == p.node - }); - if (circs.length) state.circular = circs[0]; - } - else { - state.isLeaf = true; - } - - state.notLeaf = !state.isLeaf; - state.notRoot = !state.isRoot; - - // use return values to update if defined - var ret = cb.call(state, node); - if (ret !== undefined && state.update) state.update(ret); - if (modifiers.before) modifiers.before.call(state, state.node); - - if (typeof state.node == 'object' - && state.node !== null && !state.circular) { - parents.push(state); - - var keys = Object.keys(state.node); - keys.forEach(function (key, i) { - path.push(key); - - if (modifiers.pre) modifiers.pre.call(state, state.node[key], key); - - var child = walk({ - node : state.node[key], - root : root, - path : path, - parents : parents, - callback : cb - }); - child.isLast = i == keys.length - 1; - child.isFirst = i == 0; - - if (modifiers.post) modifiers.post.call(state, child); - - path.pop(); - }); - parents.pop(); - } - - if (modifiers.after) modifiers.after.call(state, state.node); - - return state; -} diff --git a/lib/web.js b/lib/web.js deleted file mode 100644 index 93fe52d..0000000 --- a/lib/web.js +++ /dev/null @@ -1,15 +0,0 @@ -var fs = require('fs'); - -// return a special webified version of traverse -exports.source = function () { - return [ 'traverse.js', 'hash.js' ].map(scrub).join('\n'); -}; - -function scrub (filename) { - return fs.readFileSync(__dirname + '/' + filename) - .toString() - .replace(/^module\..*/mg, '') - .replace(/^var \S+\s*=\s*require\(.*\);/mg, '') - ; -} - diff --git a/package.json b/package.json index 2ec52d0..8dbaaee 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,23 @@ { "name" : "traverse", - "version" : "0.2.4", + "version" : "0.3.0", "description" : "Traverse and transform objects by visiting every node on a recursive walk.", "author" : "James Halliday", "license" : "MIT/X11", + "main" : "./index", "modules" : { - "index" : "./lib/traverse", "web" : "./lib/web", "hash" : "./lib/hash" }, "repository" : { "type" : "git", "url" : "http://github.com/substack/js-traverse.git" + }, + "devDependencies" : { + "seq" : ">=0.1.7", + "expresso" : ">=0.6.0" + }, + "scripts" : { + "test" : "expresso" } } diff --git a/test/hash.js b/test/hash.js old mode 100755 new mode 100644 diff --git a/test/instance.js b/test/instance.js old mode 100755 new mode 100644 diff --git a/test/interface.js b/test/interface.js old mode 100755 new mode 100644 diff --git a/test/json.js b/test/json.js old mode 100755 new mode 100644 diff --git a/test/leaves.js b/test/leaves.js old mode 100755 new mode 100644 diff --git a/test/negative.js b/test/negative.js old mode 100755 new mode 100644 diff --git a/test/obj.js b/test/obj.js old mode 100755 new mode 100644 diff --git a/test/stringify.js b/test/stringify.js old mode 100755 new mode 100644