From fab0b505c608d2a553c1ca74553e6a433e453a5d Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Mon, 29 Sep 2014 01:32:38 +1000 Subject: Rename to Scorpion: sounds cooler --- Gruntfile.js | 10 +- bower.json | 2 +- injector-tests.js | 359 --------------------------------------------- injector.js | 426 ------------------------------------------------------ injector.min.js | 1 - package.json | 2 +- scorpion-tests.js | 359 +++++++++++++++++++++++++++++++++++++++++++++ scorpion.js | 426 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ scorpion.min.js | 1 + 9 files changed, 793 insertions(+), 793 deletions(-) delete mode 100644 injector-tests.js delete mode 100644 injector.js delete mode 100644 injector.min.js create mode 100644 scorpion-tests.js create mode 100644 scorpion.js create mode 100644 scorpion.min.js diff --git a/Gruntfile.js b/Gruntfile.js index e2febff..4710066 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -32,8 +32,8 @@ module.exports = function (grunt) { }, test: { files: [ - "injector.js", - "injector-tests.js" + "scorpion.js", + "scorpion-tests.js" ], tasks: ['test'] } @@ -45,14 +45,14 @@ module.exports = function (grunt) { reporter: 'min', require: [ function(){ - Injector = require('./injector'); + Scorpion = require('./scorpion'); expect = require('chai').expect; } ] }, src: [ - "injector.js", - "injector-tests.js" + "scorpion.js", + "scorpion-tests.js" ] } } diff --git a/bower.json b/bower.json index 37f58d6..7ad5f8c 100644 --- a/bower.json +++ b/bower.json @@ -1,5 +1,5 @@ { - "name": "injector", + "name": "scorpion", "version": "0.1.0" } diff --git a/injector-tests.js b/injector-tests.js deleted file mode 100644 index e94126f..0000000 --- a/injector-tests.js +++ /dev/null @@ -1,359 +0,0 @@ -/*global describe,it,expect,beforeEach,Injector, setTimeout*/ - -describe("deferred", function() { - var deferred, promise; - beforeEach(function() { - deferred = Injector.defer(); - promise = deferred.promise; - }); - this.timeout(100); - - describe("promise", function() { - it("should have a 'then' method", function() { - expect(promise.then).not.to.equal(undefined); - }); - - it("should be able to chain 'then' methods", function() { - var promise1 = promise; - var promise2 = promise1.then(function(x) { - return x + 1; - }); - var promise3 = promise1.then(function(x) { - return x + 1; - }); - expect(promise1.then).not.to.equal(undefined); - expect(promise2.then).not.to.equal(undefined); - expect(promise3.then).not.to.equal(undefined); - }); - }); - - describe("resolution", function() { - - it("should resolve asynchronously", function(done) { - var hasRun = false; - promise.then(function(val) { - hasRun = true; - return val; - }); - deferred.resolve("value"); - expect(hasRun).to.equal(false); - setTimeout(function() { - expect(hasRun).to.equal(true); - done(); - }); - }); - - it("should propagate through multiple promises", function(done) { - var promise2 = promise.then(function(value) { - return value + 1; - }); - var promise3 = promise2.then(function(value) { - return value + 1; - }); - promise3.then(function(value) { - expect(value).to.equal(3); - done(); - }); - deferred.resolve(1); - }); - - it("should call all registered handlers", function(done) { - var called = 0; - promise.then(function() {called++;}); - promise.then(function() {called++;}); - promise.then(function() {called++;}); - expect(called).to.equal(0); - deferred.resolve(null); - setTimeout(function() { - expect(called).to.equal(3); - done(); - }); - }); - - it("should call handlers, even when added after resolution", function(done) { - deferred.resolve(null); - promise.then(function() { - done(); - }); - }); - - it("should not allow multiple resolution", function() { - deferred.resolve(1); - expect(function() { - deferred.resolve(2); - }).to.throw(); - }); - - it("should not allow rejecting after resolving", function() { - deferred.resolve(1); - expect(function() { - deferred.reject(2); - }).to.throw(); - }); - - it("should handle a returned promise by 'unwrapping' it", function(done) { - promise.then(function(value) { - var deferred = Injector.defer(); - deferred.resolve(value + 1); - return deferred.promise; - }).then(function(value) { - try { - expect(value).to.equal(2); - done(); - } catch (e) { - done(e); - } - }); - deferred.resolve(1); - }); - }); - - describe("rejection", function() { - it("should reject asynchronously", function(done) { - var hasRun = false; - promise.then(null, function(val) { - hasRun = true; - return val; - }); - deferred.reject("value"); - expect(hasRun).to.equal(false); - setTimeout(function() { - expect(hasRun).to.equal(true); - done(); - }); - }); - - - it("should be turned into resolution by a handler", function(done) { - var promise2 = promise.then(function(value) { - return value + 1; - }, function(value) { - return value + 100; - }); - promise2.then(function(value) { - try { - expect(value).to.equal(101); - done(); - } catch (e) { - done(e); - } - }); - deferred.reject(1); - }); - - it("should call all registered handlers", function(done) { - var called = 0; - promise.then(null, function() {called++;}); - promise.then(null, function() {called++;}); - promise.then(null, function() {called++;}); - expect(called).to.equal(0); - deferred.reject(null); - setTimeout(function() { - expect(called).to.equal(3); - done(); - }); - }); - - it("should not allow multiple rejection", function() { - deferred.reject(1); - expect(function() { - deferred.reject(2); - }).to.throw(); - }); - - it("should not allow resolving after rejecting", function() { - deferred.reject(1); - expect(function() { - deferred.resolve(2); - }).to.throw(); - }); - - it("should call handlers, even when added after resolution", function(done) { - deferred.reject(null); - promise.then(null, function() { - done(); - }); - }); - - it("should handle a returned promise by 'unwrapping' in the error case", function(done) { - promise.then(function(value) { - var deferred = Injector.defer(); - deferred.reject(value + 1); - return deferred.promise; - }).then(null, function(value) { - try { - expect(value).to.equal(2); - done(); - } catch (e) { - done(e); - } - }); - deferred.resolve(1); - }); - }); - - describe("waitForAll", function() { - it("should resolve with an array if all successful", function(done) { - var d1 = Injector.defer(), - d2 = Injector.defer(), - d3 = Injector.defer(); - Injector - .waitForAll([d1.promise, d2.promise, d3.promise]) - .then(function(values) { - if (values.length == 3 && - values[0] === 0 && - values[1] === 1 && - values[2] === 2) { - done(); - } else { - done(new Error("Error in resolved result: " + values)); - } - }, done); - d1.resolve(0); - d2.resolve(1); - d3.resolve(2); - }); - - it("should reject with an object of successes/errors if any fail", function(done) { - var d1 = Injector.defer(), - d2 = Injector.defer(), - d3 = Injector.defer(); - Injector - .waitForAll([d1.promise, d2.promise, d3.promise]) - .then(function(values) { - done(new Error("incorrectly resolved promise")); - }, function(e) { - if (e.errors && e.values && - e.values[0] === 0 && - e.values[1] === 1 && - e.errors[2] === 2) { - done(); - } else { - done(new Error("incorrect reject value")); - } - }); - d1.resolve(0); - d2.resolve(1); - d3.reject(2); - }); - }); -}); - -describe("injector", function() { - var injector, valuePlugin; - beforeEach(function() { - valuePlugin = new Injector.ValuePlugin(); - injector = new Injector([valuePlugin]); - }); - this.timeout(100); - - describe("value registration and retrieval", function() { - it("works", function(done) { - injector.register("a", function(){return "the value of a";}); - injector.get("a").then(function(value){ - if (value == "the value of a") - done(); - else - done(new Error("incorrect value for a (" + value + ")")); - }, done); - }); - - it("with dependencies works", function(done) { - injector - .register("a", function(){return "the value of a";}) - .register("b", ["a", function(a){return a.replace(/a/g, "b");}]); - injector.get("b").then(function(value){ - if (value == "the vblue of b") - done(); - else - done(new Error("incorrect value for b (" + value + ")")); - }, done); - }); - }); - - describe("destroy handlers", function() { - it("work with single gets", function(done) { - injector.register("a", function(){return "a";}); - expect("a" in valuePlugin.values).to.equal(false); - var result = injector.get("a"); - result.then(function() { - if ("a" in valuePlugin.values) { - result.destroy().then(function() { - if ("a" in valuePlugin.values) { - done(new Error("Value found for a after destruction")); - } else { - done(); - } - }); - } else { - done(new Error("No value found for a")); - } - }, done); - }); - - it("works with multiple gets", function(done) { - injector.register("a", function(){return "a";}); - expect("a" in valuePlugin.values).to.equal(false); - var result = [injector.get("a"), injector.get("a"), injector.get("a")]; - result[1].destroy(); - result[2].destroy(); - result[0].then(function() { - if ("a" in valuePlugin.values) { - result[0].destroy().then(function() { - if ("a" in valuePlugin.values) { - done(new Error("Value found for a after destruction")); - } else { - done(); - } - }); - } else { - done(new Error("No value found for a")); - } - }, done); - }); - - it("works through dependencies", function(done) { - injector - .register("a", function(){return "a";}) - .register("b", ["a", function(a){return a + "b";}]); - expect("a" in valuePlugin.values).to.equal(false); - var result = injector.get("b"); - result.then(function() { - if ("a" in valuePlugin.values) { - result.destroy().then(function() { - if ("a" in valuePlugin.values) { - done(new Error("Value found for a after destruction")); - } else { - done(); - } - }); - } else { - done(new Error("No value found for a")); - } - }, done); - }); - }); - - describe("cyclic dependency detection", function() { - it("detects simple cycles", function(done) { - injector.register("a", ["b", function(b) {return b;}]); - injector.register("b", ["a", function(a) {return a;}]); - injector.get("a").then(function() { - done(new Error("Promise should have been rejected")); - }, function(e) { - done(); - }); - }); - - it("detects cycles with intermediate nodes", function(done) { - injector.register("a", ["c", function(c) {return c;}]); - injector.register("b", ["a", function(a) {return a;}]); - injector.register("c", ["b", function(b) {return b;}]); - injector.get("a").then(function() { - done(new Error("Promise should have been rejected")); - }, function(e) { - done(); - }); - }); - }); -}); diff --git a/injector.js b/injector.js deleted file mode 100644 index e644539..0000000 --- a/injector.js +++ /dev/null @@ -1,426 +0,0 @@ -/*global module, setTimeout*/ - -var Injector = (function() { - - var Deferred = (function() { - var defer = function(fn, value) {setTimeout(function() {fn(value);});}; - var runFn = function(fn, deferred, action) { - return function(value) { - try { - var result = fn(value); - if (result && result.then) { - result.then(function(value) { - deferred.resolve(value); - }, function(value) { - deferred.reject(value); - }); - } else { - deferred.resolve(result); - } - } catch (e) { - deferred.reject(e); - } - }; - }; - - var Promise = function() { - this.resolved = false; - this.rejected = false; - this.onSuccess = []; - this.onError = []; - }; - Promise.prototype.then = function(success, error) { - success = success || function(x) {return x;}; - error = error || function(x) {throw x;}; - var deferred = new Deferred(); - var successFn = runFn(success, deferred); - var errorFn = runFn(error, deferred); - if (this.resolved) { - defer(successFn, this.value); - } else if (this.rejected) { - defer(errorFn, this.value); - } else { - if (this.onSuccess != null) - this.onSuccess.push(successFn); - if (this.onError != null) - this.onError.push(errorFn); - } - return deferred.promise; - }; - - var Deferred = function() { - this.promise = new Promise(); - }; - Deferred.prototype.resolve = function(value) { - if (this.promise.resolved) { - throw new Error("Cannot re-resolve already resolved promise"); - } else if (this.promise.rejected) { - throw new Error("Cannot resolve a rejected promise"); - } else { - this.promise.resolved = true; - this.promise.value = value; - var handlers = this.promise.onSuccess; - this.promise.onSuccess = null; - this.promise.onError = null; - setTimeout(function() { - handlers.forEach(function(handler) { - handler(value); - }); - }); - } - }; - Deferred.prototype.reject = function(value) { - if (this.promise.resolved) { - throw new Error("Cannot reject an already resolved promise"); - } else if (this.promise.rejected) { - throw new Error("Cannot re-reject a rejected promise"); - } else { - this.promise.rejected = true; - this.promise.value = value; - var handlers = this.promise.onError; - this.promise.onSuccess = null; - this.promise.onError = null; - setTimeout(function() { - handlers.forEach(function(handler) { - handler(value); - }); - }); - } - }; - - Deferred.waitForAll = function(promises) { - var deferred = new Deferred(); - var successes = 0, errors = 0; - var errorResults = {}, successResults = {}; - var expected = promises.length; - promises.forEach(function(promise, i) { - promise.then(function(value) { - successResults[i] = value; - successes++; - maybeFinish(); - }, function(error) { - errorResults[i] = error; - errors++; - maybeFinish(); - }); - }); - var maybeFinish = function() { - if (successes == expected) { - var array = []; - for (var i = 0, l = expected; i < l; ++i) - array.push(successResults[i]); - deferred.resolve(array); - } else if (successes + errors == expected) { - deferred.reject({ - errors: errorResults, - values: successResults - }); - } - }; - maybeFinish(); - return deferred.promise; - }; - - return Deferred; - })(); - - var DepsParser = (function() { - var fnArgRegex = /^function[^(]*\(([^)]*)\)/; - var parseFnArgs = function(fn) { - var parts = fnArgRegex.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 parseDep = function(dep) { - var parts = dep.split("!"); - if (parts.length == 0) { - throw new Error("Invalid dependency: " + dep); - } else if (parts.length == 1) { - return {prefix: "", name: parts[0]}; - } else { - return {prefix: parts[0], name: parts.slice(1).join("!")}; - } - }; - - return { - spec: parseSpec, - dep: parseDep - }; - })(); - - var Injector = (function(Deferred, DepsParser) { - - var Injector = function(plugins) { - // plugins are a list to be executed in order - this.plugins = plugins; - }; - - Injector.rejected = function(message, cause) { - var deferred = this.defer(); - deferred.reject(this.error(message, cause)); - return deferred.promise; - }; - Injector.prototype.rejected = Injector.rejected; - - Injector.resolved = function(value) { - var deferred = this.defer(); - deferred.resolve(value); - return deferred.promise; - }; - Injector.prototype.resolved = Injector.resolved; - - Injector.prototype.get = function(dep, stack) { - stack = [dep].concat(stack || []); - if (stack && stack.lastIndexOf(dep) != 0) { - return this.rejected("Cyclic dependency: " + stack.join(" <- ")); - } - for (var i = 0, l = this.plugins.length; i < l; ++i) { - var value = this.plugins[i].get(this, dep, stack); - if (value) { - if (!value.destroy) - value.destroy = function(){return Injector.resolved(true);}; - return value; - } - } - return this.rejected("Unknown dependency: " + stack.join(" <- ")); - }; - - Injector.prototype.register = function(name, value) { - for (var i = 0, l = this.plugins.length; i < l; ++i) { - if (this.plugins[i].register(this, name, value)) - return this; - } - throw this.error("No plugin handled registration of: " + name); - return this; - }; - - Injector.prototype.invoke = function(spec) { - var injector = this; - var parsed = this.parseSpec(spec); - var fn = parsed[0]; - var dependencies = parsed[1]; - var depPromises = dependencies.map(function(dep) { - return injector.get(dep, []); - }); - var depPromise = this.waitForAll(depPromises); - var result = depPromise.then(function(results) { - return fn.apply(injector, results); - }); - result.destroy = function() { - return Injector.waitForAll(depPromises.map(function(promise) { - return promise.destroy(); - })).then(function() { - return true; - }); - }; - return result; - }; - - - - /* static utility functions */ - - Injector.error = function(message, cause) {return new InjectorError(message, cause);}; - Injector.prototype.error = Injector.error; - - Injector.defer = function() {return new Deferred();}; - Injector.prototype.defer = Injector.defer; - - Injector.parseSpec = DepsParser.spec; - Injector.prototype.parseSpec = DepsParser.spec; - - Injector.waitForAll = Deferred.waitForAll; - Injector.prototype.waitForAll = Deferred.waitForAll; - - /* the injector error type */ - - 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; - - })(Deferred, DepsParser); - - Injector.prefixPlugin = function(prefix, plugin) { - return { - register: function(injector, name, spec) { - if (name.indexOf(prefix) == 0) { - return plugin.register(injector, - name.substr(prefix.length), - spec); - } else { - return false; - } - }, - get: function(injector, name, stack) { - if (name.indexOf(prefix) == 0) { - return plugin.get(injector, - name.substr(prefix.length), - stack); - } else { - return null; - } - } - }; - }; - - Injector.DOMPlugin = (function() { - var DOMPlugin = function() { - this.aliases = {}; - }; - - DOMPlugin.prototype.register = function(injector, name, spec) { - this.aliases[name] = spec; - return true; - }; - - DOMPlugin.prototype.get = function(injector, name, stack) { - var deferred = injector.defer(); - var interval = setInterval(function() { - var obj = $(this.aliases[name] || name); - if (obj.length) { - clearInterval(interval); - deferred.resolve(obj); - } - }.bind(this), 100); - return deferred.promise; - }; - - return DOMPlugin; - })(); - - Injector.HTTPPlugin = (function() { - var HTTPPlugin = function() { - this.aliases = {}; - }; - - HTTPPlugin.prototype.register = function(injector, name, spec) { - this.aliases[name] = spec; - return true; - }; - - HTTPPlugin.prototype.get = function(injector, name, stack) { - var deferred = injector.defer(); - $.ajax(this.aliases[name] || name).then(function(result) { - deferred.resolve(result); - // deferred.resolve.bind(deferred); - }, deferred.reject.bind(deferred)); - deferred.promise.destroy = function() { - return Injector.resolved(true); - }; - return deferred.promise; - }; - - return HTTPPlugin; - })(); - - Injector.ValuePlugin = (function() { - var ValuePlugin = function() { - this.specs = {}; - this.values = {}; - }; - - ValuePlugin.prototype.register = function(injector, name, spec) { - this.specs[name] = spec; - return true; - }; - - ValuePlugin.prototype.get = function(injector, name, stack) { - if (name in this.values) { - this.values[name].references++; - return this.values[name]; - } else if (name in this.specs) { - var spec = this.specs[name]; - var parsed = injector.parseSpec(spec); - var constructor = parsed[0]; - var dependencies = parsed[1]; - var depPromises = dependencies.map(function(dep) { - return injector.get(dep, [name].concat(stack)); - }); - var depPromise = injector.waitForAll(depPromises); - - var onDestroy = null; - var readRequestor = false; - var deferred = injector.defer(); - var result = deferred.promise; - depPromise.then(function(results) { - var wrappedInjector = Object.create(injector); - wrappedInjector.requestor = function() { - readRequestor = true; - return stack[1]; - }; - try { - deferred.resolve(constructor.apply(wrappedInjector, results)); - } catch (e) { - deferred.reject(e); - } finally { - onDestroy = wrappedInjector.onDestroy; - if (result.references <= 0 && onDestroy) - onDestroy(); - } - }, function(e) { - deferred.reject(injector.error("Error constructing " + name)); - }); - - result.references = 1; - var values = this.values; - result.destroy = function() { - this.references--; - if (this.references <= 0) { - if (onDestroy) - onDestroy(); - delete values[name]; - if (!result.resolved && !result.rejected) - deferred.reject(injector.error("Promise destroyed before value completed construction")); - return injector.waitForAll(depPromises.map(function(promise) { - return promise.destroy(); - })).then(function() { - return true; - }); - } else { - return injector.resolved(true); - } - }; - if (readRequestor) { - return result; - } else { - return this.values[name] = result; - } - } else { - return null; - } - }; - - return ValuePlugin; - })(); - - - return Injector; -})(); - -if (typeof(module) !== "undefined") - module.exports = Injector; diff --git a/injector.min.js b/injector.min.js deleted file mode 100644 index 2f09e0f..0000000 --- a/injector.min.js +++ /dev/null @@ -1 +0,0 @@ -/*! injector 0.1.0 */var Injector=function(){var a=function(){var a=function(a,b){setTimeout(function(){a(b)})},b=function(a,b){return function(c){try{var d=a(c);d&&d.then?d.then(function(a){b.resolve(a)},function(a){b.reject(a)}):b.resolve(d)}catch(e){b.reject(e)}}},c=function(){this.resolved=!1,this.rejected=!1,this.onSuccess=[],this.onError=[]};c.prototype.then=function(c,e){c=c||function(a){return a},e=e||function(a){throw a};var f=new d,g=b(c,f),h=b(e,f);return this.resolved?a(g,this.value):this.rejected?a(h,this.value):(null!=this.onSuccess&&this.onSuccess.push(g),null!=this.onError&&this.onError.push(h)),f.promise};var d=function(){this.promise=new c};return d.prototype.resolve=function(a){if(this.promise.resolved)throw new Error("Cannot re-resolve already resolved promise");if(this.promise.rejected)throw new Error("Cannot resolve a rejected promise");this.promise.resolved=!0,this.promise.value=a;var b=this.promise.onSuccess;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.prototype.reject=function(a){if(this.promise.resolved)throw new Error("Cannot reject an already resolved promise");if(this.promise.rejected)throw new Error("Cannot re-reject a rejected promise");this.promise.rejected=!0,this.promise.value=a;var b=this.promise.onError;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.waitForAll=function(a){var b=new d,c=0,e=0,f={},g={},h=a.length;a.forEach(function(a,b){a.then(function(a){g[b]=a,c++,i()},function(a){f[b]=a,e++,i()})});var i=function(){if(c==h){for(var a=[],d=0,i=h;i>d;++d)a.push(g[d]);b.resolve(a)}else c+e==h&&b.reject({errors:f,values:g})};return i(),b.promise},d}(),b=function(){var a=/^function[^(]*\(([^)]*)\)/,b=function(b){var c=a.exec(b.toString().replace(/\s+/g,""));if(null==c)throw new Error("Unable to parse fn definition");return c[1]?c[1].split(/,/):[]},c=function(a){var c,d;return"function"==typeof a||a instanceof Function?(d=b(a),c=a):(c=a[a.length-1],d=a.slice(0,a.length-1)),[c,d]},d=function(a){var b=a.split("!");if(0==b.length)throw new Error("Invalid dependency: "+a);return 1==b.length?{prefix:"",name:b[0]}:{prefix:b[0],name:b.slice(1).join("!")}};return{spec:c,dep:d}}(),c=function(a,b){var c=function(a){this.plugins=a};c.rejected=function(a,b){var c=this.defer();return c.reject(this.error(a,b)),c.promise},c.prototype.rejected=c.rejected,c.resolved=function(a){var b=this.defer();return b.resolve(a),b.promise},c.prototype.resolved=c.resolved,c.prototype.get=function(a,b){if(b=[a].concat(b||[]),b&&0!=b.lastIndexOf(a))return this.rejected("Cyclic dependency: "+b.join(" <- "));for(var d=0,e=this.plugins.length;e>d;++d){var f=this.plugins[d].get(this,a,b);if(f)return f.destroy||(f.destroy=function(){return c.resolved(!0)}),f}return this.rejected("Unknown dependency: "+b.join(" <- "))},c.prototype.register=function(a,b){for(var c=0,d=this.plugins.length;d>c;++c)if(this.plugins[c].register(this,a,b))return this;throw this.error("No plugin handled registration of: "+a)},c.prototype.invoke=function(a){var b=this,d=this.parseSpec(a),e=d[0],f=d[1],g=f.map(function(a){return b.get(a,[])}),h=this.waitForAll(g),i=h.then(function(a){return e.apply(b,a)});return i.destroy=function(){return c.waitForAll(g.map(function(a){return a.destroy()})).then(function(){return!0})},i},c.error=function(a,b){return new d(a,b)},c.prototype.error=c.error,c.defer=function(){return new a},c.prototype.defer=c.defer,c.parseSpec=b.spec,c.prototype.parseSpec=b.spec,c.waitForAll=a.waitForAll,c.prototype.waitForAll=a.waitForAll;var d=function(a,b){this.name="InjectorError",this.message=a,this.cause=b};return d.prototype=new Error,d.prototype.constructor=d,d.prototype.toString=function(){return"InjectorError: "+this.message+(this.cause?" [caused by "+this.cause+"]":"")},c}(a,b);return c.prefixPlugin=function(a,b){return{register:function(c,d,e){return 0==d.indexOf(a)?b.register(c,d.substr(a.length),e):!1},get:function(c,d,e){return 0==d.indexOf(a)?b.get(c,d.substr(a.length),e):null}}},c.DOMPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b){var c=a.defer(),d=setInterval(function(){var a=$(this.aliases[b]||b);a.length&&(clearInterval(d),c.resolve(a))}.bind(this),100);return c.promise},a}(),c.HTTPPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b){var d=a.defer();return $.ajax(this.aliases[b]||b).then(function(a){d.resolve(a)},d.reject.bind(d)),d.promise.destroy=function(){return c.resolved(!0)},d.promise},a}(),c.ValuePlugin=function(){var a=function(){this.specs={},this.values={}};return a.prototype.register=function(a,b,c){return this.specs[b]=c,!0},a.prototype.get=function(a,b,c){if(b in this.values)return this.values[b].references++,this.values[b];if(b in this.specs){var d=this.specs[b],e=a.parseSpec(d),f=e[0],g=e[1],h=g.map(function(d){return a.get(d,[b].concat(c))}),i=a.waitForAll(h),j=null,k=!1,l=a.defer(),m=l.promise;i.then(function(b){var d=Object.create(a);d.requestor=function(){return k=!0,c[1]};try{l.resolve(f.apply(d,b))}catch(e){l.reject(e)}finally{j=d.onDestroy,m.references<=0&&j&&j()}},function(){l.reject(a.error("Error constructing "+b))}),m.references=1;var n=this.values;return m.destroy=function(){return this.references--,this.references<=0?(j&&j(),delete n[b],m.resolved||m.rejected||l.reject(a.error("Promise destroyed before value completed construction")),a.waitForAll(h.map(function(a){return a.destroy()})).then(function(){return!0})):a.resolved(!0)},k?m:this.values[b]=m}return null},a}(),c}();"undefined"!=typeof module&&(module.exports=Injector); \ No newline at end of file diff --git a/package.json b/package.json index 2ff7b5b..f8e06b2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "injector", + "name": "scorpion", "version": "0.1.0", "dependencies": {}, "devDependencies": { diff --git a/scorpion-tests.js b/scorpion-tests.js new file mode 100644 index 0000000..0bbe721 --- /dev/null +++ b/scorpion-tests.js @@ -0,0 +1,359 @@ +/*global describe,it,expect,beforeEach,Scorpion, setTimeout*/ + +describe("deferred", function() { + var deferred, promise; + beforeEach(function() { + deferred = Scorpion.defer(); + promise = deferred.promise; + }); + this.timeout(100); + + describe("promise", function() { + it("should have a 'then' method", function() { + expect(promise.then).not.to.equal(undefined); + }); + + it("should be able to chain 'then' methods", function() { + var promise1 = promise; + var promise2 = promise1.then(function(x) { + return x + 1; + }); + var promise3 = promise1.then(function(x) { + return x + 1; + }); + expect(promise1.then).not.to.equal(undefined); + expect(promise2.then).not.to.equal(undefined); + expect(promise3.then).not.to.equal(undefined); + }); + }); + + describe("resolution", function() { + + it("should resolve asynchronously", function(done) { + var hasRun = false; + promise.then(function(val) { + hasRun = true; + return val; + }); + deferred.resolve("value"); + expect(hasRun).to.equal(false); + setTimeout(function() { + expect(hasRun).to.equal(true); + done(); + }); + }); + + it("should propagate through multiple promises", function(done) { + var promise2 = promise.then(function(value) { + return value + 1; + }); + var promise3 = promise2.then(function(value) { + return value + 1; + }); + promise3.then(function(value) { + expect(value).to.equal(3); + done(); + }); + deferred.resolve(1); + }); + + it("should call all registered handlers", function(done) { + var called = 0; + promise.then(function() {called++;}); + promise.then(function() {called++;}); + promise.then(function() {called++;}); + expect(called).to.equal(0); + deferred.resolve(null); + setTimeout(function() { + expect(called).to.equal(3); + done(); + }); + }); + + it("should call handlers, even when added after resolution", function(done) { + deferred.resolve(null); + promise.then(function() { + done(); + }); + }); + + it("should not allow multiple resolution", function() { + deferred.resolve(1); + expect(function() { + deferred.resolve(2); + }).to.throw(); + }); + + it("should not allow rejecting after resolving", function() { + deferred.resolve(1); + expect(function() { + deferred.reject(2); + }).to.throw(); + }); + + it("should handle a returned promise by 'unwrapping' it", function(done) { + promise.then(function(value) { + var deferred = Scorpion.defer(); + deferred.resolve(value + 1); + return deferred.promise; + }).then(function(value) { + try { + expect(value).to.equal(2); + done(); + } catch (e) { + done(e); + } + }); + deferred.resolve(1); + }); + }); + + describe("rejection", function() { + it("should reject asynchronously", function(done) { + var hasRun = false; + promise.then(null, function(val) { + hasRun = true; + return val; + }); + deferred.reject("value"); + expect(hasRun).to.equal(false); + setTimeout(function() { + expect(hasRun).to.equal(true); + done(); + }); + }); + + + it("should be turned into resolution by a handler", function(done) { + var promise2 = promise.then(function(value) { + return value + 1; + }, function(value) { + return value + 100; + }); + promise2.then(function(value) { + try { + expect(value).to.equal(101); + done(); + } catch (e) { + done(e); + } + }); + deferred.reject(1); + }); + + it("should call all registered handlers", function(done) { + var called = 0; + promise.then(null, function() {called++;}); + promise.then(null, function() {called++;}); + promise.then(null, function() {called++;}); + expect(called).to.equal(0); + deferred.reject(null); + setTimeout(function() { + expect(called).to.equal(3); + done(); + }); + }); + + it("should not allow multiple rejection", function() { + deferred.reject(1); + expect(function() { + deferred.reject(2); + }).to.throw(); + }); + + it("should not allow resolving after rejecting", function() { + deferred.reject(1); + expect(function() { + deferred.resolve(2); + }).to.throw(); + }); + + it("should call handlers, even when added after resolution", function(done) { + deferred.reject(null); + promise.then(null, function() { + done(); + }); + }); + + it("should handle a returned promise by 'unwrapping' in the error case", function(done) { + promise.then(function(value) { + var deferred = Scorpion.defer(); + deferred.reject(value + 1); + return deferred.promise; + }).then(null, function(value) { + try { + expect(value).to.equal(2); + done(); + } catch (e) { + done(e); + } + }); + deferred.resolve(1); + }); + }); + + describe("waitForAll", function() { + it("should resolve with an array if all successful", function(done) { + var d1 = Scorpion.defer(), + d2 = Scorpion.defer(), + d3 = Scorpion.defer(); + Scorpion + .waitForAll([d1.promise, d2.promise, d3.promise]) + .then(function(values) { + if (values.length == 3 && + values[0] === 0 && + values[1] === 1 && + values[2] === 2) { + done(); + } else { + done(new Error("Error in resolved result: " + values)); + } + }, done); + d1.resolve(0); + d2.resolve(1); + d3.resolve(2); + }); + + it("should reject with an object of successes/errors if any fail", function(done) { + var d1 = Scorpion.defer(), + d2 = Scorpion.defer(), + d3 = Scorpion.defer(); + Scorpion + .waitForAll([d1.promise, d2.promise, d3.promise]) + .then(function(values) { + done(new Error("incorrectly resolved promise")); + }, function(e) { + if (e.errors && e.values && + e.values[0] === 0 && + e.values[1] === 1 && + e.errors[2] === 2) { + done(); + } else { + done(new Error("incorrect reject value")); + } + }); + d1.resolve(0); + d2.resolve(1); + d3.reject(2); + }); + }); +}); + +describe("injector", function() { + var injector, valuePlugin; + beforeEach(function() { + valuePlugin = new Scorpion.ValuePlugin(); + injector = new Scorpion([valuePlugin]); + }); + this.timeout(100); + + describe("value registration and retrieval", function() { + it("works", function(done) { + injector.register("a", function(){return "the value of a";}); + injector.get("a").then(function(value){ + if (value == "the value of a") + done(); + else + done(new Error("incorrect value for a (" + value + ")")); + }, done); + }); + + it("with dependencies works", function(done) { + injector + .register("a", function(){return "the value of a";}) + .register("b", ["a", function(a){return a.replace(/a/g, "b");}]); + injector.get("b").then(function(value){ + if (value == "the vblue of b") + done(); + else + done(new Error("incorrect value for b (" + value + ")")); + }, done); + }); + }); + + describe("destroy handlers", function() { + it("work with single gets", function(done) { + injector.register("a", function(){return "a";}); + expect("a" in valuePlugin.values).to.equal(false); + var result = injector.get("a"); + result.then(function() { + if ("a" in valuePlugin.values) { + result.destroy().then(function() { + if ("a" in valuePlugin.values) { + done(new Error("Value found for a after destruction")); + } else { + done(); + } + }); + } else { + done(new Error("No value found for a")); + } + }, done); + }); + + it("works with multiple gets", function(done) { + injector.register("a", function(){return "a";}); + expect("a" in valuePlugin.values).to.equal(false); + var result = [injector.get("a"), injector.get("a"), injector.get("a")]; + result[1].destroy(); + result[2].destroy(); + result[0].then(function() { + if ("a" in valuePlugin.values) { + result[0].destroy().then(function() { + if ("a" in valuePlugin.values) { + done(new Error("Value found for a after destruction")); + } else { + done(); + } + }); + } else { + done(new Error("No value found for a")); + } + }, done); + }); + + it("works through dependencies", function(done) { + injector + .register("a", function(){return "a";}) + .register("b", ["a", function(a){return a + "b";}]); + expect("a" in valuePlugin.values).to.equal(false); + var result = injector.get("b"); + result.then(function() { + if ("a" in valuePlugin.values) { + result.destroy().then(function() { + if ("a" in valuePlugin.values) { + done(new Error("Value found for a after destruction")); + } else { + done(); + } + }); + } else { + done(new Error("No value found for a")); + } + }, done); + }); + }); + + describe("cyclic dependency detection", function() { + it("detects simple cycles", function(done) { + injector.register("a", ["b", function(b) {return b;}]); + injector.register("b", ["a", function(a) {return a;}]); + injector.get("a").then(function() { + done(new Error("Promise should have been rejected")); + }, function(e) { + done(); + }); + }); + + it("detects cycles with intermediate nodes", function(done) { + injector.register("a", ["c", function(c) {return c;}]); + injector.register("b", ["a", function(a) {return a;}]); + injector.register("c", ["b", function(b) {return b;}]); + injector.get("a").then(function() { + done(new Error("Promise should have been rejected")); + }, function(e) { + done(); + }); + }); + }); +}); diff --git a/scorpion.js b/scorpion.js new file mode 100644 index 0000000..6d7051c --- /dev/null +++ b/scorpion.js @@ -0,0 +1,426 @@ +/*global module, setTimeout*/ + +var Scorpion = (function() { + + var Deferred = (function() { + var defer = function(fn, value) {setTimeout(function() {fn(value);});}; + var runFn = function(fn, deferred, action) { + return function(value) { + try { + var result = fn(value); + if (result && result.then) { + result.then(function(value) { + deferred.resolve(value); + }, function(value) { + deferred.reject(value); + }); + } else { + deferred.resolve(result); + } + } catch (e) { + deferred.reject(e); + } + }; + }; + + var Promise = function() { + this.resolved = false; + this.rejected = false; + this.onSuccess = []; + this.onError = []; + }; + Promise.prototype.then = function(success, error) { + success = success || function(x) {return x;}; + error = error || function(x) {throw x;}; + var deferred = new Deferred(); + var successFn = runFn(success, deferred); + var errorFn = runFn(error, deferred); + if (this.resolved) { + defer(successFn, this.value); + } else if (this.rejected) { + defer(errorFn, this.value); + } else { + if (this.onSuccess != null) + this.onSuccess.push(successFn); + if (this.onError != null) + this.onError.push(errorFn); + } + return deferred.promise; + }; + + var Deferred = function() { + this.promise = new Promise(); + }; + Deferred.prototype.resolve = function(value) { + if (this.promise.resolved) { + throw new Error("Cannot re-resolve already resolved promise"); + } else if (this.promise.rejected) { + throw new Error("Cannot resolve a rejected promise"); + } else { + this.promise.resolved = true; + this.promise.value = value; + var handlers = this.promise.onSuccess; + this.promise.onSuccess = null; + this.promise.onError = null; + setTimeout(function() { + handlers.forEach(function(handler) { + handler(value); + }); + }); + } + }; + Deferred.prototype.reject = function(value) { + if (this.promise.resolved) { + throw new Error("Cannot reject an already resolved promise"); + } else if (this.promise.rejected) { + throw new Error("Cannot re-reject a rejected promise"); + } else { + this.promise.rejected = true; + this.promise.value = value; + var handlers = this.promise.onError; + this.promise.onSuccess = null; + this.promise.onError = null; + setTimeout(function() { + handlers.forEach(function(handler) { + handler(value); + }); + }); + } + }; + + Deferred.waitForAll = function(promises) { + var deferred = new Deferred(); + var successes = 0, errors = 0; + var errorResults = {}, successResults = {}; + var expected = promises.length; + promises.forEach(function(promise, i) { + promise.then(function(value) { + successResults[i] = value; + successes++; + maybeFinish(); + }, function(error) { + errorResults[i] = error; + errors++; + maybeFinish(); + }); + }); + var maybeFinish = function() { + if (successes == expected) { + var array = []; + for (var i = 0, l = expected; i < l; ++i) + array.push(successResults[i]); + deferred.resolve(array); + } else if (successes + errors == expected) { + deferred.reject({ + errors: errorResults, + values: successResults + }); + } + }; + maybeFinish(); + return deferred.promise; + }; + + return Deferred; + })(); + + var DepsParser = (function() { + var fnArgRegex = /^function[^(]*\(([^)]*)\)/; + var parseFnArgs = function(fn) { + var parts = fnArgRegex.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 parseDep = function(dep) { + var parts = dep.split("!"); + if (parts.length == 0) { + throw new Error("Invalid dependency: " + dep); + } else if (parts.length == 1) { + return {prefix: "", name: parts[0]}; + } else { + return {prefix: parts[0], name: parts.slice(1).join("!")}; + } + }; + + return { + spec: parseSpec, + dep: parseDep + }; + })(); + + var Scorpion = (function(Deferred, DepsParser) { + + var Scorpion = function(plugins) { + // plugins are a list to be executed in order + this.plugins = plugins; + }; + + Scorpion.rejected = function(message, cause) { + var deferred = this.defer(); + deferred.reject(this.error(message, cause)); + return deferred.promise; + }; + Scorpion.prototype.rejected = Scorpion.rejected; + + Scorpion.resolved = function(value) { + var deferred = this.defer(); + deferred.resolve(value); + return deferred.promise; + }; + Scorpion.prototype.resolved = Scorpion.resolved; + + Scorpion.prototype.get = function(dep, stack) { + stack = [dep].concat(stack || []); + if (stack && stack.lastIndexOf(dep) != 0) { + return this.rejected("Cyclic dependency: " + stack.join(" <- ")); + } + for (var i = 0, l = this.plugins.length; i < l; ++i) { + var value = this.plugins[i].get(this, dep, stack); + if (value) { + if (!value.destroy) + value.destroy = function(){return Scorpion.resolved(true);}; + return value; + } + } + return this.rejected("Unknown dependency: " + stack.join(" <- ")); + }; + + Scorpion.prototype.register = function(name, value) { + for (var i = 0, l = this.plugins.length; i < l; ++i) { + if (this.plugins[i].register(this, name, value)) + return this; + } + throw this.error("No plugin handled registration of: " + name); + return this; + }; + + Scorpion.prototype.invoke = function(spec) { + var injector = this; + var parsed = this.parseSpec(spec); + var fn = parsed[0]; + var dependencies = parsed[1]; + var depPromises = dependencies.map(function(dep) { + return injector.get(dep, []); + }); + var depPromise = this.waitForAll(depPromises); + var result = depPromise.then(function(results) { + return fn.apply(injector, results); + }); + result.destroy = function() { + return Scorpion.waitForAll(depPromises.map(function(promise) { + return promise.destroy(); + })).then(function() { + return true; + }); + }; + return result; + }; + + + + /* static utility functions */ + + Scorpion.error = function(message, cause) {return new ScorpionError(message, cause);}; + Scorpion.prototype.error = Scorpion.error; + + Scorpion.defer = function() {return new Deferred();}; + Scorpion.prototype.defer = Scorpion.defer; + + Scorpion.parseSpec = DepsParser.spec; + Scorpion.prototype.parseSpec = DepsParser.spec; + + Scorpion.waitForAll = Deferred.waitForAll; + Scorpion.prototype.waitForAll = Deferred.waitForAll; + + /* the injector error type */ + + var ScorpionError = function(message, cause) { + this.name = "ScorpionError"; + this.message = message; + this.cause = cause; + }; + ScorpionError.prototype = new Error(); + ScorpionError.prototype.constructor = ScorpionError; + ScorpionError.prototype.toString = function() { + return "ScorpionError: " + this.message + (this.cause ? " [caused by " + this.cause + "]" : ""); + }; + + return Scorpion; + + })(Deferred, DepsParser); + + Scorpion.prefixPlugin = function(prefix, plugin) { + return { + register: function(injector, name, spec) { + if (name.indexOf(prefix) == 0) { + return plugin.register(injector, + name.substr(prefix.length), + spec); + } else { + return false; + } + }, + get: function(injector, name, stack) { + if (name.indexOf(prefix) == 0) { + return plugin.get(injector, + name.substr(prefix.length), + stack); + } else { + return null; + } + } + }; + }; + + Scorpion.DOMPlugin = (function() { + var DOMPlugin = function() { + this.aliases = {}; + }; + + DOMPlugin.prototype.register = function(injector, name, spec) { + this.aliases[name] = spec; + return true; + }; + + DOMPlugin.prototype.get = function(injector, name, stack) { + var deferred = injector.defer(); + var interval = setInterval(function() { + var obj = $(this.aliases[name] || name); + if (obj.length) { + clearInterval(interval); + deferred.resolve(obj); + } + }.bind(this), 100); + return deferred.promise; + }; + + return DOMPlugin; + })(); + + Scorpion.HTTPPlugin = (function() { + var HTTPPlugin = function() { + this.aliases = {}; + }; + + HTTPPlugin.prototype.register = function(injector, name, spec) { + this.aliases[name] = spec; + return true; + }; + + HTTPPlugin.prototype.get = function(injector, name, stack) { + var deferred = injector.defer(); + $.ajax(this.aliases[name] || name).then(function(result) { + deferred.resolve(result); + // deferred.resolve.bind(deferred); + }, deferred.reject.bind(deferred)); + deferred.promise.destroy = function() { + return Scorpion.resolved(true); + }; + return deferred.promise; + }; + + return HTTPPlugin; + })(); + + Scorpion.ValuePlugin = (function() { + var ValuePlugin = function() { + this.specs = {}; + this.values = {}; + }; + + ValuePlugin.prototype.register = function(injector, name, spec) { + this.specs[name] = spec; + return true; + }; + + ValuePlugin.prototype.get = function(injector, name, stack) { + if (name in this.values) { + this.values[name].references++; + return this.values[name]; + } else if (name in this.specs) { + var spec = this.specs[name]; + var parsed = injector.parseSpec(spec); + var constructor = parsed[0]; + var dependencies = parsed[1]; + var depPromises = dependencies.map(function(dep) { + return injector.get(dep, [name].concat(stack)); + }); + var depPromise = injector.waitForAll(depPromises); + + var onDestroy = null; + var readRequestor = false; + var deferred = injector.defer(); + var result = deferred.promise; + depPromise.then(function(results) { + var wrappedScorpion = Object.create(injector); + wrappedScorpion.requestor = function() { + readRequestor = true; + return stack[1]; + }; + try { + deferred.resolve(constructor.apply(wrappedScorpion, results)); + } catch (e) { + deferred.reject(e); + } finally { + onDestroy = wrappedScorpion.onDestroy; + if (result.references <= 0 && onDestroy) + onDestroy(); + } + }, function(e) { + deferred.reject(injector.error("Error constructing " + name)); + }); + + result.references = 1; + var values = this.values; + result.destroy = function() { + this.references--; + if (this.references <= 0) { + if (onDestroy) + onDestroy(); + delete values[name]; + if (!result.resolved && !result.rejected) + deferred.reject(injector.error("Promise destroyed before value completed construction")); + return injector.waitForAll(depPromises.map(function(promise) { + return promise.destroy(); + })).then(function() { + return true; + }); + } else { + return injector.resolved(true); + } + }; + if (readRequestor) { + return result; + } else { + return this.values[name] = result; + } + } else { + return null; + } + }; + + return ValuePlugin; + })(); + + + return Scorpion; +})(); + +if (typeof(module) !== "undefined") + module.exports = Scorpion; diff --git a/scorpion.min.js b/scorpion.min.js new file mode 100644 index 0000000..5524044 --- /dev/null +++ b/scorpion.min.js @@ -0,0 +1 @@ +/*! scorpion 0.1.0 */var Scorpion=function(){var a=function(){var a=function(a,b){setTimeout(function(){a(b)})},b=function(a,b){return function(c){try{var d=a(c);d&&d.then?d.then(function(a){b.resolve(a)},function(a){b.reject(a)}):b.resolve(d)}catch(e){b.reject(e)}}},c=function(){this.resolved=!1,this.rejected=!1,this.onSuccess=[],this.onError=[]};c.prototype.then=function(c,e){c=c||function(a){return a},e=e||function(a){throw a};var f=new d,g=b(c,f),h=b(e,f);return this.resolved?a(g,this.value):this.rejected?a(h,this.value):(null!=this.onSuccess&&this.onSuccess.push(g),null!=this.onError&&this.onError.push(h)),f.promise};var d=function(){this.promise=new c};return d.prototype.resolve=function(a){if(this.promise.resolved)throw new Error("Cannot re-resolve already resolved promise");if(this.promise.rejected)throw new Error("Cannot resolve a rejected promise");this.promise.resolved=!0,this.promise.value=a;var b=this.promise.onSuccess;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.prototype.reject=function(a){if(this.promise.resolved)throw new Error("Cannot reject an already resolved promise");if(this.promise.rejected)throw new Error("Cannot re-reject a rejected promise");this.promise.rejected=!0,this.promise.value=a;var b=this.promise.onError;this.promise.onSuccess=null,this.promise.onError=null,setTimeout(function(){b.forEach(function(b){b(a)})})},d.waitForAll=function(a){var b=new d,c=0,e=0,f={},g={},h=a.length;a.forEach(function(a,b){a.then(function(a){g[b]=a,c++,i()},function(a){f[b]=a,e++,i()})});var i=function(){if(c==h){for(var a=[],d=0,i=h;i>d;++d)a.push(g[d]);b.resolve(a)}else c+e==h&&b.reject({errors:f,values:g})};return i(),b.promise},d}(),b=function(){var a=/^function[^(]*\(([^)]*)\)/,b=function(b){var c=a.exec(b.toString().replace(/\s+/g,""));if(null==c)throw new Error("Unable to parse fn definition");return c[1]?c[1].split(/,/):[]},c=function(a){var c,d;return"function"==typeof a||a instanceof Function?(d=b(a),c=a):(c=a[a.length-1],d=a.slice(0,a.length-1)),[c,d]},d=function(a){var b=a.split("!");if(0==b.length)throw new Error("Invalid dependency: "+a);return 1==b.length?{prefix:"",name:b[0]}:{prefix:b[0],name:b.slice(1).join("!")}};return{spec:c,dep:d}}(),c=function(a,b){var c=function(a){this.plugins=a};c.rejected=function(a,b){var c=this.defer();return c.reject(this.error(a,b)),c.promise},c.prototype.rejected=c.rejected,c.resolved=function(a){var b=this.defer();return b.resolve(a),b.promise},c.prototype.resolved=c.resolved,c.prototype.get=function(a,b){if(b=[a].concat(b||[]),b&&0!=b.lastIndexOf(a))return this.rejected("Cyclic dependency: "+b.join(" <- "));for(var d=0,e=this.plugins.length;e>d;++d){var f=this.plugins[d].get(this,a,b);if(f)return f.destroy||(f.destroy=function(){return c.resolved(!0)}),f}return this.rejected("Unknown dependency: "+b.join(" <- "))},c.prototype.register=function(a,b){for(var c=0,d=this.plugins.length;d>c;++c)if(this.plugins[c].register(this,a,b))return this;throw this.error("No plugin handled registration of: "+a)},c.prototype.invoke=function(a){var b=this,d=this.parseSpec(a),e=d[0],f=d[1],g=f.map(function(a){return b.get(a,[])}),h=this.waitForAll(g),i=h.then(function(a){return e.apply(b,a)});return i.destroy=function(){return c.waitForAll(g.map(function(a){return a.destroy()})).then(function(){return!0})},i},c.error=function(a,b){return new d(a,b)},c.prototype.error=c.error,c.defer=function(){return new a},c.prototype.defer=c.defer,c.parseSpec=b.spec,c.prototype.parseSpec=b.spec,c.waitForAll=a.waitForAll,c.prototype.waitForAll=a.waitForAll;var d=function(a,b){this.name="InjectorError",this.message=a,this.cause=b};return d.prototype=new Error,d.prototype.constructor=d,d.prototype.toString=function(){return"InjectorError: "+this.message+(this.cause?" [caused by "+this.cause+"]":"")},c}(a,b);return c.prefixPlugin=function(a,b){return{register:function(c,d,e){return 0==d.indexOf(a)?b.register(c,d.substr(a.length),e):!1},get:function(c,d,e){return 0==d.indexOf(a)?b.get(c,d.substr(a.length),e):null}}},c.DOMPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b){var c=a.defer(),d=setInterval(function(){var a=$(this.aliases[b]||b);a.length&&(clearInterval(d),c.resolve(a))}.bind(this),100);return c.promise},a}(),c.HTTPPlugin=function(){var a=function(){this.aliases={}};return a.prototype.register=function(a,b,c){return this.aliases[b]=c,!0},a.prototype.get=function(a,b){var d=a.defer();return $.ajax(this.aliases[b]||b).then(function(a){d.resolve(a)},d.reject.bind(d)),d.promise.destroy=function(){return c.resolved(!0)},d.promise},a}(),c.ValuePlugin=function(){var a=function(){this.specs={},this.values={}};return a.prototype.register=function(a,b,c){return this.specs[b]=c,!0},a.prototype.get=function(a,b,c){if(b in this.values)return this.values[b].references++,this.values[b];if(b in this.specs){var d=this.specs[b],e=a.parseSpec(d),f=e[0],g=e[1],h=g.map(function(d){return a.get(d,[b].concat(c))}),i=a.waitForAll(h),j=null,k=!1,l=a.defer(),m=l.promise;i.then(function(b){var d=Object.create(a);d.requestor=function(){return k=!0,c[1]};try{l.resolve(f.apply(d,b))}catch(e){l.reject(e)}finally{j=d.onDestroy,m.references<=0&&j&&j()}},function(){l.reject(a.error("Error constructing "+b))}),m.references=1;var n=this.values;return m.destroy=function(){return this.references--,this.references<=0?(j&&j(),delete n[b],m.resolved||m.rejected||l.reject(a.error("Promise destroyed before value completed construction")),a.waitForAll(h.map(function(a){return a.destroy()})).then(function(){return!0})):a.resolved(!0)},k?m:this.values[b]=m}return null},a}(),c}();"undefined"!=typeof module&&(module.exports=Scorpion); \ No newline at end of file -- cgit v1.2.3