codependency 0.3.1 → 1.0.0

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/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