Last active
April 30, 2017 00:25
-
-
Save rbarilani/8ac6293f7693dfa216019a2581ba6413 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (exports) { | |
var traverse = function (obj) { | |
return new Traverse(obj); | |
}; | |
exports.traverse = traverse; | |
function 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 (!node || !hasOwnProperty.call(node, key)) { | |
node = undefined; | |
break; | |
} | |
node = node[key]; | |
} | |
return node; | |
}; | |
Traverse.prototype.has = function (ps) { | |
var node = this.value; | |
for (var i = 0; i < ps.length; i ++) { | |
var key = ps[i]; | |
if (!node || !hasOwnProperty.call(node, key)) { | |
return false; | |
} | |
node = node[key]; | |
} | |
return true; | |
}; | |
Traverse.prototype.set = function (ps, value) { | |
var node = this.value; | |
for (var i = 0; i < ps.length - 1; i ++) { | |
var key = ps[i]; | |
if (!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.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); | |
forEach(objectKeys(src), 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 (stopHere) { | |
delete state.parent.node[state.key]; | |
if (stopHere) keepGoing = false; | |
}, | |
remove : function (stopHere) { | |
if (isArray(state.parent.node)) { | |
state.parent.node.splice(state.key, 1); | |
} | |
else { | |
delete state.parent.node[state.key]; | |
} | |
if (stopHere) keepGoing = false; | |
}, | |
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; | |
function updateState() { | |
if (typeof state.node === 'object' && state.node !== null) { | |
if (!state.keys || state.node_ !== state.node) { | |
state.keys = objectKeys(state.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.keys = null; | |
} | |
state.notLeaf = !state.isLeaf; | |
state.notRoot = !state.isRoot; | |
} | |
updateState(); | |
// 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); | |
updateState(); | |
forEach(state.keys, 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 && 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; | |
} | |
function copy (src) { | |
if (typeof src === 'object' && src !== null) { | |
var dst; | |
if (isArray(src)) { | |
dst = []; | |
} | |
else if (isDate(src)) { | |
dst = new Date(src.getTime ? src.getTime() : src); | |
} | |
else if (isRegExp(src)) { | |
dst = new RegExp(src); | |
} | |
else if (isError(src)) { | |
dst = { message: src.message }; | |
} | |
else if (isBoolean(src)) { | |
dst = new Boolean(src); | |
} | |
else if (isNumber(src)) { | |
dst = new Number(src); | |
} | |
else if (isString(src)) { | |
dst = new String(src); | |
} | |
else if (Object.create && Object.getPrototypeOf) { | |
dst = Object.create(Object.getPrototypeOf(src)); | |
} | |
else if (src.constructor === Object) { | |
dst = {}; | |
} | |
else { | |
var proto = | |
(src.constructor && src.constructor.prototype) | |
|| src.__proto__ | |
|| {} | |
; | |
var T = function () {}; | |
T.prototype = proto; | |
dst = new T; | |
} | |
forEach(objectKeys(src), function (key) { | |
dst[key] = src[key]; | |
}); | |
return dst; | |
} | |
else return src; | |
} | |
var objectKeys = Object.keys || function keys (obj) { | |
var res = []; | |
for (var key in obj) res.push(key) | |
return res; | |
}; | |
function toS (obj) { return Object.prototype.toString.call(obj) } | |
function isDate (obj) { return toS(obj) === '[object Date]' } | |
function isRegExp (obj) { return toS(obj) === '[object RegExp]' } | |
function isError (obj) { return toS(obj) === '[object Error]' } | |
function isBoolean (obj) { return toS(obj) === '[object Boolean]' } | |
function isNumber (obj) { return toS(obj) === '[object Number]' } | |
function isString (obj) { return toS(obj) === '[object String]' } | |
var isArray = Array.isArray || function isArray (xs) { | |
return Object.prototype.toString.call(xs) === '[object Array]'; | |
}; | |
var forEach = function (xs, fn) { | |
if (xs.forEach) return xs.forEach(fn) | |
else for (var i = 0; i < xs.length; i++) { | |
fn(xs[i], i, xs); | |
} | |
}; | |
forEach(objectKeys(Traverse.prototype), function (key) { | |
traverse[key] = function (obj) { | |
var args = [].slice.call(arguments, 1); | |
var t = new Traverse(obj); | |
return t[key].apply(t, args); | |
}; | |
}); | |
var hasOwnProperty = Object.hasOwnProperty || function (obj, key) { | |
return key in obj; | |
}; | |
})(window); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment