| module.exports = Traverse; |
| function Traverse (obj) { |
| if (!(this instanceof Traverse)) return new Traverse(obj); |
| this.value = obj; |
| } |
| |
| Traverse.prototype.get = function (ps) { |
| var node = this.value; |
| for (var i = 0; i < ps.length; i ++) { |
| var key = ps[i]; |
| if (!Object.hasOwnProperty.call(node, key)) { |
| node = undefined; |
| break; |
| } |
| node = node[key]; |
| } |
| return node; |
| }; |
| |
| Traverse.prototype.set = function (ps, value) { |
| var node = this.value; |
| for (var i = 0; i < ps.length - 1; i ++) { |
| var key = ps[i]; |
| if (!Object.hasOwnProperty.call(node, key)) node[key] = {}; |
| node = node[key]; |
| } |
| node[ps[i]] = value; |
| return value; |
| }; |
| |
| Traverse.prototype.map = function (cb) { |
| return walk(this.value, cb, true); |
| }; |
| |
| Traverse.prototype.forEach = function (cb) { |
| this.value = walk(this.value, cb, false); |
| return this.value; |
| }; |
| |
| Traverse.prototype.reduce = function (cb, init) { |
| var skip = arguments.length === 1; |
| var acc = skip ? this.value : init; |
| this.forEach(function (x) { |
| if (!this.isRoot || !skip) { |
| acc = cb.call(this, acc, x); |
| } |
| }); |
| return acc; |
| }; |
| |
| Traverse.prototype.deepEqual = function (obj) { |
| if (arguments.length !== 1) { |
| throw new Error( |
| 'deepEqual requires exactly one object to compare against' |
| ); |
| } |
| |
| var equal = true; |
| var node = obj; |
| |
| this.forEach(function (y) { |
| var notEqual = (function () { |
| equal = false; |
| //this.stop(); |
| return undefined; |
| }).bind(this); |
| |
| //if (node === undefined || node === null) return notEqual(); |
| |
| if (!this.isRoot) { |
| /* |
| if (!Object.hasOwnProperty.call(node, this.key)) { |
| return notEqual(); |
| } |
| */ |
| if (typeof node !== 'object') return notEqual(); |
| node = node[this.key]; |
| } |
| |
| var x = node; |
| |
| this.post(function () { |
| node = x; |
| }); |
| |
| var toS = function (o) { |
| return Object.prototype.toString.call(o); |
| }; |
| |
| if (this.circular) { |
| if (Traverse(obj).get(this.circular.path) !== x) notEqual(); |
| } |
| else if (typeof x !== typeof y) { |
| notEqual(); |
| } |
| else if (x === null || y === null || x === undefined || y === undefined) { |
| if (x !== y) notEqual(); |
| } |
| else if (x.__proto__ !== y.__proto__) { |
| notEqual(); |
| } |
| else if (x === y) { |
| // nop |
| } |
| else if (typeof x === 'function') { |
| if (x instanceof RegExp) { |
| // both regexps on account of the __proto__ check |
| if (x.toString() != y.toString()) notEqual(); |
| } |
| else if (x !== y) notEqual(); |
| } |
| else if (typeof x === 'object') { |
| if (toS(y) === '[object Arguments]' |
| || toS(x) === '[object Arguments]') { |
| if (toS(x) !== toS(y)) { |
| notEqual(); |
| } |
| } |
| else if (x instanceof Date || y instanceof Date) { |
| if (!(x instanceof Date) || !(y instanceof Date) |
| || x.getTime() !== y.getTime()) { |
| notEqual(); |
| } |
| } |
| else { |
| var kx = Object.keys(x); |
| var ky = Object.keys(y); |
| if (kx.length !== ky.length) return notEqual(); |
| for (var i = 0; i < kx.length; i++) { |
| var k = kx[i]; |
| if (!Object.hasOwnProperty.call(y, k)) { |
| notEqual(); |
| } |
| } |
| } |
| } |
| }); |
| |
| return equal; |
| }; |
| |
| 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 () { |
| var parents = [], nodes = []; |
| |
| return (function clone (src) { |
| for (var i = 0; i < parents.length; i++) { |
| if (parents[i] === src) { |
| return nodes[i]; |
| } |
| } |
| |
| if (typeof src === 'object' && src !== null) { |
| var dst = copy(src); |
| |
| parents.push(src); |
| nodes.push(dst); |
| |
| Object.keys(src).forEach(function (key) { |
| dst[key] = clone(src[key]); |
| }); |
| |
| parents.pop(); |
| nodes.pop(); |
| return dst; |
| } |
| else { |
| return src; |
| } |
| })(this.value); |
| }; |
| |
| function walk (root, cb, immutable) { |
| var path = []; |
| var parents = []; |
| var alive = true; |
| |
| return (function walker (node_) { |
| var node = immutable ? copy(node_) : node_; |
| var modifiers = {}; |
| |
| var keepGoing = true; |
| |
| var state = { |
| node : node, |
| node_ : node_, |
| path : [].concat(path), |
| parent : parents[parents.length - 1], |
| parents : parents, |
| key : path.slice(-1)[0], |
| isRoot : path.length === 0, |
| level : path.length, |
| circular : null, |
| update : function (x, stopHere) { |
| if (!state.isRoot) { |
| state.parent.node[state.key] = x; |
| } |
| state.node = x; |
| if (stopHere) keepGoing = false; |
| }, |
| 'delete' : function () { |
| delete state.parent.node[state.key]; |
| }, |
| remove : function () { |
| if (Array.isArray(state.parent.node)) { |
| state.parent.node.splice(state.key, 1); |
| } |
| else { |
| delete state.parent.node[state.key]; |
| } |
| }, |
| keys : null, |
| before : function (f) { modifiers.before = f }, |
| after : function (f) { modifiers.after = f }, |
| pre : function (f) { modifiers.pre = f }, |
| post : function (f) { modifiers.post = f }, |
| stop : function () { alive = false }, |
| block : function () { keepGoing = false } |
| }; |
| |
| if (!alive) return state; |
| |
| if (typeof node === 'object' && node !== null) { |
| state.keys = Object.keys(node); |
| |
| state.isLeaf = state.keys.length == 0; |
| |
| for (var i = 0; i < parents.length; i++) { |
| if (parents[i].node_ === node_) { |
| state.circular = parents[i]; |
| break; |
| } |
| } |
| } |
| else { |
| state.isLeaf = true; |
| } |
| |
| state.notLeaf = !state.isLeaf; |
| state.notRoot = !state.isRoot; |
| |
| // use return values to update if defined |
| var ret = cb.call(state, state.node); |
| if (ret !== undefined && state.update) state.update(ret); |
| |
| if (modifiers.before) modifiers.before.call(state, state.node); |
| |
| if (!keepGoing) return state; |
| |
| if (typeof state.node == 'object' |
| && state.node !== null && !state.circular) { |
| parents.push(state); |
| |
| state.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]); |
| if (immutable && Object.hasOwnProperty.call(state.node, key)) { |
| state.node[key] = child.node; |
| } |
| |
| child.isLast = i == state.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).node; |
| } |
| |
| 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); |
| }; |
| }); |
| |
| function copy (src) { |
| if (typeof src === 'object' && src !== null) { |
| var dst; |
| |
| if (Array.isArray(src)) { |
| dst = []; |
| } |
| else if (src instanceof Date) { |
| dst = new Date(src); |
| } |
| else if (src instanceof Boolean) { |
| dst = new Boolean(src); |
| } |
| else if (src instanceof Number) { |
| dst = new Number(src); |
| } |
| else if (src instanceof String) { |
| dst = new String(src); |
| } |
| else { |
| dst = Object.create(Object.getPrototypeOf(src)); |
| } |
| |
| Object.keys(src).forEach(function (key) { |
| dst[key] = src[key]; |
| }); |
| return dst; |
| } |
| else return src; |
| } |