From 029e5c1ec39fd35c9edf74c680f7c742e12486f0 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Fri, 19 Sep 2014 12:19:31 +1000 Subject: Initial commit - minimal, synchronous, injector --- injector.js | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 injector.js (limited to 'injector.js') 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; -- cgit v1.2.3