codependency 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/Guardfile +4 -4
  2. data/README.md +7 -4
  3. data/codependency.gemspec +6 -4
  4. data/lib/codependency.rb +1 -0
  5. data/lib/codependency/graph.rb +27 -34
  6. data/lib/codependency/parser.rb +11 -15
  7. data/lib/codependency/path.rb +53 -0
  8. data/lib/codependency/version.rb +1 -1
  9. data/spec/codependency/graph_spec.rb +89 -51
  10. data/spec/codependency/graph_spec/codependency_graph/files/assets/application.txt +6 -0
  11. data/spec/codependency/graph_spec/codependency_graph/files/assets/templates_account.txt +3 -0
  12. data/spec/codependency/graph_spec/codependency_graph/files/assets/templates_history.txt +3 -0
  13. data/spec/codependency/graph_spec/codependency_graph/files/assets/templates_user.txt +5 -0
  14. data/spec/codependency/graph_spec/codependency_graph/files/breakfast/butter.txt +3 -0
  15. data/spec/codependency/graph_spec/codependency_graph/files/breakfast/egg.txt +4 -0
  16. data/spec/codependency/graph_spec/codependency_graph/files/breakfast/sandwich.txt +6 -0
  17. data/spec/codependency/graph_spec/codependency_graph/files/breakfast/toast.txt +4 -0
  18. data/spec/codependency/graph_spec/codependency_graph/files/solar_system/body.txt +3 -0
  19. data/spec/codependency/graph_spec/codependency_graph/files/solar_system/earth.txt +5 -0
  20. data/spec/codependency/graph_spec/codependency_graph/files/solar_system/mars.txt +5 -0
  21. data/spec/codependency/graph_spec/codependency_graph/files/solar_system/phobos.txt +6 -0
  22. data/spec/codependency/graph_spec/codependency_graph/files/solar_system/planet.txt +4 -0
  23. data/spec/codependency/graph_spec/codependency_graph/require/assets/application.txt +11 -0
  24. data/spec/codependency/graph_spec/codependency_graph/require/assets/templates_account.txt +3 -0
  25. data/spec/codependency/graph_spec/codependency_graph/require/assets/templates_history.txt +3 -0
  26. data/spec/codependency/graph_spec/codependency_graph/require/assets/templates_user.txt +8 -0
  27. data/spec/codependency/graph_spec/codependency_graph/require/breakfast/butter.txt +3 -0
  28. data/spec/codependency/graph_spec/codependency_graph/require/breakfast/egg.txt +6 -0
  29. data/spec/codependency/graph_spec/codependency_graph/require/breakfast/sandwich.txt +13 -0
  30. data/spec/codependency/graph_spec/codependency_graph/require/breakfast/toast.txt +6 -0
  31. data/spec/codependency/graph_spec/codependency_graph/require/lox/money.txt +11 -0
  32. data/spec/codependency/graph_spec/codependency_graph/require/lox/power.txt +11 -0
  33. data/spec/codependency/graph_spec/codependency_graph/require/lox/respect.txt +11 -0
  34. data/spec/codependency/graph_spec/codependency_graph/require/solar_system/body.txt +3 -0
  35. data/spec/codependency/graph_spec/codependency_graph/require/solar_system/earth.txt +9 -0
  36. data/spec/codependency/graph_spec/codependency_graph/require/solar_system/mars.txt +9 -0
  37. data/spec/codependency/graph_spec/codependency_graph/require/solar_system/phobos.txt +13 -0
  38. data/spec/codependency/graph_spec/codependency_graph/require/solar_system/planet.txt +6 -0
  39. data/spec/codependency/graph_spec/codependency_graph/tsort/assets/application.txt +6 -0
  40. data/spec/codependency/graph_spec/codependency_graph/tsort/assets/templates_account.txt +3 -0
  41. data/spec/codependency/graph_spec/codependency_graph/tsort/assets/templates_history.txt +3 -0
  42. data/spec/codependency/graph_spec/codependency_graph/tsort/assets/templates_user.txt +5 -0
  43. data/spec/codependency/graph_spec/codependency_graph/tsort/breakfast/butter.txt +3 -0
  44. data/spec/codependency/graph_spec/codependency_graph/tsort/breakfast/egg.txt +4 -0
  45. data/spec/codependency/graph_spec/codependency_graph/tsort/breakfast/sandwich.txt +6 -0
  46. data/spec/codependency/graph_spec/codependency_graph/tsort/breakfast/toast.txt +4 -0
  47. data/spec/codependency/graph_spec/codependency_graph/tsort/solar_system/body.txt +3 -0
  48. data/spec/codependency/graph_spec/codependency_graph/tsort/solar_system/earth.txt +5 -0
  49. data/spec/codependency/graph_spec/codependency_graph/tsort/solar_system/mars.txt +5 -0
  50. data/spec/codependency/graph_spec/codependency_graph/tsort/solar_system/phobos.txt +6 -0
  51. data/spec/codependency/graph_spec/codependency_graph/tsort/solar_system/planet.txt +4 -0
  52. data/spec/codependency/parser_spec.rb +48 -40
  53. data/spec/codependency/path_spec.rb +55 -0
  54. data/spec/fixtures/assets/application.js +1 -0
  55. data/spec/fixtures/assets/templates/account.js +1 -0
  56. data/spec/fixtures/assets/templates/history.js +1 -0
  57. data/spec/fixtures/assets/templates/user.js +4 -0
  58. data/spec/fixtures/breakfast/butter.js +1 -0
  59. data/spec/fixtures/breakfast/egg.js +3 -0
  60. data/spec/fixtures/breakfast/sandwich.js +4 -0
  61. data/spec/fixtures/breakfast/toast.js +3 -0
  62. data/spec/fixtures/lox/money.rb +4 -0
  63. data/spec/fixtures/lox/power.rb +4 -0
  64. data/spec/fixtures/lox/respect.rb +4 -0
  65. data/spec/fixtures/solar_system/body.rb +2 -0
  66. data/spec/fixtures/solar_system/earth.rb +4 -0
  67. data/spec/fixtures/solar_system/mars.rb +4 -0
  68. data/spec/fixtures/solar_system/phobos.rb +5 -0
  69. data/spec/fixtures/solar_system/planet.rb +4 -0
  70. data/spec/spec_helper.rb +5 -2
  71. data/spec/support/approvals.rb +1 -0
  72. metadata +146 -17
  73. data/spec/support/breakfasts_context.rb +0 -24
  74. data/spec/support/circular_context.rb +0 -25
  75. data/spec/support/file_system_stubs.rb +0 -10
  76. data/spec/support/planets_context.rb +0 -38
  77. data/spec/support/subdirectories_context.rb +0 -21
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  guard 'rspec' do
2
- watch(%r{^spec/.+_spec\.rb$})
3
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
- watch('spec/spec_helper.rb') { 'spec' }
5
- watch(%r{spec/support/.+\.rb}){ 'spec' }
2
+ watch( %r{^spec/.+_spec\.rb$} )
3
+ watch( %r{^lib/(.+)\.rb$} ){ |m| "spec/#{m[1]}_spec.rb" }
4
+ watch( 'spec/spec_helper.rb' ){ 'spec' }
5
+ watch( %r{spec/support/.+\.rb} ){ 'spec' }
6
6
  end
