JAL-857 merge from develop
[jalview.git] / examples / biojson-doc / lib / traverse.js
diff --git a/examples/biojson-doc/lib/traverse.js b/examples/biojson-doc/lib/traverse.js
new file mode 100755 (executable)
index 0000000..bd2f937
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ Copyright 2010 James Halliday (mail@substack.net)
+
+ This project is free software released under the MIT/X11 license:
+ http://www.opensource.org/licenses/mit-license.php
+
+ Copyright 2010 James Halliday (mail@substack.net)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+var traverse;
+
+(function(){
+    traverse = function (obj) {
+        return new Traverse(obj);
+    };
+
+    function Traverse (obj) {
+        this.value = obj;
+    }
+
+    Traverse.prototype.get = function (ps) {
+        var node = this.value;
+        for (var i = 0; i < ps.length; i ++) {
+            var key = ps[i];
+            if (!node || !hasOwnProperty.call(node, key)) {
+                node = undefined;
+                break;
+            }
+            node = node[key];
+        }
+        return node;
+    };
+
+    Traverse.prototype.has = function (ps) {
+        var node = this.value;
+        for (var i = 0; i < ps.length; i ++) {
+            var key = ps[i];
+            if (!node || !hasOwnProperty.call(node, key)) {
+                return false;
+            }
+            node = node[key];
+        }
+        return true;
+    };
+
+    Traverse.prototype.set = function (ps, value) {
+        var node = this.value;
+        for (var i = 0; i < ps.length - 1; i ++) {
+            var key = ps[i];
+            if (!hasOwnProperty.call(node, key)) node[key] = {};
+            node = node[key];
+        }
+        node[ps[i]] = value;
+        return value;
+    };
+
+    Traverse.prototype.map = function (cb) {
+        return walk(this.value, cb, true);
+    };
+
+    Traverse.prototype.forEach = function (cb) {
+        this.value = walk(this.value, cb, false);
+        return this.value;
+    };
+
+    Traverse.prototype.reduce = function (cb, init) {
+        var skip = arguments.length === 1;
+        var acc = skip ? this.value : init;
+        this.forEach(function (x) {
+            if (!this.isRoot || !skip) {
+                acc = cb.call(this, acc, x);
+            }
+        });
+        return acc;
+    };
+
+    Traverse.prototype.paths = function () {
+        var acc = [];
+        this.forEach(function (x) {
+            acc.push(this.path);
+        });
+        return acc;
+    };
+
+    Traverse.prototype.nodes = function () {
+        var acc = [];
+        this.forEach(function (x) {
+            acc.push(this.node);
+        });
+        return acc;
+    };
+
+    Traverse.prototype.clone = function () {
+        var parents = [], nodes = [];
+
+        return (function clone (src) {
+            for (var i = 0; i < parents.length; i++) {
+                if (parents[i] === src) {
+                    return nodes[i];
+                }
+            }
+
+            if (typeof src === 'object' && src !== null) {
+                var dst = copy(src);
+
+                parents.push(src);
+                nodes.push(dst);
+
+                forEach(objectKeys(src), function (key) {
+                    dst[key] = clone(src[key]);
+                });
+
+                parents.pop();
+                nodes.pop();
+                return dst;
+            }
+            else {
+                return src;
+            }
+        })(this.value);
+    };
+
+    function walk (root, cb, immutable) {
+        var path = [];
+        var parents = [];
+        var alive = true;
+
+        return (function walker (node_) {
+            var node = immutable ? copy(node_) : node_;
+            var modifiers = {};
+
+            var keepGoing = true;
+
+            var state = {
+                node : node,
+                node_ : node_,
+                path : [].concat(path),
+                parent : parents[parents.length - 1],
+                parents : parents,
+                key : path.slice(-1)[0],
+                isRoot : path.length === 0,
+                level : path.length,
+                circular : null,
+                update : function (x, stopHere) {
+                    if (!state.isRoot) {
+                        state.parent.node[state.key] = x;
+                    }
+                    state.node = x;
+                    if (stopHere) keepGoing = false;
+                },
+                'delete' : function (stopHere) {
+                    delete state.parent.node[state.key];
+                    if (stopHere) keepGoing = false;
+                },
+                remove : function (stopHere) {
+                    if (isArray(state.parent.node)) {
+                        state.parent.node.splice(state.key, 1);
+                    }
+                    else {
+                        delete state.parent.node[state.key];
+                    }
+                    if (stopHere) keepGoing = false;
+                },
+                keys : null,
+                before : function (f) { modifiers.before = f },
+                after : function (f) { modifiers.after = f },
+                pre : function (f) { modifiers.pre = f },
+                post : function (f) { modifiers.post = f },
+                stop : function () { alive = false },
+                block : function () { keepGoing = false }
+            };
+
+            if (!alive) return state;
+
+            function updateState() {
+                if (typeof state.node === 'object' && state.node !== null) {
+                    if (!state.keys || state.node_ !== state.node) {
+                        state.keys = objectKeys(state.node)
+                    }
+
+                    state.isLeaf = state.keys.length == 0;
+
+                    for (var i = 0; i < parents.length; i++) {
+                        if (parents[i].node_ === node_) {
+                            state.circular = parents[i];
+                            break;
+                        }
+                    }
+                }
+                else {
+                    state.isLeaf = true;
+                    state.keys = null;
+                }
+
+                state.notLeaf = !state.isLeaf;
+                state.notRoot = !state.isRoot;
+            }
+
+            updateState();
+
+            // use return values to update if defined
+            var ret = cb.call(state, state.node);
+            if (ret !== undefined && state.update) state.update(ret);
+
+            if (modifiers.before) modifiers.before.call(state, state.node);
+
+            if (!keepGoing) return state;
+
+            if (typeof state.node == 'object'
+                && state.node !== null && !state.circular) {
+                parents.push(state);
+
+                updateState();
+
+                forEach(state.keys, function (key, i) {
+                    path.push(key);
+
+                    if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
+
+                    var child = walker(state.node[key]);
+                    if (immutable && hasOwnProperty.call(state.node, key)) {
+                        state.node[key] = child.node;
+                    }
+
+                    child.isLast = i == state.keys.length - 1;
+                    child.isFirst = i == 0;
+
+                    if (modifiers.post) modifiers.post.call(state, child);
+
+                    path.pop();
+                });
+                parents.pop();
+            }
+
+            if (modifiers.after) modifiers.after.call(state, state.node);
+
+            return state;
+        })(root).node;
+    }
+
+    function copy (src) {
+        if (typeof src === 'object' && src !== null) {
+            var dst;
+
+            if (isArray(src)) {
+                dst = [];
+            }
+            else if (isDate(src)) {
+                dst = new Date(src.getTime ? src.getTime() : src);
+            }
+            else if (isRegExp(src)) {
+                dst = new RegExp(src);
+            }
+            else if (isError(src)) {
+                dst = { message: src.message };
+            }
+            else if (isBoolean(src)) {
+                dst = new Boolean(src);
+            }
+            else if (isNumber(src)) {
+                dst = new Number(src);
+            }
+            else if (isString(src)) {
+                dst = new String(src);
+            }
+            else if (Object.create && Object.getPrototypeOf) {
+                dst = Object.create(Object.getPrototypeOf(src));
+            }
+            else if (src.constructor === Object) {
+                dst = {};
+            }
+            else {
+                var proto =
+                        (src.constructor && src.constructor.prototype)
+                            || src.__proto__
+                            || {}
+                    ;
+                var T = function () {};
+                T.prototype = proto;
+                dst = new T;
+            }
+
+            forEach(objectKeys(src), function (key) {
+                dst[key] = src[key];
+            });
+            return dst;
+        }
+        else return src;
+    }
+
+    var objectKeys = Object.keys || function keys (obj) {
+        var res = [];
+        for (var key in obj) res.push(key)
+        return res;
+    };
+
+    function toS (obj) { return Object.prototype.toString.call(obj) }
+    function isDate (obj) { return toS(obj) === '[object Date]' }
+    function isRegExp (obj) { return toS(obj) === '[object RegExp]' }
+    function isError (obj) { return toS(obj) === '[object Error]' }
+    function isBoolean (obj) { return toS(obj) === '[object Boolean]' }
+    function isNumber (obj) { return toS(obj) === '[object Number]' }
+    function isString (obj) { return toS(obj) === '[object String]' }
+
+    var isArray = Array.isArray || function isArray (xs) {
+        return Object.prototype.toString.call(xs) === '[object Array]';
+    };
+
+    var forEach = function (xs, fn) {
+        if (xs.forEach) return xs.forEach(fn)
+        else for (var i = 0; i < xs.length; i++) {
+            fn(xs[i], i, xs);
+        }
+    };
+
+    forEach(objectKeys(Traverse.prototype), function (key) {
+        traverse[key] = function (obj) {
+            var args = [].slice.call(arguments, 1);
+            var t = new Traverse(obj);
+            return t[key].apply(t, args);
+        };
+    });
+
+    var hasOwnProperty = Object.hasOwnProperty || function (obj, key) {
+        return key in obj;
+    };
+})();