codependency 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Codependency is a simple comment-based dependency graph that you can use on arbitrary files.
4
4
 
5
- It uses the program `tsort` under the hood, so sorry windoze I guess?
6
-
7
5
  ## Installation
8
6
 
9
7
  Add this line to your application's Gemfile:
@@ -41,8 +39,8 @@ end
41
39
  Then, we create a dependency graph to determine the order in which the files might need to be loaded, inserted, or compiled:
42
40
 
43
41
  ``` rb
44
- graph = Codependency::Graph.new %w| bar.rb foo.rb |
45
- graph.files # => ["foo.rb", "bar.rb"]
42
+ graph = Codependency::Graph.new "bar.rb"
43
+ graph.files # => ["./foo.rb", "./bar.rb"]
46
44
  ```
47
45
 
48
46
  ## Contributing
@@ -4,8 +4,8 @@ require File.expand_path('../lib/codependency/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Jeremy Ruppel"]
6
6
  gem.email = ["jeremy.ruppel@gmail.com"]
7
- gem.description = %q{Simple comment-based dependency graph for arbitrary files}
8
- gem.summary = %q{Simple comment-based dependency graph for arbitrary files}
7
+ gem.description = %q{A pure ruby, comment-based dependency graph}
8
+ gem.summary = %q{A pure ruby, comment-based dependency graph}
9
9
  gem.homepage = ""
10
10
 
11
11
  gem.files = `git ls-files`.split($\)
@@ -2,8 +2,5 @@ require 'codependency/version'
2
2
 
3
3
  module Codependency
4
4
  autoload :Graph, 'codependency/graph'
5
- autoload :Node, 'codependency/node'
6
5
  autoload :Parser, 'codependency/parser'
7
6
  end
8
-
9
- class CircularDependencyError < StandardError; end
@@ -1,46 +1,63 @@
1
- require 'open3'
1
+ require 'tsort'
2
2
 
3
3
  module Codependency
4
- class Graph
5
- def initialize( start, options={} )
6
- @options = options
7
- @nodes = Hash.new { |h, k| h[ k ] = Node.new( k, parser ) }
8
- @start = @nodes[ start ]
9
- end
10
-
11
- ##
12
- # a topologically sorted list of all dependencies of the `start` file.
13
- def files
14
- deps = resolve( @start, [ ] ).map( &:dependencies ).join ' '
4
+ class Graph < Hash
5
+ def initialize( path, options={} )
6
+ @path, @options = path, options
15
7
 
16
- cmd, out, err = Open3.popen3 "echo '#{deps}' | tsort"
8
+ super( ){ |h, k| h[ k ] = parser.parse( k ) }
9
+ end
10
+ attr_reader :path, :options
17
11
 
18
- if msg = err.gets
19
- raise CircularDependencyError, msg
20
- end
12
+ include TSort
21
13
 
22
- out.readlines.map( &:chomp ).reverse
14
+ ##
15
+ # the dirname to use for this graph, based on the path
16
+ def dirname
17
+ File.dirname path
23
18
  end
24
19
 
25
- protected
26
-
27
20
  ##
28
- # adds a node's dependencies to a list (memo).
29
- # intended to be used recursively.
30
- def resolve( node, list )
31
- list << node
21
+ # the extname to use for this graph, based on the path
22
+ def extname
23
+ File.extname path
24
+ end
32
25
 
33
- node.edges.map { |filename| @nodes[ filename ] }.each do |dep|
34
- resolve dep, list unless list.include?( dep )
35
- end
26
+ ##
27
+ # walk the entire graph and return self
28
+ def populate
29
+ walk path
30
+ self
31
+ end
36
32
 
37
- list
33
+ ##
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 ) }
38
37
  end
39
38
 
40
39
  ##
41
40
  # the parser to use for this graph. shared by all nodes.
42
41
  def parser
43
- @parser ||= Parser.new @options
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
51
+ end
52
+
53
+ private
54
+
55
+ # tsort interface
56
+ alias :tsort_each_node :each_key
57
+
58
+ # tsort interface
59
+ def tsort_each_child( node, &block )
60
+ fetch( node ).each( &block )
44
61
  end
45
62
  end
46
63
  end
@@ -4,6 +4,8 @@ module Codependency
4
4
  def initialize( options={} )
5
5
  @options = options
6
6
  @comment = options.delete( :comment ) || '#'
7
+ @dirname = options.delete( :dirname ) || '.'
8
+ @extname = options.delete( :extname ) || '.rb'
7
9
  end
8
10
 
