UNPKG

7.59 kBJavaScriptView Raw
1module.exports = Traverse;
2function Traverse (obj) {
3 if (!(this instanceof Traverse)) return new Traverse(obj);
4 this.value = obj;
5}
6
7Traverse.prototype.get = function (ps) {
8 var node = this.value;
9 for (var i = 0; i < ps.length; i ++) {
10 var key = ps[i];
11 if (!Object.hasOwnProperty.call(node, key)) {
12 node = undefined;
13 break;
14 }
15 node = node[key];
16 }
17 return node;
18};
19
20Traverse.prototype.set = function (ps, value) {
21 var node = this.value;
22 for (var i = 0; i < ps.length - 1; i ++) {
23 var key = ps[i];
24 if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
25 node = node[key];
26 }
27 node[ps[i]] = value;
28 return value;
29};
30
31Traverse.prototype.map = function (cb) {
32 return walk(this.value, cb, true);
33};
34
35Traverse.prototype.forEach = function (cb) {
36 this.value = walk(this.value, cb, false);
37 return this.value;
38};
39
40Traverse.prototype.reduce = function (cb, init) {
41 var skip = arguments.length === 1;
42 var acc = skip ? this.value : init;
43 this.forEach(function (x) {
44 if (!this.isRoot || !skip) {
45 acc = cb.call(this, acc, x);
46 }
47 });
48 return acc;
49};
50
51Traverse.prototype.paths = function () {
52 var acc = [];
53 this.forEach(function (x) {
54 acc.push(this.path);
55 });
56 return acc;
57};
58
59Traverse.prototype.nodes = function () {
60 var acc = [];
61 this.forEach(function (x) {
62 acc.push(this.node);
63 });
64 return acc;
65};
66
67Traverse.prototype.clone = function () {
68 var parents = [], nodes = [];
69
70 return (function clone (src) {
71 for (var i = 0; i < parents.length; i++) {
72 if (parents[i] === src) {
73 return nodes[i];
74 }
75 }
76
77 if (typeof src === 'object' && src !== null) {
78 var dst = copy(src);
79
80 parents.push(src);
81 nodes.push(dst);
82
83 forEach(Object_keys(src), function (key) {
84 dst[key] = clone(src[key]);
85 });
86
87 parents.pop();
88 nodes.pop();
89 return dst;
90 }
91 else {
92 return src;
93 }
94 })(this.value);
95};
96
97function walk (root, cb, immutable) {
98 var path = [];
99 var parents = [];
100 var alive = true;
101
102 return (function walker (node_) {
103 var node = immutable ? copy(node_) : node_;
104 var modifiers = {};
105
106 var keepGoing = true;
107
108 var state = {
109 node : node,
110 node_ : node_,
111 path : [].concat(path),
112 parent : parents[parents.length - 1],
113 parents : parents,
114 key : path.slice(-1)[0],
115 isRoot : path.length === 0,
116 level : path.length,
117 circular : null,
118 update : function (x, stopHere) {
119 if (!state.isRoot) {
120 state.parent.node[state.key] = x;
121 }
122 state.node = x;
123 if (stopHere) keepGoing = false;
124 },
125 'delete' : function (stopHere) {
126 delete state.parent.node[state.key];
127 if (stopHere) keepGoing = false;
128 },
129 remove : function (stopHere) {
130 if (Array_isArray(state.parent.node)) {
131 state.parent.node.splice(state.key, 1);
132 }
133 else {
134 delete state.parent.node[state.key];
135 }
136 if (stopHere) keepGoing = false;
137 },
138 keys : null,
139 before : function (f) { modifiers.before = f },
140 after : function (f) { modifiers.after = f },
141 pre : function (f) { modifiers.pre = f },
142 post : function (f) { modifiers.post = f },
143 stop : function () { alive = false },
144 block : function () { keepGoing = false }
145 };
146
147 if (!alive) return state;
148
149 if (typeof node === 'object' && node !== null) {
150 state.keys = Object_keys(node);
151
152 state.isLeaf = state.keys.length == 0;
153
154 for (var i = 0; i < parents.length; i++) {
155 if (parents[i].node_ === node_) {
156 state.circular = parents[i];
157 break;
158 }
159 }
160 }
161 else {
162 state.isLeaf = true;
163 }
164
165 state.notLeaf = !state.isLeaf;
166 state.notRoot = !state.isRoot;
167
168 // use return values to update if defined
169 var ret = cb.call(state, state.node);
170 if (ret !== undefined && state.update) state.update(ret);
171
172 if (modifiers.before) modifiers.before.call(state, state.node);
173
174 if (!keepGoing) return state;
175
176 if (typeof state.node == 'object'
177 && state.node !== null && !state.circular) {
178 parents.push(state);
179
180 forEach(state.keys, function (key, i) {
181 path.push(key);
182
183 if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
184
185 var child = walker(state.node[key]);
186 if (immutable && Object.hasOwnProperty.call(state.node, key)) {
187 state.node[key] = child.node;
188 }
189
190 child.isLast = i == state.keys.length - 1;
191 child.isFirst = i == 0;
192
193 if (modifiers.post) modifiers.post.call(state, child);
194
195 path.pop();
196 });
197 parents.pop();
198 }
199
200 if (modifiers.after) modifiers.after.call(state, state.node);
201
202 return state;
203 })(root).node;
204}
205
206function copy (src) {
207 if (typeof src === 'object' && src !== null) {
208 var dst;
209
210 if (Array_isArray(src)) {
211 dst = [];
212 }
213 else if (src instanceof Date) {
214 dst = new Date(src);
215 }
216 else if (src instanceof Boolean) {
217 dst = new Boolean(src);
218 }
219 else if (src instanceof Number) {
220 dst = new Number(src);
221 }
222 else if (src instanceof String) {
223 dst = new String(src);
224 }
225 else if (Object.create && Object.getPrototypeOf) {
226 dst = Object.create(Object.getPrototypeOf(src));
227 }
228 else if (src.__proto__ || src.constructor.prototype) {
229 var proto = src.__proto__ || src.constructor.prototype || {};
230 var T = function () {};
231 T.prototype = proto;
232 dst = new T;
233 if (!dst.__proto__) dst.__proto__ = proto;
234 }
235
236 forEach(Object_keys(src), function (key) {
237 dst[key] = src[key];
238 });
239 return dst;
240 }
241 else return src;
242}
243
244var Object_keys = Object.keys || function keys (obj) {
245 var res = [];
246 for (var key in obj) res.push(key)
247 return res;
248};
249
250var Array_isArray = Array.isArray || function isArray (xs) {
251 return Object.prototype.toString.call(xs) === '[object Array]';
252};
253
254var forEach = function (xs, fn) {
255 if (xs.forEach) return xs.forEach(fn)
256 else for (var i = 0; i < xs.length; i++) {
257 fn(xs[i], i, xs);
258 }
259};
260
261forEach(Object_keys(Traverse.prototype), function (key) {
262 Traverse[key] = function (obj) {
263 var args = [].slice.call(arguments, 1);
264 var t = Traverse(obj);
265 return t[key].apply(t, args);
266 };
267});