You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					323 lines
				
				8.9 KiB
			
		
		
			
		
	
	
					323 lines
				
				8.9 KiB
			| 
											2 years ago
										 | 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 state = { | ||
|  |             node : node, | ||
|  |             node_ : node_, | ||
|  |             path : [].concat(path), | ||
|  |             parent : parents.slice(-1)[0], | ||
|  |             key : path.slice(-1)[0], | ||
|  |             isRoot : path.length === 0, | ||
|  |             level : path.length, | ||
|  |             circular : null, | ||
|  |             update : function (x) { | ||
|  |                 if (!state.isRoot) { | ||
|  |                     state.parent.node[state.key] = x; | ||
|  |                 } | ||
|  |                 state.node = x; | ||
|  |             }, | ||
|  |             '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]; | ||
|  |                 } | ||
|  |             }, | ||
|  |             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 } | ||
|  |         }; | ||
|  |          | ||
|  |         if (!alive) return state; | ||
|  |          | ||
|  |         if (typeof node === 'object' && node !== null) { | ||
|  |             state.isLeaf = Object.keys(node).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 (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]); | ||
|  |                 if (immutable && Object.hasOwnProperty.call(state.node, key)) { | ||
|  |                     state.node[key] = child.node; | ||
|  |                 } | ||
|  |                  | ||
|  |                 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).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; | ||
|  | } |