summaryrefslogtreecommitdiff
path: root/injector.js
blob: 4d8b293926b897842321d14b9f7eeb6abe072550 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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;