1 | var N = require('libnested')
|
2 |
|
3 | var isModule = require('./is')
|
4 | var apply = require('./apply')
|
5 | var assertGiven = require('./assertGiven')
|
6 |
|
7 | module.exports = function combine () {
|
8 | var nestedModules = Array.prototype.slice.call(arguments)
|
9 | var modules = flattenNested(nestedModules)
|
10 |
|
11 | assertDependencies(modules)
|
12 |
|
13 | var combinedModules = {}
|
14 |
|
15 | for (var key in modules) {
|
16 | var module = modules[key]
|
17 | var needed = getNeeded(module.needs, combinedModules)
|
18 | var given = module.create(needed)
|
19 |
|
20 | assertGiven(module.gives, given, key)
|
21 |
|
22 | addGivenToCombined(given, combinedModules, module)
|
23 | }
|
24 |
|
25 | if (isEmpty(combinedModules)) {
|
26 | throw new Error('could not resolve any modules')
|
27 | }
|
28 |
|
29 | return combinedModules
|
30 | }
|
31 |
|
32 | function isString (s) {
|
33 | return typeof s === 'string'
|
34 | }
|
35 |
|
36 | function isEmpty (e) {
|
37 | for (var k in e) return false
|
38 | return true
|
39 | }
|
40 |
|
41 | function isObject (o) {
|
42 | return o && typeof o === 'object'
|
43 | }
|
44 |
|
45 | function append (obj, path, value) {
|
46 | var a = N.get(obj, path)
|
47 | if (!a) N.set(obj, path, a = [])
|
48 | a.push(value)
|
49 | }
|
50 |
|
51 | function flattenNested (modules) {
|
52 | return modules.reduce(function (a, b) {
|
53 | eachModule(b, function (value, path) {
|
54 | var k = path.join('/')
|
55 | a[k] = value
|
56 | })
|
57 | return a
|
58 | }, {})
|
59 | }
|
60 |
|
61 | function assertDependencies (modules) {
|
62 | var allNeeds = {}
|
63 | var allGives = {}
|
64 |
|
65 | for (var key in modules) {
|
66 | var module = modules[key]
|
67 | N.each(module.needs, function (v, path) {
|
68 | N.set(allNeeds, path, key)
|
69 | })
|
70 | if (isString(module.gives)) {
|
71 | N.set(allGives, [module.gives], true)
|
72 | } else {
|
73 | N.each(module.gives, function (v, path) {
|
74 | N.set(allGives, path, true)
|
75 | })
|
76 | }
|
77 | }
|
78 |
|
79 | N.each(allNeeds, function (key, path) {
|
80 | if (!N.get(allGives, path)) { throw new Error('unmet need: `' + path.join('.') + '`, needed by module ' + ((isNaN(key)) ? '`' + key + '`' : '')) }
|
81 | })
|
82 | }
|
83 |
|
84 | function addGivenToCombined (given, combined, module) {
|
85 | if (isString(module.gives)) {
|
86 | append(combined, [module.gives], given)
|
87 | } else {
|
88 | N.each(module.gives, function (_, path) {
|
89 | var fun = N.get(given, path)
|
90 | append(combined, path, fun)
|
91 | })
|
92 | }
|
93 | }
|
94 |
|
95 | function getNeeded (needs, combined) {
|
96 | return N.map(needs, function (type, path) {
|
97 | var dependency = N.get(combined, path)
|
98 | if (!dependency) {
|
99 | dependency = N.set(combined, path, [])
|
100 | }
|
101 | return apply[type](dependency)
|
102 | })
|
103 | }
|
104 |
|
105 | function eachModule (obj, iter, path) {
|
106 | path = path || []
|
107 | for (var k in obj) {
|
108 | if (isObject(obj[k])) {
|
109 | if (isModule(obj[k])) iter(obj[k], path.concat(k))
|
110 | else eachModule(obj[k], iter, path.concat(k))
|
111 | }
|
112 | }
|
113 | }
|