-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First commit - extract dependency-graph from asset-smasher
- Loading branch information
0 parents
commit 1fdaf69
Showing
7 changed files
with
352 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Ignore node_modules | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Dependency Graph Changelog | ||
|
||
## 0.1.0 (May 18, 2013) | ||
|
||
- Initial Release - extracted out of asset-smasher |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (C) 2013 by Jim Riecken | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Dependency Graph | ||
|
||
Simple dependency graph | ||
|
||
## Overview | ||
|
||
This is a simple dependency graph useful for determining the order to do a list of things that depend on certain items being done before they are. | ||
|
||
To use, `npm install dependency-graph` and then `require('dependency-graph').DepGraph` | ||
|
||
## API | ||
|
||
### DepGraph | ||
|
||
Nodes in the graph are just simple strings. | ||
|
||
- `addNode(name)` - add a node in the graph | ||
- `removeNode(name)` - remove a node from the graph | ||
- `hasNode(name)` - check if a node exists in the graph | ||
- `addDependency(from, to)` - add a dependency between two nodes (will throw an Error if one of the nodes does not exist) | ||
- `removeDependency(from, to)` - remove a dependency between two nodes | ||
- `dependenciesOf(name, leavesOnly)` - get an array containing the nodes that the specified node depends on (transitively). If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned in the array. | ||
- `dependantsOf(name, leavesOnly)` - get an array containing the nodes that depend on the specified node (transitively). If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array. | ||
- `overallOrder(leavesOnly)` - construct the overall processing order for the dependency graph. If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned. | ||
|
||
## Examples | ||
|
||
var DepGraph = require('dependency-graph').DepGraph; | ||
|
||
var graph = new DepGraph(); | ||
graph.addNode('a'); | ||
graph.addNode('b'); | ||
graph.addNode('c'); | ||
|
||
graph.addDependency('a', 'b'); | ||
graph.addDependency('b', 'c'); | ||
|
||
graph.dependenciesOf('a'); // ['c', 'b'] | ||
graph.dependenciesOf('b'); // ['c'] | ||
graph.dependantsOf('c'); // ['a', 'b'] | ||
|
||
graph.overallOrder(); // ['c', 'b', 'a'] | ||
graph.overallOrder(true); // ['c'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/** | ||
* A simple dependency graph | ||
*/ | ||
var _ = require('underscore'); | ||
|
||
/** | ||
* Helper for creating a Depth-First-Search on | ||
* a set of edges. | ||
* | ||
* Detects cycles and throws an Error if one is detected | ||
* | ||
* @param edges The set of edges to DFS through | ||
* @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges) | ||
* @param result An array in which the results will be populated | ||
*/ | ||
function createDFS(edges, leavesOnly, result) { | ||
var chain = {}; | ||
var visited = {}; | ||
return function DFS(name) { | ||
visited[name] = true; | ||
chain[name] = true; | ||
edges[name].forEach(function (edgeName) { | ||
if (!visited[edgeName]) { | ||
DFS(edgeName); | ||
} else if (chain[edgeName]) { | ||
throw new Error('Dependency Cycle Found: ' + edgeName); | ||
} | ||
}); | ||
chain[name] = false; | ||
if ((!leavesOnly || edges[name].length === 0) && result.indexOf(name) === -1) { | ||
result.push(name); | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Simple Dependency Graph | ||
*/ | ||
var DepGraph = exports.DepGraph = function DepGraph() { | ||
this.nodes = {}; | ||
this.outgoingEdges = {}; // Node name -> [Dependency Node name] | ||
this.incomingEdges = {}; // Node name -> [Dependant Node name] | ||
}; | ||
DepGraph.prototype = { | ||
addNode:function (name) { | ||
this.nodes[name] = name; | ||
this.outgoingEdges[name] = []; | ||
this.incomingEdges[name] = []; | ||
}, | ||
removeNode:function (name) { | ||
delete this.nodes[name]; | ||
delete this.outgoingEdges[name]; | ||
delete this.incomingEdges[name]; | ||
_.each(this.incomingEdges, function (edges) { | ||
var idx = edges.indexOf(name); | ||
if (idx >= 0) { | ||
edges.splice(idx, 1); | ||
} | ||
}); | ||
}, | ||
hasNode:function (name) { | ||
return !!this.nodes[name]; | ||
}, | ||
addDependency:function (from, to) { | ||
if (this.hasNode(from) && this.hasNode(to)) { | ||
if (this.outgoingEdges[from].indexOf(to) === -1) { | ||
this.outgoingEdges[from].push(to); | ||
} | ||
if (this.incomingEdges[to].indexOf(from) === -1) { | ||
this.incomingEdges[to].push(from); | ||
} | ||
return true; | ||
} else { | ||
throw new Error('One of the nodes does not exist: ' + from + ', ' + to); | ||
} | ||
}, | ||
removeDependency:function (from, to) { | ||
var idx = this.outgoingEdges[from].indexOf(to); | ||
if (idx >= 0) { | ||
this.outgoingEdges[from].splice(idx, 1); | ||
} | ||
idx = this.incomingEdges[to].indexOf(from); | ||
if (idx >= 0) { | ||
this.incomingEdges[to].splice(idx, 1); | ||
} | ||
}, | ||
dependenciesOf:function (name, leavesOnly) { | ||
if (this.nodes[name]) { | ||
var result = []; | ||
var DFS = createDFS(this.outgoingEdges, leavesOnly, result); | ||
DFS(name); | ||
var idx = result.indexOf(name); | ||
if (idx >= 0) { | ||
result.splice(idx, 1); | ||
} | ||
return result; | ||
} | ||
else { | ||
throw new Error('Node does not exist: ' + name); | ||
} | ||
}, | ||
dependantsOf:function (name, leavesOnly) { | ||
if (this.nodes[name]) { | ||
var result = []; | ||
var DFS = createDFS(this.incomingEdges, leavesOnly, result); | ||
DFS(name); | ||
var idx = result.indexOf(name); | ||
if (idx >= 0) { | ||
result.splice(idx, 1); | ||
} | ||
return result; | ||
} else { | ||
throw new Error('Node does not exist: ' + name); | ||
} | ||
}, | ||
overallOrder:function (leavesOnly) { | ||
var self = this; | ||
var result = []; | ||
var DFS = createDFS(this.outgoingEdges, leavesOnly, result); | ||
_.each(_.filter(_.keys(this.nodes), function (node) { | ||
return self.incomingEdges[node].length === 0; | ||
}), function (n) { | ||
DFS(n); | ||
}); | ||
if (_.size(this.nodes) > 0 && result.length === 0) { | ||
// Special case when there are no nodes with no dependants | ||
throw new Error('Dependency Cycle Found'); | ||
} | ||
return result; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "dependency-graph", | ||
"description": "Simple dependency graph.", | ||
"version": "0.1.0", | ||
"author": "Jim Riecken <[email protected]>", | ||
"keywords": [ | ||
"dependency", | ||
"graph" | ||
], | ||
"license": { | ||
"type": "MIT", | ||
"url": "http://github.com/jriecken/dependency-graph/raw/master/LICENSE" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/jriecken/dependency-graph.git" | ||
}, | ||
"bugs": { | ||
"url": "http://github.com/jriecken/dependency-graph/issues" | ||
}, | ||
"main": "./lib/dep_graph.js", | ||
"scripts": { | ||
"test": "jasmine-node specs" | ||
}, | ||
"dependencies": { | ||
"underscore": "1.4.4" | ||
}, | ||
"optionalDependencies": {}, | ||
"devDependencies": { | ||
"jasmine-node": "1.7.1" | ||
}, | ||
"engines": { | ||
"node": ">= 0.6.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
var DepGraph = require('../lib/dep_graph').DepGraph; | ||
|
||
describe('DepGraph', function () { | ||
|
||
it('should be able to add/remove nodes', function () { | ||
var graph = new DepGraph(); | ||
|
||
graph.addNode('Foo'); | ||
graph.addNode('Bar'); | ||
|
||
expect(graph.hasNode('Foo')).toBe(true); | ||
expect(graph.hasNode('Bar')).toBe(true); | ||
expect(graph.hasNode('NotThere')).toBe(false); | ||
|
||
graph.removeNode('Bar'); | ||
|
||
expect(graph.hasNode('Bar')).toBe(false); | ||
}); | ||
|
||
it('should be able to add dependencies between nodes', function () { | ||
var graph = new DepGraph(); | ||
|
||
graph.addNode('a'); | ||
graph.addNode('b'); | ||
graph.addNode('c'); | ||
|
||
graph.addDependency('a','b'); | ||
graph.addDependency('a','c'); | ||
|
||
expect(graph.dependenciesOf('a')).toEqual(['b', 'c']); | ||
}); | ||
|
||
it('should throw an error if a node does not exist and a dependency is added', function () { | ||
var graph = new DepGraph(); | ||
|
||
graph.addNode('a'); | ||
|
||
expect(function () { | ||
graph.addDependency('a','b'); | ||
}).toThrow(); | ||
}); | ||
|
||
it('should detect cycles', function () { | ||
var graph = new DepGraph(); | ||
|
||
graph.addNode('a'); | ||
graph.addNode('b'); | ||
graph.addNode('c'); | ||
|
||
graph.addDependency('a', 'b'); | ||
graph.addDependency('b', 'c'); | ||
graph.addDependency('c', 'a'); | ||
|
||
expect(function () { | ||
graph.dependenciesOf('a'); | ||
}).toThrow(); | ||
}); | ||
|
||
it('should retrieve dependencies and dependants in the correct order', function () { | ||
var graph = new DepGraph(); | ||
|
||
graph.addNode('a'); | ||
graph.addNode('b'); | ||
graph.addNode('c'); | ||
graph.addNode('d'); | ||
|
||
graph.addDependency('a', 'd'); | ||
graph.addDependency('a', 'b'); | ||
graph.addDependency('b', 'c'); | ||
graph.addDependency('d', 'b'); | ||
|
||
expect(graph.dependenciesOf('a')).toEqual(['c', 'b', 'd']); | ||
expect(graph.dependenciesOf('b')).toEqual(['c']); | ||
expect(graph.dependenciesOf('c')).toEqual([]); | ||
expect(graph.dependenciesOf('d')).toEqual(['c', 'b']); | ||
|
||
expect(graph.dependantsOf('a')).toEqual([]); | ||
expect(graph.dependantsOf('b')).toEqual(['a','d']); | ||
expect(graph.dependantsOf('c')).toEqual(['a','d','b']); | ||
expect(graph.dependantsOf('d')).toEqual(['a']); | ||
}); | ||
|
||
it('should be able to resolve the overall order of things', function () { | ||
var graph = new DepGraph(); | ||
|
||
graph.addNode('a'); | ||
graph.addNode('b'); | ||
graph.addNode('c'); | ||
graph.addNode('d'); | ||
graph.addNode('e'); | ||
|
||
graph.addDependency('a', 'b'); | ||
graph.addDependency('a', 'c'); | ||
graph.addDependency('b', 'c'); | ||
graph.addDependency('c', 'd'); | ||
|
||
expect(graph.overallOrder()).toEqual(['d', 'c', 'b', 'a', 'e']); | ||
}); | ||
|
||
it('should be able to only retrieve the "leaves" in the overall order', function () { | ||
var graph = new DepGraph(); | ||
|
||
graph.addNode('a'); | ||
graph.addNode('b'); | ||
graph.addNode('c'); | ||
graph.addNode('d'); | ||
graph.addNode('e'); | ||
|
||
graph.addDependency('a', 'b'); | ||
graph.addDependency('a', 'c'); | ||
graph.addDependency('b', 'c'); | ||
graph.addDependency('c', 'd'); | ||
|
||
expect(graph.overallOrder(true)).toEqual(['d', 'e']); | ||
}); | ||
|
||
}); |