/
easyioc.js
181 lines (138 loc) · 4.18 KB
/
easyioc.js
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
var _ = require('lodash')
function isObject(thing){
if (typeof thing === 'object')
return {}.toString.apply(thing) === '[object Object]'
}
var public_methods = {
empty: empty,
exec: exec,
add: add
}
module.exports = public_methods
var modules = {} // This is the master list, it will contain all modules.
var adding = {} // Allows add() to be called during exec() execution.
var path = [] // How we detect circular dependencies.
function Module(name, module){
var loaded = null
this.fetch = function(){
if (!loaded) {
if (_.isFunction(module)) {
loaded = execFuncLoad(name, module)
}
else if (isObject(module))
loaded = module
else
throw new Error('"'+name+'" module added with invalid target: "'+module+'"')
}
return loaded
}
}
function FileGroup (files) {
var loaded = null
this.fetch = function(){
if (!loaded) {
loaded = _.each(files, function(file){
if (_.isFunction(file))
return execFuncLoad(makeId(), file)
if (isObject(file))
return file
if (!_.isString(file))
throw new Error('easyioc.exec(): Invalid object in array: "'+file+'". Must be either function, string, or object (although the latter is discouraged).')
if (!require.resolve(file))
throw new Error('Require could not resolve "'+file+'".')
var module = require(file)
if (_.isFunction(module))
return execFuncLoad(makeId(), module)
else
return module
})
}
return loaded
}
}
function execFuncLoad (name, module) { // This where we detect circular dependency paths.
var deps = fetchDeps(module)
deps = deps.map(function(dep){
if (_.includes(path, dep))
throw new Error('Dependency circle encountered: '+path.concat(dep).join(' ➤ ')) // ▷ ➔ ➤ ▸ ▶ ➠ ⧐ ➞
path.push(dep)
var mod = load(name, dep)
path.pop()
return mod
})
return module.apply(new Object, deps)
}
function load (func, dep) {
if (_.has(modules, dep))
return modules[dep].fetch()
else
throw new Error('Module "'+func+'" requires unknown dependency "'+dep+'".')
}
function fetchDeps (target) {
if (target.length) {
var args_regex = /^function\s*[^\(]*\(\s*([^\)]*)\)/m
var text = target.toString()
var res = text.match(args_regex)[1].split(',').map(function(e){return e.trim()})
return res
}
return []
}
// credit: http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript
function makeId (n) {
n = n || 10
var text = ""
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for (var i = 0; i < n; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length))
return text
}
function doAdd (name, object) {
modules[name] = adding[name] = object
}
// Exposed methods \\
/**
* target can be a
* 1) function,
* 2) any valid string for require(),
* 3) or array of strings for require().
* Name is optional except when target is a string path.
* Method is silent when it fails.
*/
function add (name, target) {
var module = target
if (_.isArray(name))
doAdd(makeId(), new FileGroup(name))
else if (_.isString(name)) {
if (_.isArray(target))
doAdd(name, new FileGroup(target))
else {
if (!target)
module = require(name)
else if (_.isString(target))
module = require(target)
else if (_.isFunction(target))
module = target
doAdd(name, new Module(name, module))
}
}
return public_methods
}
/**
*
*/
function exec(){
var loading = adding
adding = {}
for (var each in loading) {
loading[each].fetch()
}
return public_methods
}
/**
*
*/
function empty(){
adding = {}
modules = {}
return public_methods
}