2 Copyright 2010 James Halliday (mail@substack.net)
4 This project is free software released under the MIT/X11 license:
5 http://www.opensource.org/licenses/mit-license.php
7 Copyright 2010 James Halliday (mail@substack.net)
9 Permission is hereby granted, free of charge, to any person obtaining a copy
10 of this software and associated documentation files (the "Software"), to deal
11 in the Software without restriction, including without limitation the rights
12 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the Software is
14 furnished to do so, subject to the following conditions:
16 The above copyright notice and this permission notice shall be included in
17 all copies or substantial portions of the Software.
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31 traverse = function (obj) {
32 return new Traverse(obj);
35 function Traverse (obj) {
39 Traverse.prototype.get = function (ps) {
40 var node = this.value;
41 for (var i = 0; i < ps.length; i ++) {
43 if (!node || !hasOwnProperty.call(node, key)) {
52 Traverse.prototype.has = function (ps) {
53 var node = this.value;
54 for (var i = 0; i < ps.length; i ++) {
56 if (!node || !hasOwnProperty.call(node, key)) {
64 Traverse.prototype.set = function (ps, value) {
65 var node = this.value;
66 for (var i = 0; i < ps.length - 1; i ++) {
68 if (!hasOwnProperty.call(node, key)) node[key] = {};
75 Traverse.prototype.map = function (cb) {
76 return walk(this.value, cb, true);
79 Traverse.prototype.forEach = function (cb) {
80 this.value = walk(this.value, cb, false);
84 Traverse.prototype.reduce = function (cb, init) {
85 var skip = arguments.length === 1;
86 var acc = skip ? this.value : init;
87 this.forEach(function (x) {
88 if (!this.isRoot || !skip) {
89 acc = cb.call(this, acc, x);
95 Traverse.prototype.paths = function () {
97 this.forEach(function (x) {
103 Traverse.prototype.nodes = function () {
105 this.forEach(function (x) {
111 Traverse.prototype.clone = function () {
112 var parents = [], nodes = [];
114 return (function clone (src) {
115 for (var i = 0; i < parents.length; i++) {
116 if (parents[i] === src) {
121 if (typeof src === 'object' && src !== null) {
127 forEach(objectKeys(src), function (key) {
128 dst[key] = clone(src[key]);
141 function walk (root, cb, immutable) {
146 return (function walker (node_) {
147 var node = immutable ? copy(node_) : node_;
150 var keepGoing = true;
155 path : [].concat(path),
156 parent : parents[parents.length - 1],
158 key : path.slice(-1)[0],
159 isRoot : path.length === 0,
162 update : function (x, stopHere) {
164 state.parent.node[state.key] = x;
167 if (stopHere) keepGoing = false;
169 'delete' : function (stopHere) {
170 delete state.parent.node[state.key];
171 if (stopHere) keepGoing = false;
173 remove : function (stopHere) {
174 if (isArray(state.parent.node)) {
175 state.parent.node.splice(state.key, 1);
178 delete state.parent.node[state.key];
180 if (stopHere) keepGoing = false;
183 before : function (f) { modifiers.before = f },
184 after : function (f) { modifiers.after = f },
185 pre : function (f) { modifiers.pre = f },
186 post : function (f) { modifiers.post = f },
187 stop : function () { alive = false },
188 block : function () { keepGoing = false }
191 if (!alive) return state;
193 function updateState() {
194 if (typeof state.node === 'object' && state.node !== null) {
195 if (!state.keys || state.node_ !== state.node) {
196 state.keys = objectKeys(state.node)
199 state.isLeaf = state.keys.length == 0;
201 for (var i = 0; i < parents.length; i++) {
202 if (parents[i].node_ === node_) {
203 state.circular = parents[i];
213 state.notLeaf = !state.isLeaf;
214 state.notRoot = !state.isRoot;
219 // use return values to update if defined
220 var ret = cb.call(state, state.node);
221 if (ret !== undefined && state.update) state.update(ret);
223 if (modifiers.before) modifiers.before.call(state, state.node);
225 if (!keepGoing) return state;
227 if (typeof state.node == 'object'
228 && state.node !== null && !state.circular) {
233 forEach(state.keys, function (key, i) {
236 if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
238 var child = walker(state.node[key]);
239 if (immutable && hasOwnProperty.call(state.node, key)) {
240 state.node[key] = child.node;
243 child.isLast = i == state.keys.length - 1;
244 child.isFirst = i == 0;
246 if (modifiers.post) modifiers.post.call(state, child);
253 if (modifiers.after) modifiers.after.call(state, state.node);
259 function copy (src) {
260 if (typeof src === 'object' && src !== null) {
266 else if (isDate(src)) {
267 dst = new Date(src.getTime ? src.getTime() : src);
269 else if (isRegExp(src)) {
270 dst = new RegExp(src);
272 else if (isError(src)) {
273 dst = { message: src.message };
275 else if (isBoolean(src)) {
276 dst = new Boolean(src);
278 else if (isNumber(src)) {
279 dst = new Number(src);
281 else if (isString(src)) {
282 dst = new String(src);
284 else if (Object.create && Object.getPrototypeOf) {
285 dst = Object.create(Object.getPrototypeOf(src));
287 else if (src.constructor === Object) {
292 (src.constructor && src.constructor.prototype)
296 var T = function () {};
301 forEach(objectKeys(src), function (key) {
309 var objectKeys = Object.keys || function keys (obj) {
311 for (var key in obj) res.push(key)
315 function toS (obj) { return Object.prototype.toString.call(obj) }
316 function isDate (obj) { return toS(obj) === '[object Date]' }
317 function isRegExp (obj) { return toS(obj) === '[object RegExp]' }
318 function isError (obj) { return toS(obj) === '[object Error]' }
319 function isBoolean (obj) { return toS(obj) === '[object Boolean]' }
320 function isNumber (obj) { return toS(obj) === '[object Number]' }
321 function isString (obj) { return toS(obj) === '[object String]' }
323 var isArray = Array.isArray || function isArray (xs) {
324 return Object.prototype.toString.call(xs) === '[object Array]';
327 var forEach = function (xs, fn) {
328 if (xs.forEach) return xs.forEach(fn)
329 else for (var i = 0; i < xs.length; i++) {
334 forEach(objectKeys(Traverse.prototype), function (key) {
335 traverse[key] = function (obj) {
336 var args = [].slice.call(arguments, 1);
337 var t = new Traverse(obj);
338 return t[key].apply(t, args);
342 var hasOwnProperty = Object.hasOwnProperty || function (obj, key) {