UNPKG

5 kBJavaScriptView Raw
1// Load modules
2
3var Hoek = require('hoek');
4
5
6// Declare internals
7
8var internals = {};
9
10
11exports = module.exports = internals.Topo = function () {
12
13 this._items = [];
14 this.nodes = [];
15};
16
17
18internals.Topo.prototype.add = function (nodes, options) {
19
20 var self = this;
21
22 options = options || {};
23
24 // Validate rules
25
26 var before = [].concat(options.before || []);
27 var after = [].concat(options.after || []);
28 var group = options.group || '?';
29
30 Hoek.assert(before.indexOf(group) === -1, 'Item cannot come before itself:', group);
31 Hoek.assert(before.indexOf('?') === -1, 'Item cannot come before unassociated items');
32 Hoek.assert(after.indexOf(group) === -1, 'Item cannot come after itself:', group);
33 Hoek.assert(after.indexOf('?') === -1, 'Item cannot come after unassociated items');
34
35 ([].concat(nodes)).forEach(function (node, i) {
36
37 var item = {
38 seq: self._items.length,
39 before: before,
40 after: after,
41 group: group,
42 node: node
43 };
44
45 self._items.push(item);
46 });
47
48 // Insert event
49
50 var error = this._sort();
51 Hoek.assert(!error, 'item', (group !== '?' ? 'added into group ' + group : ''), 'created a dependencies error');
52
53 return this.nodes;
54};
55
56
57internals.Topo.prototype._sort = function () {
58
59 // Construct graph
60
61 var groups = {};
62 var graph = {};
63 var graphAfters = {};
64
65 for (var i = 0, il = this._items.length; i < il; ++i) {
66 var item = this._items[i];
67 var seq = item.seq; // Unique across all items
68 var group = item.group;
69
70 // Determine Groups
71
72 groups[group] = groups[group] || [];
73 groups[group].push(seq);
74
75 // Build intermediary graph using 'before'
76
77 graph[seq] = [item.before];
78
79 // Build second intermediary graph with 'after'
80
81 var after = item.after;
82 for (var j = 0, jl = after.length; j < jl; ++j) {
83 graphAfters[after[j]] = (graphAfters[after[j]] || []).concat(seq);
84 }
85 }
86
87 // Expand intermediary graph
88
89 var graphNodes = Object.keys(graph);
90 for (i = 0, il = graphNodes.length; i < il; ++i) {
91 var node = graphNodes[i];
92 var expandedGroups = [];
93
94 var graphNodeItems = Object.keys(graph[node]);
95 for (j = 0, jl = graphNodeItems.length; j < jl; ++j) {
96 var group = graph[node][graphNodeItems[j]];
97 groups[group] = groups[group] || [];
98 groups[group].forEach(function (d) {
99
100 expandedGroups.push(d);
101 });
102 }
103 graph[node] = expandedGroups;
104 }
105
106 // Merge intermediary graph using graphAfters into final graph
107
108 var afterNodes = Object.keys(graphAfters);
109 for (i = 0, il = afterNodes.length; i < il; ++i) {
110 var group = afterNodes[i];
111
112 if (groups[group]) {
113 for (j = 0, jl = groups[group].length; j < jl; ++j) {
114 var node = groups[group][j];
115 graph[node] = graph[node].concat(graphAfters[group]);
116 }
117 }
118 }
119
120 // Compile ancestors
121
122 var ancestors = {};
123 graphNodes = Object.keys(graph);
124 for (i = 0, il = graphNodes.length; i < il; ++i) {
125 var node = graphNodes[i];
126 var children = graph[node];
127
128 for (j = 0, jl = children.length; j < jl; ++j) {
129 ancestors[children[j]] = (ancestors[children[j]] || []).concat(node);
130 }
131 }
132
133 // Topo sort
134
135 var visited = {};
136 var sorted = [];
137
138 for (i = 0, il = this._items.length; i < il; ++i) {
139 var next = i;
140
141 if (ancestors[i]) {
142 next = null;
143 for (j = 0, jl = this._items.length; j < jl; ++j) {
144 if (visited[j] === true) {
145 continue;
146 }
147
148 if (!ancestors[j]) {
149 ancestors[j] = [];
150 }
151
152 var shouldSeeCount = ancestors[j].length;
153 var seenCount = 0;
154 for (var l = 0, ll = shouldSeeCount; l < ll; ++l) {
155 if (sorted.indexOf(ancestors[j][l]) >= 0) {
156 ++seenCount;
157 }
158 }
159
160 if (seenCount === shouldSeeCount) {
161 next = j;
162 break;
163 }
164 }
165 }
166
167 if (next !== null) {
168 next = next.toString(); // Normalize to string TODO: replace with seq
169 visited[next] = true;
170 sorted.push(next);
171 }
172 }
173
174 if (sorted.length !== this._items.length) {
175 return new Error('Invalid dependencies');
176 }
177
178 var seqIndex = {};
179 this._items.forEach(function (item) {
180
181 seqIndex[item.seq] = item;
182 });
183
184 var sortedNodes = [];
185 this._items = sorted.map(function (value) {
186
187 var item = seqIndex[value];
188 sortedNodes.push(item.node);
189 return item;
190 });
191
192 this.nodes = sortedNodes;
193};