summaryrefslogtreecommitdiff
path: root/injector.js
diff options
context:
space:
mode:
Diffstat (limited to 'injector.js')
-rw-r--r--injector.js133
1 files changed, 133 insertions, 0 deletions
diff --git a/injector.js b/injector.js
new file mode 100644
index 0000000..4d8b293
--- /dev/null
+++ b/injector.js
@@ -0,0 +1,133 @@
+/*global module*/
+var Injector = (function() {
+
+ var Injector = function() {
+ this.specs = {};
+ this.values = {};
+ this.stack = [];
+ this.usedRequestor = null;
+ };
+
+ Injector.prototype.register = function(name, spec) {
+ if (typeof(name) == "function" || name instanceof Function) {
+ this.specs[name.name] = name;
+ } else if (typeof(name) == "string" || name instanceof String) {
+ this.specs[name] = spec;
+ } else {
+ for (var key in name) {
+ this.register(key, name[key]);
+ }
+ }
+ return this;
+ };
+
+ Injector.prototype.get = function(name) {
+ if (name in this.specs) {
+ var oldUsedRequestor = this.usedRequestor;
+ this.usedRequestor = false;
+ this.stack.push(name);
+ try {
+ throwIfCyclic(name, this.stack);
+ var result = this.invoke(this.specs[name]);
+ if (this.usedRequestor) {
+ return result;
+ } else {
+ delete this.specs[name];
+ return (this.values[name] = result);
+ }
+ } catch (e) {
+ throw (e instanceof InjectorError
+ ? e
+ : new InjectorError("Error constructing value for " + stackString(this.stack), e));
+ } finally {
+ this.stack.pop();
+ this.usedRequestor = oldUsedRequestor;
+ }
+ }
+ if (name in this.values) {
+ return this.values[name];
+ } else {
+ throw new InjectorError("Dependency " + name + " not found");
+ }
+ };
+
+ Injector.prototype.requestor = function() {
+ switch (this.stack.length) {
+ case 0:
+ throw new InjectorError("Cannot use requestor for invoked function - none exists");
+ case 1:
+ throw new InjectorError("Cannot use requestor for top-level constructor - none exists");
+ default:
+ if (this.usedRequestor === false)
+ this.usedRequestor = true;
+ return this.stack[this.stack.length - 2];
+ }
+ };
+
+ Injector.prototype.invoke = function(spec) {
+ var parsed = parseSpec(spec);
+ var fn = parsed[0];
+ var dependencies = parsed[1];
+ return fn.apply(this, dependencies.map(function(dependency) {
+ return this.get(dependency);
+ }, this));
+ };
+
+ Injector.prototype.construct = function() {
+ };
+
+
+ var parsingRegex = /^function[^(]*\(([^)]*)\)/;
+ var parseFnArgs = function(fn) {
+ var parts = parsingRegex.exec(fn.toString().replace(/\s+/g, ""));
+ if (parts == null) {
+ throw new Error("Unable to parse fn definition");
+ } else {
+ return parts[1] ? parts[1].split(/,/) : [];
+ }
+ };
+
+ var parseSpec = function(spec) {
+ var fn, dependencies;
+ if (typeof(spec) == "function" || spec instanceof Function) {
+ dependencies = parseFnArgs(spec);
+ fn = spec;
+ } else {
+ fn = spec[spec.length - 1];
+ dependencies = spec.slice(0, spec.length - 1);
+ }
+ return [fn, dependencies];
+ };
+
+
+ var stackString = function(stack) {
+ stack.reverse();
+ var result = stack.join(" <- ");
+ stack.reverse();
+ return result;
+ };
+
+ var throwIfCyclic = function(name, stack) {
+ if (stack.indexOf(name) != stack.length - 1) {
+ throw new InjectorError("Cyclic dependency detected " + stackString(stack));
+ }
+ };
+
+
+ var InjectorError = function(message, cause) {
+ this.name = "InjectorError";
+ this.message = message;
+ this.cause = cause;
+ };
+ InjectorError.prototype = new Error();
+ InjectorError.prototype.constructor = InjectorError;
+ InjectorError.prototype.toString = function() {
+ return "InjectorError: " + this.message + (this.cause ? " [caused by " + this.cause + "]" : "");
+ };
+
+ return Injector;
+})();
+
+
+if (typeof(module) !== "undefined")
+ module.exports = Injector;