data/README.md CHANGED
@@ -18,12 +18,14 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- Say you have two files, `bar.rb` and `foo.rb`. `bar` comes before `foo` in a natural naming scheme, but `bar` has a dependency on `foo`. We can express this using a simple comment syntax at the head of the file like this:
21
+ Say you have two files, `bar.rb` and `foo.rb`. `bar` comes before `foo` in a
22
+ natural naming scheme, but `bar` has a dependency on `foo`. We can express
23
+ this using a simple comment syntax at the head of the file like this:
22
24
 
23
25
  **bar.rb:**
24
26
 
25
27
  ``` rb
26
- # require foo
28
+ #= require foo
27
29
 
28
30
  class Bar
29
31
  end
@@ -39,8 +41,9 @@ end
39
41
  Then, we create a dependency graph to determine the order in which the files might need to be loaded, inserted, or compiled:
40
42
 
41
43
  ``` rb
42
- graph = Codependency::Graph.new "bar.rb"
43
- graph.files # => ["./foo.rb", "./bar.rb"]
44
+ graph = Codependency::Graph.new
45
+ graph.path << '.' # works like PATH, append search paths for this graph
46
+ graph.files # => ["./foo.rb", "./bar.rb"] # returns a topologically sorted list of relative filepaths
44
47
  ```
45
48
 
46
49
  ## Contributing
@@ -6,7 +6,8 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["jeremy.ruppel@gmail.com"]
7
7
  gem.description = %q{A pure ruby, comment-based dependency graph}
8
8
  gem.summary = %q{A pure ruby, comment-based dependency graph}
9
- gem.homepage = ""
9
+ gem.homepage = 'https://github.com/jeremyruppel/codependency'
10
+ gem.license = 'MIT'
10
11
 
11
12
  gem.files = `git ls-files`.split($\)
12
13
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -15,7 +16,8 @@ Gem::Specification.new do |gem|
15
16
  gem.require_paths = ["lib"]
16
17
  gem.version = Codependency::VERSION
17
18
 
18
- gem.add_development_dependency 'rspec', '2.11.0'
19
- gem.add_development_dependency 'guard-rspec', '1.2.1'
20
- gem.add_development_dependency 'rb-fsevent', '0.9.1'
19
+ gem.add_development_dependency 'rspec', '2.12.0'
20
+ gem.add_development_dependency 'guard-rspec', '1.2.1'
21
+ gem.add_development_dependency 'rb-fsevent', '0.9.1'
22
+ gem.add_development_dependency 'git-approvals', '0.2.0'
21
23
  end
@@ -3,4 +3,5 @@ require 'codependency/version'
3
3
  module Codependency
4
4
  autoload :Graph, 'codependency/graph'
5
5
  autoload :Parser, 'codependency/parser'
6
+ autoload :Path, 'codependency/path'
6
7
  end
@@ -2,59 +2,52 @@ require 'tsort'
2
2
 
3
3
  module Codependency
4
4
  class Graph < Hash
5
- def initialize( path, options={} )
6
- @path, @options = path, options
7
-
8
- super( ){ |h, k| h[ k ] = parser.parse( k ) }
9
- end
10
- attr_reader :path, :options
11
-
12
- include TSort
13
5
 
14
6
  ##
15
- # the dirname to use for this graph, based on the path
16
- def dirname
17
- File.dirname path
18
- end
19
-
20
- ##
21
- # the extname to use for this graph, based on the path
22
- def extname
23
- File.extname path
7
+ # Adds a file to the dependency graph. The filename given should be
8
+ # relative and should not contain an extension, like:
9
+ #
10
+ # # require application/user
11
+ #
12
+ # Attempts to locate and add all dependencies of this file recursively,
13
+ # creating our graph.
14
+ def require( file )
15
+ self[ file ] ||= parser.parse path[ file ]
16
+ self[ file ].each do |file|
17
+ self << file unless key?( file )
18
+ end
24
19
  end
20
+ alias :<< :require
25
21
 
26
22
  ##
27
- # walk the entire graph and return self
28
- def populate
29
- walk path
30
- self
23
+ # Returns the sorted list of files as determined by this graph,
24
+ # relative to the calling file.
25
+ def files
26
+ here = Pathname( caller.first.split( ':' ).shift )
27
+ tsort.map { |file| path[ file ].relative_path_from( here ) }
31
28
  end
32
29
 
33
30
  ##
34
- # discover all nodes in this graph by walking it
35
- def walk( path )
36
- self[ path ].each { |path| walk( path ) unless has_key?( path ) }
31
+ # The path set for this dependency graph.
32
+ def path
33
+ @path ||= Path.new parser.extensions
37
34
  end
38
35
 
39
36
  ##
40
- # the parser to use for this graph. shared by all nodes.
37
+ # The file parser for this dependency graph.
41
38
  def parser
42
- @parser ||= begin
43
- Parser.new options.merge( :dirname => dirname, :extname => extname )
44
- end
45
- end
46
-
47
- ##
48
- # a topologically sorted list of all dependencies of the `start` file.
49
- def files
50
- populate.tsort
39
+ @parser ||= Parser.new
51
40
  end
52
41
 
53
42
  private
54
43
 
44
+ include TSort
45
+
46
+ ##
55
47
  # tsort interface
56
48
  alias :tsort_each_node :each_key
57
49
 
50
+ ##
58
51
  # tsort interface
59
52
  def tsort_each_child( node, &block )
60
53
  fetch( node ).each( &block )
@@ -1,28 +1,24 @@
1
1
  module Codependency
2
2
  class Parser
3
3
 
4
- def initialize( options={} )
5
- @options = options
6
- @comment = options.delete( :comment ) || '#'
7
- @dirname = options.delete( :dirname ) || '.'
8
- @extname = options.delete( :extname ) || '.rb'
9
- end
4
+ PATTERNS = Hash.new { |hash, key|
5
+ raise "Unknown extension '#{key}'. Known extensions are #{hash.keys.inspect}."
6
+ }
7
+ PATTERNS[ '.rb' ] = /^#= require ([\w\/]+)$/
8
+ PATTERNS[ '.js' ] = /^\/\/= require ([\w\/]+)$/
10
9
 
11
10
  ##
12
- # determines a file's dependencies based on the configured comment pattern.
11
+ # Determines a file's dependencies based on the file's extension.
13
12
  def parse( file )
13
+ pattern = PATTERNS[ File.extname( file ) ]
14
+
14
15
  IO.readlines( file ).take_while do |line|
15
16
  line =~ pattern
16
- end.map { |line| "#{@dirname}/#{line[ pattern, 1 ]}#{@extname}" }
17
+ end.map { |line| line[ pattern, 1 ] }
17
18
  end
18
19
 
19
- protected
20
-
21
- ##
22
- # the comment pattern to use.
23
- # TODO allow this to be more configurable
24
- def pattern
25
- @pattern ||= /^#{@comment} require (.+)$/
20
+ def extensions
21
+ PATTERNS.keys
26
22
  end
27
23
  end
28
24
  end
@@ -0,0 +1,53 @@
1
+ require 'pathname'
2
+
3
+ module Codependency
4
+ class Path < Array
5
+
6
+ def initialize( extensions=%w| .rb | )
7
+ @extensions = Array(extensions)
8
+ end
9
+ attr_reader :extensions
10
+
11
+ ##
12
+ # Appends a path to this path set. If the path exists, it
13
+ # will be expanded. Raises Errno::ENOENT if the path
14
+ # does not exist. Raises Errno::ENOTDIR if the path
15
+ # is not a directory.
16
+ def <<( str )
17
+ path = Pathname( str )
18
+
19
+ case
20
+ when !path.exist?
21
+ raise Errno::ENOENT, path.to_path
22
+ when !path.directory?
23
+ raise Errno::ENOTDIR, path.to_path
24
+ else
25
+ super path.expand_path
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Sugar syntax for calling #find if given a string.
31
+ # Otherwise, behaves like normal array access.
32
+ def []( path_or_index )
33
+ if path_or_index.is_a? String
34
+ find path_or_index
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Attempts to find the given file in this path set.
42
+ # Raises Errno::ENOENT if the file cannot be found.
43
+ def find( str )
44
+ super lambda { raise Errno::ENOENT, str } do |dir|
45
+ path = extensions.find do |ext|
46
+ file = dir.join str + ext
47
+ break file if file.exist?
48
+ end
49
+ break path if path
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module Codependency
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -1,74 +1,112 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Codependency::Graph do
4
- describe 'accessors' do
5
- subject { described_class.new 'planets/phobos.rb' }
6
- its( :path ){ should eq( 'planets/phobos.rb' ) }
7
- its( :dirname ){ should eq( 'planets' ) }
8
- its( :extname ){ should eq( '.rb' ) }
9
- its( :options ){ should eq( { } ) }
10
- end
4
+ it { should be_a( Hash ) }
11
5
 
12
- describe 'populate', :files => :planets do
13
- subject { described_class.new './phobos.rb' }
6
+ describe '#path' do
7
+ its( :path ){ should be_a( Codependency::Path ) }
8
+ its( :path ){ should be_empty }
9
+ end
14
10
 
15
- context 'initially' do
16
- it { should be_empty }
17
- end
11
+ describe '#require' do
12
+ let( :dirname ){ example.example_group.description }
13
+ let( :basename ){ example.description }
14
+ let( :file ){ File.join dirname, basename }
18
15
 
19
- context 'after calling populate' do
20
- before { subject.populate }
16
+ before { subject.path << './spec/fixtures' }
17
+ before { subject << file }
21
18
 
22
- it { should include( './phobos.rb' => [ './body.rb', './mars.rb' ] ) }
23
- it { should include( './mars.rb' => [ './planet.rb' ] ) }
24
- it { should include( './planet.rb' => [ './body.rb' ] ) }
25
- it { should include( './body.rb' => [ ] ) }
19
+ describe 'solar_system' do
20
+ example( 'body' ){ verify { subject } }
21
+ example( 'earth' ){ verify { subject } }
22
+ example( 'mars' ){ verify { subject } }
23
+ example( 'phobos' ){ verify { subject } }
24
+ example( 'planet' ){ verify { subject } }
26
25
  end
27
-
28
- it 'should return itself' do
29
- subject.populate.should == subject
26
+ describe 'breakfast' do
27
+ example( 'butter' ){ verify { subject } }
28
+ example( 'egg' ){ verify { subject } }
29
+ example( 'sandwich' ){ verify { subject } }
30
+ example( 'toast' ){ verify { subject } }
30
31
  end
31
- end
32
-
33
- context 'planets', :files => :planets do
34
- subject { described_class.new start }
35
-
36
- context 'earth' do
37
- let( :start ){ './earth.rb' }
38
- its( :files ){ should eq( %w| ./body.rb ./planet.rb ./earth.rb | ) }
32
+ describe 'lox' do
33
+ example( 'money' ){ verify { subject } }
34
+ example( 'power' ){ verify { subject } }
35
+ example( 'respect' ){ verify { subject } }
39
36
  end
40
-
41
- context 'phobos' do
42
- let( :start ){ './phobos.rb' }
43
- its( :files ){ should eq( %w| ./body.rb ./planet.rb ./mars.rb ./phobos.rb | ) }
37
+ describe 'assets' do
38
+ example( 'templates/account' ){ verify { subject } }
39
+ example( 'templates/history' ){ verify { subject } }
40
+ example( 'templates/user' ){ verify { subject } }
41
+ example( 'application' ){ verify { subject } }
44
42
  end
45
43
  end
46
44
 
47
- context 'breakfasts', :files => :breakfasts do
48
- subject { described_class.new start, :comment => '//' }
45
+ describe '#tsort' do
46
+ let( :dirname ){ example.example_group.description }
47
+ let( :basename ){ example.description }
48
+ let( :file ){ File.join dirname, basename }
49
+
50
+ before { subject.path << './spec/fixtures' }
51
+ before { subject << file }
49
52
 
50
- context 'sandwich' do
51
- let( :start ){ './sandwich.js' }
52
- its( :files ){ should eq( %w| ./butter.js ./egg.js ./toast.js ./sandwich.js | ) }
53
+ describe 'solar_system' do
54
+ example( 'body' ){ verify { subject.tsort } }
55
+ example( 'earth' ){ verify { subject.tsort } }
56
+ example( 'mars' ){ verify { subject.tsort } }
57
+ example( 'phobos' ){ verify { subject.tsort } }
58
+ example( 'planet' ){ verify { subject.tsort } }
59
+ end
60
+ describe 'breakfast' do
61
+ example( 'butter' ){ verify { subject.tsort } }
62
+ example( 'egg' ){ verify { subject.tsort } }
63
+ example( 'sandwich' ){ verify { subject.tsort } }
64
+ example( 'toast' ){ verify { subject.tsort } }
65
+ end
66
+ describe 'lox' do
67
+ example( 'money' ){ expect { subject.tsort }.to raise_error( TSort::Cyclic ) }
68
+ example( 'power' ){ expect { subject.tsort }.to raise_error( TSort::Cyclic ) }
69
+ example( 'respect' ){ expect { subject.tsort }.to raise_error( TSort::Cyclic ) }
70
+ end
71
+ describe 'assets' do
72
+ example( 'templates/account' ){ verify { subject.tsort } }
73
+ example( 'templates/history' ){ verify { subject.tsort } }
74
+ example( 'templates/user' ){ verify { subject.tsort } }
75
+ example( 'application' ){ verify { subject.tsort } }
53
76
  end
54
77
  end
55
78
 
56
- context 'circular dependencies', :files => :circular do
57
- subject { described_class.new start }
79
+ describe '#files' do
80
+ let( :dirname ){ example.example_group.description }
81
+ let( :basename ){ example.description }
82
+ let( :file ){ File.join dirname, basename }
58
83
 
59
- let( :start ){ './money.rb' }
84
+ before { subject.path << './spec/fixtures' }
85
+ before { subject << file }
60
86
 
61
- it 'should raise an error' do
62
- expect { subject.files }.to raise_error( TSort::Cyclic )
87
+ describe 'solar_system' do
88
+ example( 'body' ){ verify { subject.files } }
89
+ example( 'earth' ){ verify { subject.files } }
90
+ example( 'mars' ){ verify { subject.files } }
91
+ example( 'phobos' ){ verify { subject.files } }
92
+ example( 'planet' ){ verify { subject.files } }
63
93
  end
64
- end
65
-
66
- context 'subdirectories', :files => :subdirectories do
67
- subject { described_class.new start, :comment => '//' }
68
-
69
- context 'application.js' do
70
- let( :start ){ 'assets/application.js' }
71
- its( :files ){ should eq( %w| assets/templates/user/history.js assets/templates/user/account.js assets/templates/user.js assets/application.js | ) }
94
+ describe 'breakfast' do
95
+ example( 'butter' ){ verify { subject.files } }
96
+ example( 'egg' ){ verify { subject.files } }
97
+ example( 'sandwich' ){ verify { subject.files } }
98
+ example( 'toast' ){ verify { subject.files } }
99
+ end
100
+ describe 'lox' do
101
+ example( 'money' ){ expect { subject.tsort }.to raise_error( TSort::Cyclic ) }
102
+ example( 'power' ){ expect { subject.tsort }.to raise_error( TSort::Cyclic ) }
103
+ example( 'respect' ){ expect { subject.tsort }.to raise_error( TSort::Cyclic ) }
104
+ end
105
+ describe 'assets' do
106
+ example( 'templates/account' ){ verify { subject.files } }
107
+ example( 'templates/history' ){ verify { subject.files } }
108
+ example( 'templates/user' ){ verify { subject.files } }
109
+ example( 'application' ){ verify { subject.files } }
72
110
  end
73
111
  end
74
112
  end
@@ -0,0 +1,6 @@
1
+ [
2
+ [0] #<Pathname:../../fixtures/assets/templates/history.js>,
3
+ [1] #<Pathname:../../fixtures/assets/templates/account.js>,
4
+ [2] #<Pathname:../../fixtures/assets/templates/user.js>,
5
+ [3] #<Pathname:../../fixtures/assets/application.js>
6
+ ]
@@ -0,0 +1,3 @@
1
+ [
2
+ [0] #<Pathname:../../fixtures/assets/templates/account.js>
3
+ ]
@@ -0,0 +1,3 @@
1
+ [
2
+ [0] #<Pathname:../../fixtures/assets/templates/history.js>
3
+ ]
@@ -0,0 +1,5 @@
1
+ [
2
+ [0] #<Pathname:../../fixtures/assets/templates/history.js>,
3
+ [1] #<Pathname:../../fixtures/assets/templates/account.js>,
4
+ [2] #<Pathname:../../fixtures/assets/templates/user.js>
5
+ ]
@@ -0,0 +1,3 @@
1
+ [
2
+ [0] #<Pathname:../../fixtures/breakfast/butter.js>
3
+ ]