rubydeps 0.1.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Daniel Cadenas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ rubydeps
2
+ ========
3
+
4
+ A tool to create class dependency graphs from test suites
5
+
6
+ Command line usage
7
+ ------------------
8
+
9
+
10
+ Rubydeps will run your test suite to record the call graph of your project and use it to create a dot graph.
11
+
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
+ 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
+
15
+ rubydeps testunit --path-filter='activemodel/lib' # to run Test::Unit tests
16
+ or
17
+ rubydeps rspec --path-filter='activemodel/lib' # to run RSpec tests
18
+ or
19
+ rubydeps rspec2 --path-filter='activemodel/lib' # to run RSpec 2 tests
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.:
23
+
24
+ dot -Tsvg rubydeps.dot > rubydeps.svg
25
+
26
+
27
+ Library usage
28
+ -------------
29
+
30
+ Just require rubydeps and pass a block to analyze to the dot_for method.
31
+
32
+ Rubydeps.dot_for(path_filter_regexp) do
33
+ //your code goes here
34
+ end
35
+
36
+ Sample output
37
+ -------------
38
+
39
+ This is the result of running rubydeps on the [Mechanize](http://github.com/tenderlove/mechanize) tests:
40
+
41
+ ![Mechanize dependencies](https://github.com/dcadenas/rubydeps/raw/master/mechanize-deps.png)
42
+
43
+ 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
+
45
+ Dependencies
46
+ ------------
47
+
48
+ * rcov
49
+ * graphviz
50
+ * ruby-graphviz
51
+
52
+ Note on Patches/Pull Requests
53
+ -----------------------------
54
+
55
+ * Fork the project.
56
+ * Make your feature addition or bug fix.
57
+ * Add tests for it. This is important so I don't break it in a
58
+ future version unintentionally.
59
+ * Commit, do not mess with rakefile, version, or history.
60
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
61
+ * Send me a pull request. Bonus points for topic branches.
62
+
63
+ Copyright
64
+ ---------
65
+
66
+ Copyright (c) 2010 Daniel Cadenas. See LICENSE for details.
67
+
68
+ Development sponsored by [Cubox](http://www.cuboxsa.com)
data/bin/rubydeps ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubydeps'
3
+ require 'thor'
4
+
5
+ module Rubydeps
6
+ class Runner < Thor
7
+ desc "testunit", "Create the dependency graph after runnning the testunit tests"
8
+ method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
9
+ def testunit
10
+ require 'test/unit'
11
+
12
+ (Dir["./test/**/*_test.rb"] + Dir["./test/**/test_*.rb"]).each { |f| load f unless f =~ /^-/ }
13
+
14
+ ARGV.clear
15
+ Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
16
+ ::Test::Unit::AutoRunner.run
17
+ end
18
+ end
19
+
20
+ desc "rspec", "Create the dependency graph after runnning the rspec tests"
21
+ #TODO: this breaks when using underscores, investigate
22
+ method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
23
+ def rspec
24
+ require 'spec'
25
+
26
+ p = ::Spec::Runner::OptionParser.new($stderr, $stdout)
27
+ p.parse(Dir["./spec/**/*_spec.rb"])
28
+ op = p.options
29
+
30
+ ARGV.clear
31
+ Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
32
+ ::Spec::Runner::CommandLine.run(op)
33
+ end
34
+ end
35
+
36
+ desc "rspec2", "Create the dependency graph after runnning the rspec 2 tests"
37
+ method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
38
+ def rspec2
39
+ require 'rspec'
40
+
41
+ op = ::RSpec::Core::ConfigurationOptions.new(Dir["./spec/**/*_spec.rb"])
42
+ op.parse_options
43
+
44
+ ARGV.clear
45
+ Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
46
+ ::RSpec::Core::CommandLine.new(op).run($stderr, $stdout)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ Rubydeps::Runner.start(ARGV)
53
+
data/lib/rubydeps.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'graphviz'
2
+ require 'set'
3
+ require 'rcovrt'
4
+ require 'rcov'
5
+
6
+ module Rubydeps
7
+ def self.dot_for(path_filter = /.*/, &block_to_analyze)
8
+ dependencies_hash = hash_for(path_filter, &block_to_analyze)
9
+
10
+ if dependencies_hash
11
+ g = GraphViz::new( "G", :use => 'dot', :mode => 'major', :rankdir => 'LR', :concentrate => 'true', :fontname => 'Arial')
12
+ dependencies_hash.each do |k,vs|
13
+ if !k.empty? && !vs.empty?
14
+ n1 = g.add_node(k.to_s)
15
+ if vs.respond_to?(:each)
16
+ vs.each do |v|
17
+ unless v.empty?
18
+ n2 = g.add_node(v.to_s)
19
+ g.add_edge(n2, n1)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ g.output( :dot => "rubydeps.dot" )
27
+ end
28
+ end
29
+
30
+ def self.hash_for(path_filter = /.*/, &block_to_analyze)
31
+ analyzer = Rcov::CallSiteAnalyzer.new
32
+ analyzer.run_hooked do
33
+ block_to_analyze.call
34
+ end
35
+
36
+ dependency_hash = create_dependency_hash(analyzer, path_filter)
37
+ clean_hash(dependency_hash)
38
+ end
39
+
40
+ private
41
+ def self.path_filtered_site?(code_site, path_filter)
42
+ code_site && path_filter =~ File.expand_path(code_site.file)
43
+ end
44
+
45
+ #we build a hash structured in this way: {"called_class_name1" => ["calling_class_name1", "calling_class_name2"], "called_class_name2" => ...}
46
+ def self.create_dependency_hash(analyzer, path_filter)
47
+ dependency_hash = {}
48
+ analyzer.analyzed_classes.each do |c|
49
+ called_class_name = normalize_class_name(c)
50
+ analyzer.methods_for_class(c).each do |m|
51
+ called_class_method = "#{c}##{m}"
52
+ def_site = analyzer.defsite(called_class_method)
53
+ if path_filtered_site?(def_site, path_filter)
54
+ calling_class_names = []
55
+ analyzer.callsites(called_class_method).each do |call_site, _|
56
+ if path_filtered_site?(call_site, path_filter)
57
+ calling_class = call_site.calling_class
58
+ calling_class_name = normalize_class_name(calling_class.to_s)
59
+ calling_class_names << calling_class_name
60
+ end
61
+ end
62
+ dependency_hash[called_class_name] ||= []
63
+ dependency_hash[called_class_name] += calling_class_names
64
+ end
65
+ end
66
+ end
67
+
68
+ dependency_hash
69
+ end
70
+
71
+ def self.clean_hash(dependency_hash)
72
+ cleaned_hash = {}
73
+ 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 }
76
+ cleaned_hash.delete(called_class_name) if cleaned_hash[called_class_name].empty?
77
+ end
78
+ end
79
+
80
+ cleaned_hash
81
+ end
82
+
83
+ def self.interesting_class_name(class_name)
84
+ !class_name.empty? && class_name != "Rcov::CallSiteAnalyzer" && class_name != "Rcov::DifferentialAnalyzer"
85
+ end
86
+
87
+ def self.normalize_class_name(klass)
88
+ good_class_name = klass.gsub(/#<Class:(.+)>/, '\1')
89
+ good_class_name.gsub!(/\([^\)]*\)/, "")
90
+ good_class_name.gsub(/0x[\da-fA-F]+/, '(hex number)')
91
+ end
92
+ end
@@ -0,0 +1,89 @@
1
+ #require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'file_test_helper'
3
+
4
+ class Grandparent
5
+ def self.class_method
6
+ end
7
+
8
+ def instance_method
9
+ end
10
+ end
11
+
12
+ class Parent
13
+ def self.class_method
14
+ Grandparent.class_method
15
+ end
16
+
17
+ def instance_method
18
+ end
19
+ end
20
+
21
+ class Son
22
+ def self.class_method
23
+ parent = Parent.new
24
+ parent.instance_method
25
+ parent.instance_method
26
+ class_method2
27
+ class_method2
28
+ end
29
+
30
+ def self.class_method2
31
+ end
32
+
33
+ def instance_method
34
+ Parent.class_method
35
+ Grandparent.class_method
36
+ end
37
+ end
38
+
39
+ describe "Rubydeps" do
40
+ include FileTestHelper
41
+ it "should show the class level dependencies" do
42
+ dependencies = Rubydeps.hash_for do
43
+ class IHaveAClassLevelDependency
44
+ Son.class_method
45
+ end
46
+ end
47
+
48
+ dependencies.should == {"Parent"=>["Son"]}
49
+ end
50
+
51
+ it "should create a dot file" do
52
+ with_files do
53
+ dependencies = Rubydeps.dot_for do
54
+ class IHaveAClassLevelDependency
55
+ Son.class_method
56
+ end
57
+ end
58
+
59
+ File.read("rubydeps.dot").should match("digraph G")
60
+ end
61
+ end
62
+
63
+ it "should be idempotent" do
64
+ Rubydeps.hash_for do
65
+ class IHaveAClassLevelDependency
66
+ Son.class_method
67
+ end
68
+ end
69
+
70
+ dependencies = Rubydeps.hash_for do
71
+ class IHaveAClassLevelDependency
72
+ Son.class_method
73
+ end
74
+ end
75
+
76
+ dependencies.should == {"Parent"=>["Son"]}
77
+ end
78
+
79
+ it "should show the dependencies between the classes inside the block" do
80
+ dependencies = Rubydeps.hash_for do
81
+ Son.class_method
82
+ Son.new.instance_method
83
+ end
84
+
85
+ dependencies.keys.should =~ ["Parent", "Grandparent"]
86
+ dependencies["Parent"].should == ["Son"]
87
+ dependencies["Grandparent"].should =~ ["Son", "Parent"]
88
+ end
89
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubydeps'
2
+ require 'rspec'
3
+ require 'rspec/autorun'
4
+
5
+ Rspec.configure do |config|
6
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubydeps
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Daniel Cadenas
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-28 00:00:00 -03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 25
30
+ segments:
31
+ - 2
32
+ - 5
33
+ - 1
34
+ version: 2.5.1
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: file_test_helper
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 23
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 0
50
+ version: 1.0.0
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: ruby-graphviz
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 25
62
+ segments:
63
+ - 0
64
+ - 9
65
+ - 17
66
+ version: 0.9.17
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: rcov
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 43
78
+ segments:
79
+ - 0
80
+ - 9
81
+ - 8
82
+ version: 0.9.8
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: thor
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 35
94
+ segments:
95
+ - 0
96
+ - 14
97
+ - 2
98
+ version: 0.14.2
99
+ type: :runtime
100
+ version_requirements: *id005
101
+ description: Graphs ruby dependencies
102
+ email: dcadenas@gmail.com
103
+ executables:
104
+ - rubydeps
105
+ extensions: []
106
+
107
+ extra_rdoc_files:
108
+ - LICENSE
109
+ files:
110
+ - .document
111
+ - .gitignore
112
+ - LICENSE
113
+ - README.md
114
+ - bin/rubydeps
115
+ - lib/rubydeps.rb
116
+ - spec/rubydeps_spec.rb
117
+ - spec/spec_helper.rb
118
+ has_rdoc: true
119
+ homepage: http://github.com/dcadenas/rubydeps
120
+ licenses: []
121
+
122
+ post_install_message:
123
+ rdoc_options:
124
+ - --charset=UTF-8
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ requirements: []
146
+
147
+ rubyforge_project:
148
+ rubygems_version: 1.6.2
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: Graphs ruby depencencies
152
+ test_files:
153
+ - spec/rubydeps_spec.rb
154
+ - spec/spec_helper.rb