source: trip-planner-front/node_modules/dependency-graph/lib/dep_graph.js@ 6c1585f

Last change on this file since 6c1585f was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 10.6 KB
Line 
1/**
2 * A simple dependency graph
3 */
4
5/**
6 * Helper for creating a Topological Sort using Depth-First-Search on a set of edges.
7 *
8 * Detects cycles and throws an Error if one is detected (unless the "circular"
9 * parameter is "true" in which case it ignores them).
10 *
11 * @param edges The set of edges to DFS through
12 * @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges)
13 * @param result An array in which the results will be populated
14 * @param circular A boolean to allow circular dependencies
15 */
16function createDFS(edges, leavesOnly, result, circular) {
17 var visited = {};
18 return function (start) {
19 if (visited[start]) {
20 return;
21 }
22 var inCurrentPath = {};
23 var currentPath = [];
24 var todo = []; // used as a stack
25 todo.push({ node: start, processed: false });
26 while (todo.length > 0) {
27 var current = todo[todo.length - 1]; // peek at the todo stack
28 var processed = current.processed;
29 var node = current.node;
30 if (!processed) {
31 // Haven't visited edges yet (visiting phase)
32 if (visited[node]) {
33 todo.pop();
34 continue;
35 } else if (inCurrentPath[node]) {
36 // It's not a DAG
37 if (circular) {
38 todo.pop();
39 // If we're tolerating cycles, don't revisit the node
40 continue;
41 }
42 currentPath.push(node);
43 throw new DepGraphCycleError(currentPath);
44 }
45
46 inCurrentPath[node] = true;
47 currentPath.push(node);
48 var nodeEdges = edges[node];
49 // (push edges onto the todo stack in reverse order to be order-compatible with the old DFS implementation)
50 for (var i = nodeEdges.length - 1; i >= 0; i--) {
51 todo.push({ node: nodeEdges[i], processed: false });
52 }
53 current.processed = true;
54 } else {
55 // Have visited edges (stack unrolling phase)
56 todo.pop();
57 currentPath.pop();
58 inCurrentPath[node] = false;
59 visited[node] = true;
60 if (!leavesOnly || edges[node].length === 0) {
61 result.push(node);
62 }
63 }
64 }
65 };
66}
67
68/**
69 * Simple Dependency Graph
70 */
71var DepGraph = (exports.DepGraph = function DepGraph(opts) {
72 this.nodes = {}; // Node -> Node/Data (treated like a Set)
73 this.outgoingEdges = {}; // Node -> [Dependency Node]
74 this.incomingEdges = {}; // Node -> [Dependant Node]
75 this.circular = opts && !!opts.circular; // Allows circular deps
76});
77DepGraph.prototype = {
78 /**
79 * The number of nodes in the graph.
80 */
81 size: function () {
82 return Object.keys(this.nodes).length;
83 },
84 /**
85 * Add a node to the dependency graph. If a node already exists, this method will do nothing.
86 */
87 addNode: function (node, data) {
88 if (!this.hasNode(node)) {
89 // Checking the arguments length allows the user to add a node with undefined data
90 if (arguments.length === 2) {
91 this.nodes[node] = data;
92 } else {
93 this.nodes[node] = node;
94 }
95 this.outgoingEdges[node] = [];
96 this.incomingEdges[node] = [];
97 }
98 },
99 /**
100 * Remove a node from the dependency graph. If a node does not exist, this method will do nothing.
101 */
102 removeNode: function (node) {
103 if (this.hasNode(node)) {
104 delete this.nodes[node];
105 delete this.outgoingEdges[node];
106 delete this.incomingEdges[node];
107 [this.incomingEdges, this.outgoingEdges].forEach(function (edgeList) {
108 Object.keys(edgeList).forEach(function (key) {
109 var idx = edgeList[key].indexOf(node);
110 if (idx >= 0) {
111 edgeList[key].splice(idx, 1);
112 }
113 }, this);
114 });
115 }
116 },
117 /**
118 * Check if a node exists in the graph
119 */
120 hasNode: function (node) {
121 return this.nodes.hasOwnProperty(node);
122 },
123 /**
124 * Get the data associated with a node name
125 */
126 getNodeData: function (node) {
127 if (this.hasNode(node)) {
128 return this.nodes[node];
129 } else {
130 throw new Error("Node does not exist: " + node);
131 }
132 },
133 /**
134 * Set the associated data for a given node name. If the node does not exist, this method will throw an error
135 */
136 setNodeData: function (node, data) {
137 if (this.hasNode(node)) {
138 this.nodes[node] = data;
139 } else {
140 throw new Error("Node does not exist: " + node);
141 }
142 },
143 /**
144 * Add a dependency between two nodes. If either of the nodes does not exist,
145 * an Error will be thrown.
146 */
147 addDependency: function (from, to) {
148 if (!this.hasNode(from)) {
149 throw new Error("Node does not exist: " + from);
150 }
151 if (!this.hasNode(to)) {
152 throw new Error("Node does not exist: " + to);
153 }
154 if (this.outgoingEdges[from].indexOf(to) === -1) {
155 this.outgoingEdges[from].push(to);
156 }
157 if (this.incomingEdges[to].indexOf(from) === -1) {
158 this.incomingEdges[to].push(from);
159 }
160 return true;
161 },
162 /**
163 * Remove a dependency between two nodes.
164 */
165 removeDependency: function (from, to) {
166 var idx;
167 if (this.hasNode(from)) {
168 idx = this.outgoingEdges[from].indexOf(to);
169 if (idx >= 0) {
170 this.outgoingEdges[from].splice(idx, 1);
171 }
172 }
173
174 if (this.hasNode(to)) {
175 idx = this.incomingEdges[to].indexOf(from);
176 if (idx >= 0) {
177 this.incomingEdges[to].splice(idx, 1);
178 }
179 }
180 },
181 /**
182 * Return a clone of the dependency graph. If any custom data is attached
183 * to the nodes, it will only be shallow copied.
184 */
185 clone: function () {
186 var source = this;
187 var result = new DepGraph();
188 var keys = Object.keys(source.nodes);
189 keys.forEach(function (n) {
190 result.nodes[n] = source.nodes[n];
191 result.outgoingEdges[n] = source.outgoingEdges[n].slice(0);
192 result.incomingEdges[n] = source.incomingEdges[n].slice(0);
193 });
194 return result;
195 },
196 /**
197 * Get an array containing the direct dependencies of the specified node.
198 *
199 * Throws an Error if the specified node does not exist.
200 */
201 directDependenciesOf: function (node) {
202 if (this.hasNode(node)) {
203 return this.outgoingEdges[node].slice(0);
204 } else {
205 throw new Error("Node does not exist: " + node);
206 }
207 },
208 /**
209 * Get an array containing the nodes that directly depend on the specified node.
210 *
211 * Throws an Error if the specified node does not exist.
212 */
213 directDependantsOf: function (node) {
214 if (this.hasNode(node)) {
215 return this.incomingEdges[node].slice(0);
216 } else {
217 throw new Error("Node does not exist: " + node);
218 }
219 },
220 /**
221 * Get an array containing the nodes that the specified node depends on (transitively).
222 *
223 * Throws an Error if the graph has a cycle, or the specified node does not exist.
224 *
225 * If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned
226 * in the array.
227 */
228 dependenciesOf: function (node, leavesOnly) {
229 if (this.hasNode(node)) {
230 var result = [];
231 var DFS = createDFS(
232 this.outgoingEdges,
233 leavesOnly,
234 result,
235 this.circular
236 );
237 DFS(node);
238 var idx = result.indexOf(node);
239 if (idx >= 0) {
240 result.splice(idx, 1);
241 }
242 return result;
243 } else {
244 throw new Error("Node does not exist: " + node);
245 }
246 },
247 /**
248 * get an array containing the nodes that depend on the specified node (transitively).
249 *
250 * Throws an Error if the graph has a cycle, or the specified node does not exist.
251 *
252 * If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
253 */
254 dependantsOf: function (node, leavesOnly) {
255 if (this.hasNode(node)) {
256 var result = [];
257 var DFS = createDFS(
258 this.incomingEdges,
259 leavesOnly,
260 result,
261 this.circular
262 );
263 DFS(node);
264 var idx = result.indexOf(node);
265 if (idx >= 0) {
266 result.splice(idx, 1);
267 }
268 return result;
269 } else {
270 throw new Error("Node does not exist: " + node);
271 }
272 },
273 /**
274 * Construct the overall processing order for the dependency graph.
275 *
276 * Throws an Error if the graph has a cycle.
277 *
278 * If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned.
279 */
280 overallOrder: function (leavesOnly) {
281 var self = this;
282 var result = [];
283 var keys = Object.keys(this.nodes);
284 if (keys.length === 0) {
285 return result; // Empty graph
286 } else {
287 if (!this.circular) {
288 // Look for cycles - we run the DFS starting at all the nodes in case there
289 // are several disconnected subgraphs inside this dependency graph.
290 var CycleDFS = createDFS(this.outgoingEdges, false, [], this.circular);
291 keys.forEach(function (n) {
292 CycleDFS(n);
293 });
294 }
295
296 var DFS = createDFS(
297 this.outgoingEdges,
298 leavesOnly,
299 result,
300 this.circular
301 );
302 // Find all potential starting points (nodes with nothing depending on them) an
303 // run a DFS starting at these points to get the order
304 keys
305 .filter(function (node) {
306 return self.incomingEdges[node].length === 0;
307 })
308 .forEach(function (n) {
309 DFS(n);
310 });
311
312 // If we're allowing cycles - we need to run the DFS against any remaining
313 // nodes that did not end up in the initial result (as they are part of a
314 // subgraph that does not have a clear starting point)
315 if (this.circular) {
316 keys
317 .filter(function (node) {
318 return result.indexOf(node) === -1;
319 })
320 .forEach(function (n) {
321 DFS(n);
322 });
323 }
324
325 return result;
326 }
327 },
328 /**
329 * Get an array of nodes that have no dependants (i.e. nothing depends on them).
330 */
331 entryNodes: function () {
332 var self = this;
333 return Object.keys(this.nodes).filter(function (node) {
334 return self.incomingEdges[node].length === 0;
335 });
336 }
337};
338
339// Create some aliases
340DepGraph.prototype.directDependentsOf = DepGraph.prototype.directDependantsOf;
341DepGraph.prototype.dependentsOf = DepGraph.prototype.dependantsOf;
342
343/**
344 * Cycle error, including the path of the cycle.
345 */
346var DepGraphCycleError = (exports.DepGraphCycleError = function (cyclePath) {
347 var message = "Dependency Cycle Found: " + cyclePath.join(" -> ");
348 var instance = new Error(message);
349 instance.cyclePath = cyclePath;
350 Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
351 if (Error.captureStackTrace) {
352 Error.captureStackTrace(instance, DepGraphCycleError);
353 }
354 return instance;
355});
356DepGraphCycleError.prototype = Object.create(Error.prototype, {
357 constructor: {
358 value: Error,
359 enumerable: false,
360 writable: true,
361 configurable: true
362 }
363});
364Object.setPrototypeOf(DepGraphCycleError, Error);
Note: See TracBrowser for help on using the repository browser.