9
11
  ##
@@ -11,7 +13,7 @@ module Codependency
11
13
  def parse( file )
12
14
  IO.readlines( file ).take_while do |line|
13
15
  line =~ pattern
14
- end.map { |line| line[ pattern, 1 ] }
16
+ end.map { |line| "#{@dirname}/#{line[ pattern, 1 ]}#{@extname}" }
15
17
  end
16
18
 
17
19
  protected
@@ -1,3 +1,3 @@
1
1
  module Codependency
2
- VERSION = '0.3.1'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -1,45 +1,74 @@
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
11
+
12
+ describe 'populate', :files => :planets do
13
+ subject { described_class.new './phobos.rb' }
14
+
15
+ context 'initially' do
16
+ it { should be_empty }
17
+ end
18
+
19
+ context 'after calling populate' do
20
+ before { subject.populate }
21
+
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' => [ ] ) }
26
+ end
27
+
28
+ it 'should return itself' do
29
+ subject.populate.should == subject
30
+ end
31
+ end
32
+
4
33
  context 'planets', :files => :planets do
5
- subject { Codependency::Graph.new graph }
34
+ subject { described_class.new start }
6
35
 
7
36
  context 'earth' do
8
- let( :graph ){ 'earth.rb' }
9
- its( :files ){ should eq( %w| body.rb planet.rb earth.rb | ) }
37
+ let( :start ){ './earth.rb' }
38
+ its( :files ){ should eq( %w| ./body.rb ./planet.rb ./earth.rb | ) }
10
39
  end
11
40
 
12
41
  context 'phobos' do
13
- let( :graph ){ 'phobos.rb' }
14
- its( :files ){ should eq( %w| body.rb planet.rb mars.rb phobos.rb | ) }
42
+ let( :start ){ './phobos.rb' }
43
+ its( :files ){ should eq( %w| ./body.rb ./planet.rb ./mars.rb ./phobos.rb | ) }
15
44
  end
16
45
  end
17
46
 
18
47
  context 'breakfasts', :files => :breakfasts do
19
- subject { Codependency::Graph.new graph, :comment => '//' }
48
+ subject { described_class.new start, :comment => '//' }
20
49
 
21
50
  context 'sandwich' do
22
- let( :graph ){ 'sandwich.js' }
23
- its( :files ){ should eq( %w| butter.js egg.js toast.js sandwich.js | ) }
51
+ let( :start ){ './sandwich.js' }
52
+ its( :files ){ should eq( %w| ./butter.js ./egg.js ./toast.js ./sandwich.js | ) }
24
53
  end
25
54
  end
26
55
 
27
56
  context 'circular dependencies', :files => :circular do
28
- subject { Codependency::Graph.new graph }
57
+ subject { described_class.new start }
29
58
 
30
- let( :graph ){ 'money.rb' }
59
+ let( :start ){ './money.rb' }
31
60
 
32
61
  it 'should raise an error' do
33
- expect { subject.files }.to raise_error( CircularDependencyError )
62
+ expect { subject.files }.to raise_error( TSort::Cyclic )
34
63
  end
35
64
  end
36
65
 
37
66
  context 'subdirectories', :files => :subdirectories do
38
- subject { Codependency::Graph.new graph, :comment => '//' }
67
+ subject { described_class.new start, :comment => '//' }
39
68
 
40
69
  context 'application.js' do
41
- let( :graph ){ 'application.js' }
42
- its( :files ){ should eq( %w| templates/user/history.js templates/user/account.js templates/user.js application.js | ) }
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 | ) }
43
72
  end
44
73
  end
45
74
  end
@@ -5,45 +5,45 @@ describe Codependency::Parser do
5
5
  let( :parser ){ Codependency::Parser.new }
6
6
 
7
7
  context 'body' do
8
- subject { parser.parse( 'body.rb' ) }
8
+ subject { parser.parse( './body.rb' ) }
9
9
  it { should eq( [ ] ) }
10
10
  end
11
11
  context 'earth' do
12
- subject { parser.parse( 'earth.rb' ) }
13
- it { should eq( [ 'planet' ] ) }
12
+ subject { parser.parse( './earth.rb' ) }
13
+ it { should eq( [ './planet.rb' ] ) }
14
14
  end
15
15
  context 'mars' do
16
- subject { parser.parse( 'mars.rb' ) }
17
- it { should eq( [ 'planet' ] ) }
16
+ subject { parser.parse( './mars.rb' ) }
17
+ it { should eq( [ './planet.rb' ] ) }
18
18
  end
