deep_end 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +53 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/Rakefile +7 -0
- data/deep_end.gemspec +24 -0
- data/deep_end.sublime-project +8 -0
- data/lib/deep_end/version.rb +3 -0
- data/lib/deep_end.rb +116 -0
- data/spec/lib/deep_end_spec.rb +94 -0
- data/spec/spec_helper.rb +1 -0
- metadata +107 -0
data/.gitignore
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Also see Global .gitignore: ~/.gitignore_global
|
2
|
+
# https://github.com/stationkeeping/osx/blob/master/git/.gitignore_global
|
3
|
+
|
4
|
+
# Environmental Variables
|
5
|
+
#########################
|
6
|
+
|
7
|
+
.env
|
8
|
+
|
9
|
+
# Rails
|
10
|
+
#########################
|
11
|
+
|
12
|
+
*.rbc
|
13
|
+
*.sassc
|
14
|
+
.sass-cache
|
15
|
+
capybara-*.html
|
16
|
+
.rspec
|
17
|
+
.rvmrc
|
18
|
+
/.bundle
|
19
|
+
/vendor/bundle
|
20
|
+
/log/*
|
21
|
+
/tmp/*
|
22
|
+
/db/*.sqlite3
|
23
|
+
/public/system/*
|
24
|
+
/coverage/
|
25
|
+
/spec/tmp/*
|
26
|
+
**.orig
|
27
|
+
rerun.txt
|
28
|
+
pickle-email-*.html
|
29
|
+
.project
|
30
|
+
|
31
|
+
# Ruby
|
32
|
+
#########################
|
33
|
+
|
34
|
+
*.gem
|
35
|
+
*.rbc
|
36
|
+
.bundle
|
37
|
+
.config
|
38
|
+
coverage
|
39
|
+
InstalledFiles
|
40
|
+
lib/bundler/man
|
41
|
+
pkg
|
42
|
+
rdoc
|
43
|
+
spec/reports
|
44
|
+
test/tmp
|
45
|
+
test/version_tmp
|
46
|
+
tmp
|
47
|
+
|
48
|
+
# YARD artifacts
|
49
|
+
.yardoc
|
50
|
+
_yardoc
|
51
|
+
doc/
|
52
|
+
|
53
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Pedr Browne
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# DeepEnd
|
2
|
+
|
3
|
+
This gem processes a list of objects and their dependencies, ordering them so that dependencies are correctly resolved and checking for circular dependencies.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'deep_end'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install deep_end
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
The Graph is the only class you have to worry about:
|
22
|
+
|
23
|
+
dependency_graph = Graph.new
|
24
|
+
|
25
|
+
Add any objects you like to it, along with an array of any other objects that they depend on:
|
26
|
+
|
27
|
+
# Create objects - you can use any objects/instances
|
28
|
+
dependency_a = {name: 'a'}
|
29
|
+
dependency_b = {name: 'b'}
|
30
|
+
dependency_c = {name: 'c'}
|
31
|
+
|
32
|
+
dependency_graph.add_dependency dependency_c, [dependency_b, dependency_a]
|
33
|
+
dependency_graph.add_dependency dependency_b, [dependency_a]
|
34
|
+
dependency_graph.add_dependency dependency_a
|
35
|
+
|
36
|
+
An array of all objects in an order that obeys dependencies is returned from 'add_dependency',
|
37
|
+
It is also available from the `resolved_dependencies` property.
|
38
|
+
|
39
|
+
dependency_graph.resolved_dependencies # [{:name=>"a"}, {:name=>"b"}, {:name=>"c"}]
|
40
|
+
|
41
|
+
To reuse the Graph you can remove all objects and dependencies using `reset`:
|
42
|
+
|
43
|
+
dependency_graph.reset
|
44
|
+
|
45
|
+
Whenever a new object is added with `add_dependency`, the graph re-calculates dependencies. An object can
|
46
|
+
be added multiple times with different dependencies. Dependencies are cumulative, so adding an object for
|
47
|
+
a second time with different dependencies will result in the object having both sets of dependencies.
|
48
|
+
|
49
|
+
If an object is added with itself as a dependency, a `SelfDependencyError` will be raised.
|
50
|
+
|
51
|
+
If a circular dependency is detected a `CircularDependencyError` will be raise.
|
52
|
+
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
1. Fork it
|
57
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
58
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
59
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
60
|
+
5. Create new Pull Request
|
61
|
+
|
data/Rakefile
ADDED
data/deep_end.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'deep_end/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "deep_end"
|
8
|
+
gem.version = DeepEnd::VERSION
|
9
|
+
gem.authors = ["Pedr Browne"]
|
10
|
+
gem.email = ["pedr.browne@gmail.com"]
|
11
|
+
gem.description = %q{Simple dependency resolver}
|
12
|
+
gem.summary = %q{This gem processes a list of objects and their dependencies, ordering them so that dependencies are correctly resolved and checking for circular dependencies.}
|
13
|
+
gem.homepage = "https://github.com/stationkeeping/Deep-End"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "log4r"
|
21
|
+
|
22
|
+
gem.add_development_dependency 'rake'
|
23
|
+
gem.add_development_dependency 'rspec'
|
24
|
+
end
|
data/lib/deep_end.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require "deep_end/version"
|
2
|
+
|
3
|
+
module DeepEnd
|
4
|
+
|
5
|
+
# Errors
|
6
|
+
class SelfDependencyError < StandardError; end
|
7
|
+
class CircularDependencyError < StandardError; end
|
8
|
+
|
9
|
+
# Graph Node
|
10
|
+
class Node
|
11
|
+
|
12
|
+
attr_reader :key
|
13
|
+
attr_accessor :seen
|
14
|
+
attr_reader :edges
|
15
|
+
|
16
|
+
def initialize(key)
|
17
|
+
@key = key
|
18
|
+
@edges = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def addEdge(node)
|
22
|
+
@edges << node
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
# Dependency Graph
|
28
|
+
class Graph
|
29
|
+
|
30
|
+
def resolved_dependencies
|
31
|
+
a = []
|
32
|
+
@resolved.each{|node| a << node.key}
|
33
|
+
return a
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
reset
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add a new node, causing dependencies to be re-evaluated
|
41
|
+
def add_dependency(key, dependencies = [])
|
42
|
+
|
43
|
+
raise SelfDependencyError, "An object's dependencies cannot contain itself" if dependencies.include? key
|
44
|
+
|
45
|
+
node = node_for_key_or_new key
|
46
|
+
dependencies.each do |dependency|
|
47
|
+
node.addEdge(node_for_key_or_new(dependency))
|
48
|
+
end
|
49
|
+
resolve_dependencies
|
50
|
+
return @resolved
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the graph to its virgin state
|
54
|
+
def reset
|
55
|
+
@resolved = []
|
56
|
+
@seen_this_pass
|
57
|
+
@nodes = []
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
# Recurse through nodes
|
63
|
+
def resolve_dependencies
|
64
|
+
reset_seen
|
65
|
+
@resolved = []
|
66
|
+
@nodes.each do |node|
|
67
|
+
@seen_this_pass = []
|
68
|
+
resolve_dependency node unless node.seen
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Recurse through node edges
|
73
|
+
def resolve_dependency(node)
|
74
|
+
node.seen = true
|
75
|
+
@seen_this_pass << node
|
76
|
+
|
77
|
+
node.edges.each do |edge|
|
78
|
+
unless @resolved.include? edge
|
79
|
+
unless @seen_this_pass.include? edge
|
80
|
+
unless edge.seen
|
81
|
+
resolve_dependency edge
|
82
|
+
end
|
83
|
+
else
|
84
|
+
raise CircularDependencyError, "Circular reference detected: #{node.key.to_s} - #{edge.key.to_s}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
@resolved << node
|
89
|
+
end
|
90
|
+
|
91
|
+
def key_exists(key)
|
92
|
+
return node_for_key(key).present?
|
93
|
+
end
|
94
|
+
|
95
|
+
def node_for_key(key)
|
96
|
+
@nodes.each { |node| return node if node.key == key }
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
def node_for_key_or_new(key)
|
101
|
+
existing_node = node_for_key(key)
|
102
|
+
if existing_node
|
103
|
+
return existing_node
|
104
|
+
else
|
105
|
+
node = Node.new key
|
106
|
+
@nodes << node
|
107
|
+
return node
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def reset_seen
|
112
|
+
@nodes.each{ |node| node.seen = false}
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module DeepEnd
|
4
|
+
|
5
|
+
describe Graph do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@graph = Graph.new
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'when first created' do
|
12
|
+
it 'should have no resolved dependencies' do
|
13
|
+
@graph.resolved_dependencies.should be_empty
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when given dendencies' do
|
18
|
+
|
19
|
+
# Create dependencies
|
20
|
+
before(:each) do
|
21
|
+
@dependency_a = {name: 'a'}
|
22
|
+
@dependency_b = {name: 'b'}
|
23
|
+
@dependency_c = {name: 'c'}
|
24
|
+
@dependency_d = {name: 'd'}
|
25
|
+
@dependency_e = {name: 'e'}
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should not be empty' do
|
29
|
+
@graph.add_dependency @dependency_a
|
30
|
+
sorted_objects = @graph.resolved_dependencies.should_not be_empty
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should contain the dependency' do
|
34
|
+
@graph.add_dependency @dependency_a
|
35
|
+
sorted_objects = @graph.resolved_dependencies.should include(@dependency_a)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should maintain order of non-interdependent objects' do
|
39
|
+
# Add dependencies
|
40
|
+
@graph.add_dependency @dependency_a
|
41
|
+
@graph.add_dependency @dependency_b
|
42
|
+
@graph.add_dependency @dependency_c
|
43
|
+
# Check order
|
44
|
+
sorted_objects = @graph.resolved_dependencies
|
45
|
+
sorted_objects[0].should == @dependency_a
|
46
|
+
sorted_objects[1].should == @dependency_b
|
47
|
+
sorted_objects[2].should == @dependency_c
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should correctly order interdependent objects' do
|
51
|
+
# Add dependencies
|
52
|
+
@graph.add_dependency @dependency_c, [@dependency_b, @dependency_a]
|
53
|
+
@graph.add_dependency @dependency_b, [@dependency_a]
|
54
|
+
@graph.add_dependency @dependency_a
|
55
|
+
# Check order
|
56
|
+
sorted_objects = @graph.resolved_dependencies
|
57
|
+
sorted_objects[0].should == @dependency_a
|
58
|
+
sorted_objects[1].should == @dependency_b
|
59
|
+
sorted_objects[2].should == @dependency_c
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should raise a SelfDependencyError if an object is added as its own dependency' do
|
63
|
+
# Add dependencies
|
64
|
+
expect { @graph.add_dependency @dependency_a, [@dependency_a] }.to raise_error(SelfDependencyError)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should raise a CircularDependencyError if objects are added with direct circular dependencies' do
|
68
|
+
# Add dependencies
|
69
|
+
@graph.add_dependency @dependency_b, [@dependency_a]
|
70
|
+
expect { @graph.add_dependency @dependency_a, [@dependency_b] }.to raise_error(CircularDependencyError)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should raise a CircularDependencyError if objects are added with indirect circular dependencies' do
|
74
|
+
# Add dependencies
|
75
|
+
@graph.add_dependency @dependency_b, [@dependency_c]
|
76
|
+
@graph.add_dependency @dependency_c, [@dependency_a]
|
77
|
+
expect { @graph.add_dependency @dependency_a, [@dependency_b] }.to raise_error(CircularDependencyError)
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when reset" do
|
81
|
+
|
82
|
+
it 'should have no resolved dependencies' do
|
83
|
+
# Add dependencies
|
84
|
+
@graph.add_dependency @dependency_c, [@dependency_b, @dependency_a]
|
85
|
+
@graph.add_dependency @dependency_b, [@dependency_a]
|
86
|
+
@graph.add_dependency @dependency_a
|
87
|
+
@graph.reset
|
88
|
+
@graph.resolved_dependencies.length.should == 0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'deep_end'
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deep_end
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pedr Browne
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: log4r
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Simple dependency resolver
|
63
|
+
email:
|
64
|
+
- pedr.browne@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- deep_end.gemspec
|
75
|
+
- deep_end.sublime-project
|
76
|
+
- lib/deep_end.rb
|
77
|
+
- lib/deep_end/version.rb
|
78
|
+
- spec/lib/deep_end_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
homepage: https://github.com/stationkeeping/Deep-End
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.8.23
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: This gem processes a list of objects and their dependencies, ordering them
|
104
|
+
so that dependencies are correctly resolved and checking for circular dependencies.
|
105
|
+
test_files:
|
106
|
+
- spec/lib/deep_end_spec.rb
|
107
|
+
- spec/spec_helper.rb
|