1 | var traverse = module.exports = function (obj) {
|
2 | return new Traverse(obj);
|
3 | };
|
4 |
|
5 | function Traverse (obj) {
|
6 | this.value = obj;
|
7 | }
|
8 |
|
9 | Traverse.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 |
|
22 | Traverse.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 |
|
34 | Traverse.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 |
|
45 | Traverse.prototype.map = function (cb) {
|
46 | return walk(this.value, cb, true);
|
47 | };
|
48 |
|
49 | Traverse.prototype.forEach = function (cb) {
|
50 | this.value = walk(this.value, cb, false);
|
51 | return this.value;
|
52 | };
|
53 |
|
54 | Traverse.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 |
|
65 | Traverse.prototype.paths = function () {
|
66 | var acc = [];
|
67 | this.forEach(function (x) {
|
68 | acc.push(this.path);
|
69 | });
|
70 | return acc;
|
71 | };
|
72 |
|
73 | Traverse.prototype.nodes = function () {
|
74 | var acc = [];
|
75 | this.forEach(function (x) {
|
76 | acc.push(this.node);
|
77 | });
|
78 | return acc;
|
79 | };
|
80 |
|
81 | Traverse.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 |
|
111 | function 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 |
|
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 |
|
220 | function 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 |
|
270 | var Object_keys = Object.keys || function keys (obj) {
|
271 | var res = [];
|
272 | for (var key in obj) res.push(key)
|
273 | return res;
|
274 | };
|
275 |
|
276 | function toS (obj) { return Object.prototype.toString.call(obj) }
|
277 | function isDate (obj) { return toS(obj) === '[object Date]' }
|
278 | function isRegExp (obj) { return toS(obj) === '[object RegExp]' }
|
279 | function isError (obj) { return toS(obj) === '[object Error]' }
|
280 | function isBoolean (obj) { return toS(obj) === '[object Boolean]' }
|
281 | function isNumber (obj) { return toS(obj) === '[object Number]' }
|
282 | function isString (obj) { return toS(obj) === '[object String]' }
|
283 |
|
284 | var isArray = Array.isArray || function isArray (xs) {
|
285 | return Object.prototype.toString.call(xs) === '[object Array]';
|
286 | };
|
287 |
|
288 | var 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 |
|
295 | forEach(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 | });
|