src/main/scopes.js
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);
}
};
}