betajs/betajs-scoped

View on GitHub
src/main/scopes.js

Summary

Maintainability
F
3 days
Test Coverage
function newScope (parent, parentNS, rootNS, globalNS) {
    
    var self = this;
    var nextScope = null;
    var childScopes = [];
    var parentNamespace = parentNS;
    var rootNamespace = rootNS;
    var globalNamespace = globalNS;
    var localNamespace = newNamespace({tree: true});
    var privateNamespace = newNamespace({tree: false});
    
    var bindings = {
        "global": {
            namespace: globalNamespace
        }, "root": {
            namespace: rootNamespace
        }, "local": {
            namespace: localNamespace
        }, "default": {
            namespace: privateNamespace
        }, "parent": {
            namespace: parentNamespace
        }, "scope": {
            namespace: localNamespace,
            readonly: false
        }
    };
    
    var custom = function (argmts, name, callback) {
        var args = Helper.matchArgs(argmts, {
            options: "object",
            namespaceLocator: true,
            dependencies: "array",
            hiddenDependencies: "array",
            callback: true,
            context: "object"
        });
        
        var options = Helper.extend({
            lazy: this.options.lazy
        }, args.options || {});
        
        var ns = this.resolve(args.namespaceLocator);
        
        var execute = function () {
            this.require(args.dependencies, args.hiddenDependencies, function () {
                var _arguments = [];
                for (var a = 0; a < arguments.length; ++a)
                    _arguments.push(arguments[a]);
                _arguments[_arguments.length - 1].ns = ns;
                if (this.options.compile) {
                    var params = [];
                    for (var i = 0; i < argmts.length; ++i)
                        params.push(Helper.stringify(argmts[i]));
                    this.compiled += this.options.ident + "." + name + "(" + params.join(", ") + ");\n\n";
                }
                if (this.options.dependencies) {
                    this.dependencies[ns.path] = this.dependencies[ns.path] || {};
                    if (args.dependencies) {
                        args.dependencies.forEach(function (dep) {
                            this.dependencies[ns.path][this.resolve(dep).path] = true;
                        }, this);
                    }
                    if (args.hiddenDependencies) {
                        args.hiddenDependencies.forEach(function (dep) {
                            this.dependencies[ns.path][this.resolve(dep).path] = true;
                        }, this);
                    }
                }
                var result = this.options.compile ? {} : args.callback.apply(args.context || this, _arguments);
                callback.call(this, ns, result);
            }, this);
        };
        
        if (options.lazy)
            ns.namespace.lazy(ns.path, execute, this);
        else
            execute.apply(this);

        return this;
    };
    
    /** 
     * This module provides all functionality in a scope.
     * 
     * @module Scoped
     * @access public
     */
    return {
        
        getGlobal: Helper.method(Globals, Globals.getPath),
        setGlobal: Helper.method(Globals, Globals.setPath),
        
        options: {
            lazy: false,
            ident: "Scoped",
            compile: false,
            dependencies: false
        },
        
        compiled: "",
        
        dependencies: {},
        
        
        /**
         * Returns a reference to the next scope that will be obtained by a subScope call.
         * 
         * @return {object} next scope
         */
        nextScope: function () {
            if (!nextScope)
                nextScope = newScope(this, localNamespace, rootNamespace, globalNamespace);
            return nextScope;
        },
        
        /**
         * Creates a sub scope of the current scope and returns it.
         * 
         * @return {object} sub scope
         */
        subScope: function () {
            var sub = this.nextScope();
            childScopes.push(sub);
            nextScope = null;
            return sub;
        },
        
        /**
         * Creates a binding within in the scope. 
         * 
         * @param {string} alias identifier of the new binding
         * @param {string} namespaceLocator identifier of an existing namespace path
         * @param {object} options options for the binding
         * 
         */
        binding: function (alias, namespaceLocator, options) {
            if (!bindings[alias] || !bindings[alias].readonly) {
                var ns;
                if (Helper.typeOf(namespaceLocator) != "string") {
                    ns = {
                        namespace: newNamespace({
                            tree: true,
                            root: namespaceLocator
                        }),
                        path: null    
                    };
                } else
                    ns = this.resolve(namespaceLocator);
                bindings[alias] = Helper.extend(options, ns);
            }
            return this;
        },
        
        
        /**
         * Resolves a name space locator to a name space.
         * 
         * @param {string} namespaceLocator name space locator
         * @return {object} resolved name space
         * 
         */
        resolve: function (namespaceLocator) {
            var parts = namespaceLocator.split(":");
            if (parts.length == 1) {
                throw ("The locator '" + parts[0] + "' requires a namespace.");
            } else {
                var binding = bindings[parts[0]];
                if (!binding)
                    throw ("The namespace '" + parts[0] + "' has not been defined (yet).");
                return {
                    namespace: binding.namespace,
                    path : binding.path && parts[1] ? binding.path + "." + parts[1] : (binding.path || parts[1])
                };
            }
        },

        
        /**
         * Defines a new name space once a list of name space locators is available.
         * 
         * @param {string} namespaceLocator the name space that is to be defined
         * @param {array} dependencies a list of name space locator dependencies (optional)
         * @param {array} hiddenDependencies a list of hidden name space locators (optional)
         * @param {function} callback a callback function accepting all dependencies as arguments and returning the new definition
         * @param {object} context a callback context (optional)
         * 
         */
        define: function () {
            return custom.call(this, arguments, "define", function (ns, result) {
                if (ns.namespace.get(ns.path))
                    throw ("Scoped namespace " + ns.path + " has already been defined. Use extend to extend an existing namespace instead");
                ns.namespace.set(ns.path, result);
            });
        },
        
        
        /**
         * Assume a specific version of a module and fail if it is not met.
         * 
         * @param {string} assumption name space locator
         * @param {string} version assumed version
         * 
         */
        assumeVersion: function () {
            var args = Helper.matchArgs(arguments, {
                assumption: true,
                dependencies: "array",
                callback: true,
                context: "object",
                error: "string"
            });
            var dependencies = args.dependencies || [];
            dependencies.unshift(args.assumption);
            this.require(dependencies, function () {
                var argv = arguments;
                var assumptionValue = argv[0].replace(/[^\d\.]/g, "");
                argv[0] = assumptionValue.split(".");
                for (var i = 0; i < argv[0].length; ++i)
                    argv[0][i] = parseInt(argv[0][i], 10);
                if (Helper.typeOf(args.callback) === "function") {
                    if (!args.callback.apply(args.context || this, args))
                        throw ("Scoped Assumption '" + args.assumption + "' failed, value is " + assumptionValue + (args.error ? ", but assuming " + args.error : ""));
                } else {
                    var version = (args.callback + "").replace(/[^\d\.]/g, "").split(".");
                    for (var j = 0; j < Math.min(argv[0].length, version.length); ++j)
                        if (parseInt(version[j], 10) > argv[0][j])
                            throw ("Scoped Version Assumption '" + args.assumption + "' failed, value is " + assumptionValue + ", but assuming at least " + args.callback);
                }
            });
        },
        
        
        /**
         * Extends a potentially existing name space once a list of name space locators is available.
         * 
         * @param {string} namespaceLocator the name space that is to be defined
         * @param {array} dependencies a list of name space locator dependencies (optional)
         * @param {array} hiddenDependencies a list of hidden name space locators (optional)
         * @param {function} callback a callback function accepting all dependencies as arguments and returning the new additional definitions.
         * @param {object} context a callback context (optional)
         * 
         */
        extend: function () {
            return custom.call(this, arguments, "extend", function (ns, result) {
                ns.namespace.extend(ns.path, result);
            });
        },
                
        
        /**
         * Requires a list of name space locators and calls a function once they are present.
         * 
         * @param {array} dependencies a list of name space locator dependencies (optional)
         * @param {array} hiddenDependencies a list of hidden name space locators (optional)
         * @param {function} callback a callback function accepting all dependencies as arguments
         * @param {object} context a callback context (optional)
         * 
         */
        require: function () {
            var args = Helper.matchArgs(arguments, {
                dependencies: "array",
                hiddenDependencies: "array",
                callback: "function",
                context: "object"
            });
            args.callback = args.callback || function () {};
            var dependencies = args.dependencies || [];
            var allDependencies = dependencies.concat(args.hiddenDependencies || []);
            var count = allDependencies.length;
            var deps = [];
            var environment = {};
            if (count) {
                var f = function (value) {
                    if (this.i < deps.length)
                        deps[this.i] = value;
                    count--;
                    if (count === 0) {
                        deps.push(environment);
                        args.callback.apply(args.context || this.ctx, deps);
                    }
                };
                for (var i = 0; i < allDependencies.length; ++i) {
                    var ns = this.resolve(allDependencies[i]);
                    if (i < dependencies.length)
                        deps.push(null);
                    ns.namespace.obtain(ns.path, f, {
                        ctx: this,
                        i: i
                    });
                }
            } else {
                deps.push(environment);
                args.callback.apply(args.context || this, deps);
            }
            return this;
        },

        
        /**
         * Digest a name space locator, checking whether it has been defined by an external system.
         * 
         * @param {string} namespaceLocator name space locator
         */
        digest: function (namespaceLocator) {
            var ns = this.resolve(namespaceLocator);
            ns.namespace.digest(ns.path);
            return this;
        },
        
        
        /**
         * Returns all unresolved definitions under a namespace locator
         * 
         * @param {string} namespaceLocator name space locator, e.g. "global:"
         * @return {array} list of all unresolved definitions 
         */
        unresolved: function (namespaceLocator) {
            var ns = this.resolve(namespaceLocator);
            return ns.namespace.unresolvedWatchers(ns.path);
        },
        
        /**
         * Exports the scope.
         * 
         * @return {object} exported scope
         */
        __export: function () {
            return {
                parentNamespace: parentNamespace.__export(),
                rootNamespace: rootNamespace.__export(),
                globalNamespace: globalNamespace.__export(),
                localNamespace: localNamespace.__export(),
                privateNamespace: privateNamespace.__export()
            };
        },
        
        /**
         * Imports a scope from an exported scope.
         * 
         * @param {object} data exported scope to be imported
         * 
         */
        __import: function (data) {
            parentNamespace.__import(data.parentNamespace);
            rootNamespace.__import(data.rootNamespace);
            globalNamespace.__import(data.globalNamespace);
            localNamespace.__import(data.localNamespace);
            privateNamespace.__import(data.privateNamespace);
        }
        
    };
    
}