UNPKG

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