19
19
  context 'phobos' do
20
- subject { parser.parse( 'phobos.rb' ) }
21
- it { should eq( [ 'body', 'mars' ] ) }
20
+ subject { parser.parse( './phobos.rb' ) }
21
+ it { should eq( [ './body.rb', './mars.rb' ] ) }
22
22
  end
23
23
  context 'planet' do
24
- subject { parser.parse( 'planet.rb' ) }
25
- it { should eq( [ 'body' ] ) }
24
+ subject { parser.parse( './planet.rb' ) }
25
+ it { should eq( [ './body.rb' ] ) }
26
26
  end
27
27
  end
28
28
 
29
29
  context 'breakfasts', :files => :breakfasts do
30
- let( :parser ){ Codependency::Parser.new :comment => '//' }
30
+ let( :parser ){ Codependency::Parser.new :comment => '//', :extname => '.js' }
31
31
 
32
32
  context 'butter' do
33
- subject { parser.parse( 'butter.js' ) }
33
+ subject { parser.parse( './butter.js' ) }
34
34
  it { should eq( [ ] ) }
35
35
  end
36
36
  context 'egg' do
37
- subject { parser.parse( 'egg.js' ) }
38
- it { should eq( [ 'butter' ] ) }
37
+ subject { parser.parse( './egg.js' ) }
38
+ it { should eq( [ './butter.js' ] ) }
39
39
  end
40
40
  context 'toast' do
41
- subject { parser.parse( 'toast.js' ) }
42
- it { should eq( [ 'butter' ] ) }
41
+ subject { parser.parse( './toast.js' ) }
42
+ it { should eq( [ './butter.js' ] ) }
43
43
  end
44
44
  context 'sandwich' do
45
- subject { parser.parse( 'sandwich.js' ) }
46
- it { should eq( [ 'egg', 'toast' ] ) }
45
+ subject { parser.parse( './sandwich.js' ) }
46
+ it { should eq( [ './egg.js', './toast.js' ] ) }
47
47
  end
48
48
  end
49
49
  end
@@ -1,21 +1,21 @@
1
1
  shared_context 'breakfast foods', :files => :breakfasts do
2
2
 
3
3
  before do
4
- file 'butter.js', <<-EOS
4
+ file './butter.js', <<-EOS
5
5
  var Butter = { }
6
6
  EOS
7
7
 
8
- file 'egg.js', <<-EOS
8
+ file './egg.js', <<-EOS
9
9
  // require butter
10
10
  var Egg = { }
11
11
  EOS
12
12
 
13
- file 'toast.js', <<-EOS
13
+ file './toast.js', <<-EOS
14
14
  // require butter
15
15
  var Toast = { }
16
16
  EOS
17
17
 
18
- file 'sandwich.js', <<-EOS
18
+ file './sandwich.js', <<-EOS
19
19
  // require egg
20
20
  // require toast
21
21
  var Sandwich = { }
@@ -1,21 +1,21 @@
1
1
  shared_context 'circular dependencies', :files => :circular do
2
2
 
3
3
  before do
4
- file 'money.rb', <<-EOS
4
+ file './money.rb', <<-EOS
5
5
  # require power
6
6
 
7
7
  class Money
8
8
  end
9
9
  EOS
10
10
 
11
- file 'power.rb', <<-EOS
11
+ file './power.rb', <<-EOS
12
12
  # require respect
13
13
 
14
14
  class Power
15
15
  end
16
16
  EOS
17
17
 
18
- file 'respect.rb', <<-EOS
18
+ file './respect.rb', <<-EOS
19
19
  # require money
20
20
 
21
21
  class Respect
@@ -1,26 +1,26 @@
1
1
  shared_context 'solar system', :files => :planets do
2
2
 
3
3
  before do
4
- file 'body.rb', <<-EOS
4
+ file './body.rb', <<-EOS
5
5
  class Body
6
6
  end
7
7
  EOS
8
8
 
9
- file 'earth.rb', <<-EOS
9
+ file './earth.rb', <<-EOS
10
10
  # require planet
11
11
 
12
12
  class Earth
13
13
  end
14
14
  EOS
15
15
 
16
- file 'mars.rb', <<-EOS
16
+ file './mars.rb', <<-EOS
17
17
  # require planet
18
18
 
19
19
  class Mars
20
20
  end
21
21
  EOS
22
22
 
23
- file 'phobos.rb', <<-EOS
23
+ file './phobos.rb', <<-EOS
24
24
  # require body
25
25
  # require mars
