rubydeps 0.1.0 → 0.2.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
@@ -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