rubydeps 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -12,24 +12,30 @@ Rubydeps will run your test suite to record the call graph of your project and u
12
12
  First of all, be sure to step into the root directory of your project, rubydeps searches for ./spec or ./test dirs from there.
13
13
  For example, if we want to graph the Rails activemodel dependency graph we'd cd to rails/activemodel and from there we'd write:
14
14
 
15
- rubydeps testunit --path-filter='activemodel/lib' # to run Test::Unit tests
15
+ rubydeps testunit #to run Test::Unit tests
16
16
  or
17
- rubydeps rspec --path-filter='activemodel/lib' # to run RSpec tests
17
+ rubydeps rspec #to run RSpec tests
18
18
  or
19
- rubydeps rspec2 --path-filter='activemodel/lib' # to run RSpec 2 tests
19
+ rubydeps rspec2 #to run RSpec 2 tests
20
20
 
21
- This will output a rubydeps.dot. The --path-filter option specifies a regexp that matches the path of the files you are interested in analyzing. For example you could have filters like 'project_name/app|project_name/lib' to analyze only code that is located in the 'app' and 'lib' dirs or as an alternative you could just exclude some directory you are not interested using a negative regexp like 'project_name(?!.*test)'
22
- You can convert the dot file to any image format you like using the dot utility that comes with the graphviz installation e.g.:
21
+ This will output a rubydeps.dot. You can convert the dot file to any image format you like using the dot utility that comes with the graphviz installation e.g.:
23
22
 
24
23
  dot -Tsvg rubydeps.dot > rubydeps.svg
25
24
 
25
+ ### Command line options
26
+
27
+ The --path-filter option specifies a regexp that matches the path of the files you are interested in analyzing. For example you could have filters like 'project_name/app|project_name/lib' to analyze only code that is located in the 'app' and 'lib' dirs or as an alternative you could just exclude some directory you are not interested using a negative regexp like 'project_name(?!.*test)'
28
+
29
+ The --class_name_filter option is similar to the --path_filter options except that the regexp is matched against the class names (i.e. graph node names).
26
30
 
27
31
  Library usage
28
32
  -------------
29
33
 
30
34
  Just require rubydeps and pass a block to analyze to the dot_for method.
31
35
 
32
- Rubydeps.dot_for(path_filter_regexp) do
36
+ require 'rubydeps'
37
+
38
+ Rubydeps.dot_for(:path_filter => path_filter_regexp, :class_name_filter) do
33
39
  //your code goes here
34
40
  end
35
41
 