26
26
 
@@ -28,7 +28,7 @@ shared_context 'solar system', :files => :planets do
28
28
  end
29
29
  EOS
30
30
 
31
- file 'planet.rb', <<-EOS
31
+ file './planet.rb', <<-EOS
32
32
  # require body
33
33
 
34
34
  class Planet
@@ -1,20 +1,20 @@
1
1
  shared_context 'subdirectories', :files => :subdirectories do
2
2
 
3
3
  before do
4
- file 'application.js', <<-EOS
4
+ file 'assets/application.js', <<-EOS
5
5
  // require templates/user
6
6
  EOS
7
7
 
8
- file 'templates/user.js', <<-EOS
8
+ file 'assets/templates/user.js', <<-EOS
9
9
  // require templates/user/history
10
10
  // require templates/user/account
11
11
  EOS
12
12
 
13
- file 'templates/user/history.js', <<-EOS
13
+ file 'assets/templates/user/history.js', <<-EOS
14
14
  var History = { }
15
15
  EOS
16
16
 
17
- file 'templates/user/account.js', <<-EOS
17
+ file 'assets/templates/user/account.js', <<-EOS
18
18
  var Account = { }
19
19
  EOS
20
20
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codependency
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-23 00:00:00.000000000 Z
12
+ date: 2012-09-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -59,7 +59,7 @@ dependencies:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
61
  version: 0.9.1
62
- description: Simple comment-based dependency graph for arbitrary files
62
+ description: A pure ruby, comment-based dependency graph
63
63
  email:
64
64
  - jeremy.ruppel@gmail.com
65
65
  executables: []
@@ -75,11 +75,9 @@ files:
75
75
  - codependency.gemspec
76
76
  - lib/codependency.rb
77
77
  - lib/codependency/graph.rb
78
- - lib/codependency/node.rb
79
78
  - lib/codependency/parser.rb
80
79
  - lib/codependency/version.rb
81
80
  - spec/codependency/graph_spec.rb
82
- - spec/codependency/node_spec.rb
83
81
  - spec/codependency/parser_spec.rb
84
82
  - spec/spec_helper.rb
85
83
  - spec/support/breakfasts_context.rb
@@ -110,10 +108,9 @@ rubyforge_project:
110
108
  rubygems_version: 1.8.19
111
109
  signing_key:
112
110
  specification_version: 3
113
- summary: Simple comment-based dependency graph for arbitrary files
111
+ summary: A pure ruby, comment-based dependency graph
114
112
  test_files:
115
113
  - spec/codependency/graph_spec.rb
116
- - spec/codependency/node_spec.rb
117
114
  - spec/codependency/parser_spec.rb
118
115
  - spec/spec_helper.rb
119
116
  - spec/support/breakfasts_context.rb
@@ -1,26 +0,0 @@
1
- module Codependency
2
- class Node
3
- def initialize( filename, parser )
4
- raise Errno::ENOENT, filename unless File.exist?( filename )
5
- @filename = filename
6
- @parser = parser
7
- end
8
- attr_reader :filename, :parser
9
-
10
- ##
11
- # all of this node's edges
12
- def edges
13
- @edges ||= begin
14
- parser.parse( filename ).map do |f|
15
- "#{f}#{File.extname( filename )}"
16
- end
17
- end
18
- end
19
-
20
- ##
21
- # a string representing this node's edges, formatted for `tsort`.
22
- def dependencies
23
- edges.map { |edge| [ filename, edge ] }.flatten.join ' '
24
- end
25
- end
26
- end
@@ -1,25 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Codependency::Node do
4
- let( :parser ){ double 'Parser', :parse => [ 'body' ] }
5
-
6
- context 'when the file exists', :files => :planets do
7
- subject { Codependency::Node.new 'planet.rb', parser }
8
- its( :edges ){ should eq( [ 'body.rb' ] ) }
9
- end
10
-
11
- context 'when the file does not exist' do
12
- it 'should raise an error' do
13
- expect {
14
- Codependency::Node.new 'pluto.rb', parser
15
- }.to raise_error( Errno::ENOENT, 'No such file or directory - pluto.rb' )
16
- end
17
- end
18
-
19
- describe 'dependencies', :files => :planets do
20
- let( :parser ){ double 'Parser', :parse => [ 'body', 'mars' ] }
21
- subject { Codependency::Node.new 'phobos.rb', parser }
22
-
23
- its( :dependencies ){ should eq( 'phobos.rb body.rb phobos.rb mars.rb' ) }
24
- end
25
- end