1 | module.exports = Traverse;
|
2 | function Traverse (obj) {
|
3 | if (!(this instanceof Traverse)) return new Traverse(obj);
|
4 | this.value = obj;
|
5 | }
|
6 |
|
7 | Traverse.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 |
|
20 | Traverse.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 |
|
31 | Traverse.prototype.map = function (cb) {
|
32 | return walk(this.value, cb, true);
|
33 | };
|
34 |
|
35 | Traverse.prototype.forEach = function (cb) {
|
36 | this.value = walk(this.value, cb, false);
|
37 | return this.value;
|
38 | };
|
39 |
|
40 | Traverse.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 |
|
51 | Traverse.prototype.paths = function () {
|
52 | var acc = [];
|
53 | this.forEach(function (x) {
|
54 | acc.push(this.path);
|
55 | });
|
56 | return acc;
|
57 | };
|
58 |
|
59 | Traverse.prototype.nodes = function () {
|
60 | var acc = [];
|
61 | this.forEach(function (x) {
|
62 | acc.push(this.node);
|
63 | });
|
64 | return acc;
|
65 | };
|
66 |
|
67 | Traverse.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 |
|
97 | function 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 |
|
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 |
|
206 | function 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 |
|
244 | var Object_keys = Object.keys || function keys (obj) {
|
245 | var res = [];
|
246 | for (var key in obj) res.push(key)
|
247 | return res;
|
248 | };
|
249 |
|
250 | var Array_isArray = Array.isArray || function isArray (xs) {
|
251 | return Object.prototype.toString.call(xs) === '[object Array]';
|
252 | };
|
253 |
|
254 | var 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 |
|
261 | forEach(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 | });
|