@@ -42,6 +48,11 @@ This is the result of running rubydeps on the [Mechanize](http://github.com/tend
42
48
 
43
49
  Notice that sometimes you may have missing dependencies as we graph the dependencies exercised by your tests so it's a quick bird's eye view to check your project coverage.
44
50
 
51
+ Installation
52
+ ------------
53
+
54
+ gem install rubydeps
55
+
45
56
  Dependencies
46
57
  ------------
47
58
 
data/bin/rubydeps CHANGED
@@ -6,13 +6,13 @@ module Rubydeps
6
6
  class Runner < Thor
7
7
  desc "testunit", "Create the dependency graph after runnning the testunit tests"
8
8
  method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
9
+ method_option :class_name_filter, :type => :string, :default => '', :required => true
9
10
  def testunit
10
11
  require 'test/unit'
11
12
 
12
13
  (Dir["./test/**/*_test.rb"] + Dir["./test/**/test_*.rb"]).each { |f| load f unless f =~ /^-/ }
13
14
 
14
- ARGV.clear
15
- Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
15
+ create_dependencies_dot_for(options) do
16
16
  ::Test::Unit::AutoRunner.run
17
17
  end
18
18
  end
@@ -20,6 +20,7 @@ module Rubydeps
20
20
  desc "rspec", "Create the dependency graph after runnning the rspec tests"
21
21
  #TODO: this breaks when using underscores, investigate
22
22
  method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
23
+ method_option :class_name_filter, :type => :string, :default => '', :required => true
23
24
  def rspec
24
25
  require 'spec'
25
26
 
@@ -27,25 +28,33 @@ module Rubydeps
27
28
  p.parse(Dir["./spec/**/*_spec.rb"])
28
29
  op = p.options
29
30
 
30
- ARGV.clear
31
- Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
31
+ create_dependencies_dot_for(options) do
32
32
  ::Spec::Runner::CommandLine.run(op)
33
33
  end
34
34
  end
35
35
 
36
36
  desc "rspec2", "Create the dependency graph after runnning the rspec 2 tests"
37
37
  method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
38
+ method_option :class_name_filter, :type => :string, :default => '', :required => true
38
39
  def rspec2
39
40
  require 'rspec'
40
41
 
41
42
  op = ::RSpec::Core::ConfigurationOptions.new(Dir["./spec/**/*_spec.rb"])
42
43
  op.parse_options
43
44
 
44
- ARGV.clear
45
- Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
45
+ create_dependencies_dot_for(options) do
46
46
  ::RSpec::Core::CommandLine.new(op).run($stderr, $stdout)
47
47
  end
48
48
  end
49
+
50
+ private
51
+
52
+ def create_dependencies_dot_for(options)
53
+ ARGV.clear
54
+ Rubydeps.dot_for(:path_filter => Regexp.new(options[:path_filter]), :class_name_filter => Regexp.new(options[:class_name_filter])) do
55
+ yield
56
+ end
57
+ end
49
58
  end
50
59
  end
51
60
 
data/lib/rubydeps.rb CHANGED
@@ -4,8 +4,8 @@ require 'rcovrt'
4
4
  require 'rcov'
5
5
 
6
6
  module Rubydeps
7
- def self.dot_for(path_filter = /.*/, &block_to_analyze)
8
- dependencies_hash = hash_for(path_filter, &block_to_analyze)
7
+ def self.dot_for(options = {}, &block_to_analyze)
8
+ dependencies_hash = hash_for(options, &block_to_analyze)
9
9
 
10
10
  if dependencies_hash
11
11
  g = GraphViz::new( "G", :use => 'dot', :mode => 'major', :rankdir => 'LR', :concentrate => 'true', :fontname => 'Arial')
@@ -27,14 +27,17 @@ module Rubydeps
27
27
  end
28
28
  end
29
29
 
30
- def self.hash_for(path_filter = /.*/, &block_to_analyze)
30
+ def self.hash_for(options = {}, &block_to_analyze)
31
31
  analyzer = Rcov::CallSiteAnalyzer.new
32
32
  analyzer.run_hooked do
33
33
  block_to_analyze.call
34
34
  end
35
35
 
36
+ path_filter = options.fetch(:path_filter, /.*/)
37
+ class_name_filter = options.fetch(:class_name_filter, /.*/)
38
+
36
39
  dependency_hash = create_dependency_hash(analyzer, path_filter)
37
- clean_hash(dependency_hash)
40
+ clean_hash(dependency_hash, class_name_filter)
38
41
  end
39
42
 
40
43
  private
@@ -43,6 +46,7 @@ private
43
46
  end
44
47
 
45
48
  #we build a hash structured in this way: {"called_class_name1" => ["calling_class_name1", "calling_class_name2"], "called_class_name2" => ...}
49
+ #TODO: want moar love here
46
50
  def self.create_dependency_hash(analyzer, path_filter)
47
51
  dependency_hash = {}
48
52
  analyzer.analyzed_classes.each do |c|
@@ -51,7 +55,7 @@ private
51
55
  called_class_method = "#{c}##{m}"
52
56
  def_site = analyzer.defsite(called_class_method)
53
57
  if path_filtered_site?(def_site, path_filter)
54
- calling_class_names = []
58
+ calling_class_names = Set.new
55
59
  analyzer.callsites(called_class_method).each do |call_site, _|
56
60
  if path_filtered_site?(call_site, path_filter)
57
61
  calling_class = call_site.calling_class
@@ -59,7 +63,7 @@ private
59
63
  calling_class_names << calling_class_name
60
64
  end
61
65
  end
62
- dependency_hash[called_class_name] ||= []
66
+ dependency_hash[called_class_name] ||= Set.new
63
67
  dependency_hash[called_class_name] += calling_class_names
64
68
  end
65
69
  end
@@ -68,11 +72,15 @@ private
68
72
  dependency_hash
69
73
  end
70
74
 
71
- def self.clean_hash(dependency_hash)
75
+ def self.clean_hash(dependency_hash, class_name_filter)
72
76
  cleaned_hash = {}
73
77
  dependency_hash.each do |called_class_name, calling_class_names|
74
- if interesting_class_name(called_class_name) && !dependency_hash[called_class_name].empty?
75
- cleaned_hash[called_class_name] = calling_class_names.compact.uniq.select{|c| interesting_class_name(c) && c != called_class_name }
78
+ if interesting_class_name(called_class_name) && !dependency_hash[called_class_name].empty? && called_class_name =~ class_name_filter
79
+ cleaned_hash[called_class_name] = (calling_class_names - [nil]).select do |c|
80
+ interesting_class_name(c) &&
81
+ c != called_class_name &&
82
+ c =~ class_name_filter
83
+ end
76
84
  cleaned_hash.delete(called_class_name) if cleaned_hash[called_class_name].empty?
77
85
  end
78
86
  end
@@ -1,4 +1,3 @@
1
- #require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
1
  require 'file_test_helper'
3
2
 
4
3
  class Grandparent
@@ -86,4 +85,53 @@ describe "Rubydeps" do
86
85
  dependencies["Parent"].should == ["Son"]
87
86
  dependencies["Grandparent"].should =~ ["Son", "Parent"]
88
87
  end
88
+
89
+ sample_dir_structure = {'path1/class_a.rb' => <<-CLASSA,
90
+ require 'path1/class_b'
91
+ require 'path2/class_c'
92
+ class A
93
+ def depend_on_b_and_c
94
+ B.new.b
95
+ C.new.c
96
+ end
97
+ end
98
+ CLASSA
99
+ 'path1/class_b.rb' => 'class B; def b; end end',
100
+ 'path2/class_c.rb' => 'class C; def c; end end'}
101
+
102
+ it "should not filter classes when no filter is specified" do
103
+ with_files(sample_dir_structure) do
104
+ load 'path1/class_a.rb'
105
+
106
+ dependencies = Rubydeps.hash_for do
107
+ A.new.depend_on_b_and_c
108
+ end
109
+
110
+ dependencies.should == {"B"=>["A"], "C"=>["A"]}
111
+ end
112
+ end
113
+
114
+ it "should filter classes when a path filter is specified" do
115
+ with_files(sample_dir_structure) do
116
+ load 'path1/class_a.rb'
117
+
118
+ dependencies = Rubydeps.hash_for(:path_filter => /path1/) do
119
+ A.new.depend_on_b_and_c
120
+ end
121
+
122
+ dependencies.should == {"B"=>["A"]}
123
+ end
124
+ end
125
+
126
+ it "should filter classes when a class name filter is specified" do
127
+ with_files(sample_dir_structure) do
128
+ load 'path1/class_a.rb'
129
+
130
+ dependencies = Rubydeps.hash_for(:class_name_filter => /C|A/) do
131
+ A.new.depend_on_b_and_c
132
+ end
133
+
134
+ dependencies.should == {"C"=>["A"]}
135
+ end
136
+ end
89
137
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubydeps
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Daniel Cadenas