indirect-metric_fu 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/HISTORY +19 -0
  2. data/MIT-LICENSE +1 -1
  3. data/Manifest.txt +25 -0
  4. data/README +74 -21
  5. data/Rakefile +4 -10
  6. data/lib/metric_fu/base.rb +116 -9
  7. data/lib/metric_fu/churn.rb +8 -7
  8. data/lib/metric_fu/flay.rb +17 -0
  9. data/lib/metric_fu/flog.rb +129 -0
  10. data/lib/metric_fu/reek.rb +17 -0
  11. data/lib/metric_fu/roodi.rb +17 -0
  12. data/lib/metric_fu.rb +17 -3
  13. data/lib/tasks/churn.rake +1 -3
  14. data/lib/tasks/coverage.rake +19 -10
  15. data/lib/tasks/flay.rake +1 -4
  16. data/lib/tasks/flog.rake +10 -10
  17. data/lib/tasks/metric_fu.rake +8 -4
  18. data/lib/tasks/metric_fu.rb +1 -1
  19. data/lib/tasks/railroad.rake +39 -0
  20. data/lib/tasks/reek.rake +6 -0
  21. data/lib/tasks/roodi.rake +7 -0
  22. data/lib/tasks/saikuro.rake +1 -1
  23. data/lib/tasks/stats.rake +2 -3
  24. data/lib/templates/churn.html.erb +7 -4
  25. data/lib/templates/default.css +45 -0
  26. data/lib/templates/flay.html.erb +17 -10
  27. data/lib/templates/flog.html.erb +27 -15
  28. data/lib/templates/flog_page.html.erb +15 -3
  29. data/lib/templates/reek.html.erb +30 -0
  30. data/lib/templates/roodi.html.erb +26 -0
  31. data/spec/base_spec.rb +31 -9
  32. data/spec/churn_spec.rb +11 -4
  33. data/spec/config_spec.rb +110 -0
  34. data/spec/{flay_reporter_spec.rb → flay_spec.rb} +10 -3
  35. data/spec/flog_spec.rb +208 -0
  36. data/spec/md5_tracker_spec.rb +1 -3
  37. data/spec/reek_spec.rb +26 -0
  38. data/spec/spec_helper.rb +7 -3
  39. metadata +42 -18
  40. data/lib/metric_fu/flay_reporter.rb +0 -17
  41. data/lib/metric_fu/flog_reporter/base.rb +0 -49
  42. data/lib/metric_fu/flog_reporter/generator.rb +0 -39
  43. data/lib/metric_fu/flog_reporter/operator.rb +0 -10
  44. data/lib/metric_fu/flog_reporter/page.rb +0 -33
  45. data/lib/metric_fu/flog_reporter/scanned_method.rb +0 -28
  46. data/lib/metric_fu/flog_reporter.rb +0 -5
  47. data/lib/templates/churn.css +0 -38
  48. data/lib/templates/flay.css +0 -38
  49. data/lib/templates/flog.css +0 -39
  50. data/spec/flog_reporter/base_spec.rb +0 -69
@@ -0,0 +1,129 @@
1
+ module MetricFu
2
+
3
+ def self.generate_flog_report
4
+ Flog::Generator.generate_report
5
+ system("open #{Flog::Generator.metric_dir}/index.html") if open_in_browser?
6
+ end
7
+
8
+ module Flog
9
+ class Generator < Base::Generator
10
+ def generate_report
11
+ @base_dir = self.class.metric_dir
12
+ pages = []
13
+ flog_results.each do |filename|
14
+ page = Base.parse(open(filename, "r") { |f| f.read })
15
+ if page
16
+ page.filename = filename
17
+ pages << page
18
+ end
19
+ end
20
+ generate_pages(pages)
21
+ end
22
+
23
+ def generate_pages(pages)
24
+ pages.each do |page|
25
+ unless MetricFu::MD5Tracker.file_already_counted?(page.filename)
26
+ generate_page(page)
27
+ end
28
+ end
29
+ save_html(ERB.new(File.read(template_file)).result(binding))
30
+ end
31
+
32
+ def generate_page(page)
33
+ save_html(page.to_html, page.path)
34
+ end
35
+
36
+ def flog_results
37
+ Dir.glob("#{@base_dir}/**/*.txt")
38
+ end
39
+
40
+ def self.template_name
41
+ "flog"
42
+ end
43
+ end
44
+
45
+ SCORE_FORMAT = "%0.2f"
46
+
47
+ class Base
48
+ METHOD_LINE_REGEX = /([A-Za-z]+#.*):\s\((\d+\.\d+)\)/
49
+ OPERATOR_LINE_REGEX = /\s+(\d+\.\d+):\s(.*)$/
50
+
51
+ class << self
52
+
53
+ def parse(text)
54
+ score = text[/\w+ = (\d+\.\d+)/, 1]
55
+ return nil unless score
56
+ page = Page.new(score)
57
+
58
+ text.each_line do |method_line|
59
+ if match = method_line.match(METHOD_LINE_REGEX)
60
+ page.scanned_methods << ScannedMethod.new(match[1], match[2])
61
+ end
62
+
63
+ if match = method_line.match(OPERATOR_LINE_REGEX)
64
+ return if page.scanned_methods.empty?
65
+ page.scanned_methods.last.operators << Operator.new(match[1], match[2])
66
+ end
67
+ end
68
+ page
69
+ end
70
+
71
+ end
72
+ end
73
+
74
+ class Page < MetricFu::Base::Generator
75
+ attr_accessor :filename, :score, :scanned_methods
76
+
77
+ def initialize(score, scanned_methods = [])
78
+ @score = score.to_f
79
+ @scanned_methods = scanned_methods
80
+ end
81
+
82
+ def path
83
+ @path ||= File.basename(filename, ".txt") + '.html'
84
+ end
85
+
86
+ def to_html
87
+ ERB.new(File.read(template_file)).result(binding)
88
+ end
89
+
90
+ def average_score
91
+ return 0 if scanned_methods.length == 0
92
+ sum = 0
93
+ scanned_methods.each do |m|
94
+ sum += m.score
95
+ end
96
+ sum / scanned_methods.length
97
+ end
98
+
99
+ def highest_score
100
+ scanned_methods.inject(0) do |highest, m|
101
+ m.score > highest ? m.score : highest
102
+ end
103
+ end
104
+
105
+ def template_name
106
+ 'flog_page'
107
+ end
108
+ end
109
+
110
+ class Operator
111
+ attr_accessor :score, :operator
112
+
113
+ def initialize(score, operator)
114
+ @score = score.to_f
115
+ @operator = operator
116
+ end
117
+ end
118
+
119
+ class ScannedMethod
120
+ attr_accessor :name, :score, :operators
121
+
122
+ def initialize(name, score, operators = [])
123
+ @name = name
124
+ @score = score.to_f
125
+ @operators = operators
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,17 @@
1
+ module MetricFu
2
+
3
+ def self.generate_reek_report
4
+ Reek.generate_report
5
+ system("open #{Reek.metric_dir}/index.html") if open_in_browser?
6
+ end
7
+
8
+ class Reek < Base::Generator
9
+
10
+ def analyze
11
+ files_to_reek = MetricFu.reek[:dirs_to_reek].map{|dir| Dir[File.join(dir, "**/*.rb")] }
12
+ output = `reek #{files_to_reek.join(" ")}`
13
+ @matches = output.chomp.split("\n\n").map{|m| m.split("\n") }
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module MetricFu
2
+
3
+ def self.generate_roodi_report
4
+ MetricFu::Roodi.generate_report
5
+ system("open #{Roodi.metric_dir}/index.html") if open_in_browser?
6
+ end
7
+
8
+ class Roodi < Base::Generator
9
+
10
+ def analyze
11
+ files_to_analyze = MetricFu.roodi[:dirs_to_roodi].map{|dir| Dir[File.join(dir, "**/*.rb")] }
12
+ output = `roodi #{files_to_analyze.join(" ")}`
13
+ @matches = output.chomp.split("\n").map{|m| m.split(" - ") }
14
+ end
15
+
16
+ end
17
+ end
data/lib/metric_fu.rb CHANGED
@@ -1,3 +1,17 @@
1
- require File.join(File.dirname(__FILE__), 'metric_fu', 'base') #require first because of dependecies
2
- require File.join(File.dirname(__FILE__), 'tasks', 'metric_fu')
3
- Dir[File.join(File.dirname(__FILE__), 'metric_fu/*.rb')].each{|l| require l }
1
+ module MetricFu
2
+ TEMPLATE_DIR = File.join(File.dirname(__FILE__), '..', 'templates')
3
+ BASE_DIRECTORY = ENV['CC_BUILD_ARTIFACTS'] || 'tmp/metric_fu'
4
+ RAILS = File.exist?("config/environment.rb")
5
+
6
+ if RAILS
7
+ CODE_DIRS = ['app', 'lib']
8
+ else
9
+ CODE_DIRS = ['lib']
10
+ end
11
+ end
12
+
13
+ # Load metric_fu base class
14
+ require File.join(File.dirname(__FILE__), 'metric_fu', 'base')
15
+ # Load metrics tasks
16
+ require "rake"
17
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].each { |ext| load ext }
data/lib/tasks/churn.rake CHANGED
@@ -4,8 +4,6 @@ namespace :metrics do
4
4
 
5
5
  desc "Which files change the most"
6
6
  task :churn do
7
- churn_dir = File.join(MetricFu::BASE_DIRECTORY, 'churn')
8
- MetricFu::Churn.generate_report(churn_dir, defined?(MetricFu::CHURN_OPTIONS) ? MetricFu::CHURN_OPTIONS : {} )
9
- system("open #{churn_dir}/index.html") if PLATFORM['darwin']
7
+ MetricFu.generate_churn_report
10
8
  end
11
9
  end
@@ -1,5 +1,4 @@
1
1
  require 'fileutils'
2
-
3
2
  begin
4
3
  require 'rcov'
5
4
  require 'rcov/rcovtask'
@@ -18,20 +17,30 @@ begin
18
17
  task(:clean) { rm_f("rcov_tmp", :verbose => false) }
19
18
 
20
19
  desc "RCov task to generate report"
21
- Spec::Rake::SpecTask.new(:do => :clean) do |t|
20
+ Rcov::RcovTask.new(:do => :clean) do |t|
22
21
  FileUtils.mkdir_p(MetricFu::BASE_DIRECTORY) unless File.directory?(MetricFu::BASE_DIRECTORY)
23
- t.ruby_opts = ['-rtest/unit']
24
- t.spec_files = FileList['test/**/*_test.rb', 'spec/**/*spec.rb']
25
- t.spec_opts = ["--format", "html:#{SPEC_HTML_FILE}", "--diff"]
26
- t.rcov = true
27
- t.rcov_opts = ["--sort coverage", "--html", "--rails", "--exclude /gems/,/Library/"]
28
- t.rcov_dir = COVERAGE_DIR
22
+ t.test_files = FileList[*MetricFu.coverage[:test_files]]
23
+ t.rcov_opts = MetricFu.coverage[:rcov_opts]
24
+ t.output_dir = COVERAGE_DIR
25
+ # this line is a fix for Rails 2.1 relative loading issues
26
+ t.libs << 'test'
29
27
  end
28
+ # TODO not sure what this improves but it requires the diff-lcs gem
29
+ # http://github.com/indirect/metric_fu/commit/b9c1cf75f09d5b531b388cd01661eb16b5126968#diff-1
30
+ # Spec::Rake::SpecTask.new(:do => :clean) do |t|
31
+ # FileUtils.mkdir_p(MetricFu::BASE_DIRECTORY) unless File.directory?(MetricFu::BASE_DIRECTORY)
32
+ # t.ruby_opts = ['-rtest/unit']
33
+ # t.spec_files = FileList['test/**/*_test.rb', 'spec/**/*spec.rb']
34
+ # t.spec_opts = ["--format", "html:#{SPEC_HTML_FILE}", "--diff"]
35
+ # t.rcov = true
36
+ # t.rcov_opts = ["--sort coverage", "--html", "--rails", "--exclude /gems/,/Library/"]
37
+ # t.rcov_dir = COVERAGE_DIR
38
+ # end
30
39
  end
31
40
 
32
41
  desc "Generate and open coverage report"
33
42
  task :coverage => ['coverage:do'] do
34
- system("open #{COVERAGE_DIR}/index.html") if PLATFORM['darwin']
43
+ system("open #{COVERAGE_DIR}/index.html") if MetricFu.open_in_browser?
35
44
  end
36
45
  end
37
46
  rescue LoadError
@@ -41,4 +50,4 @@ rescue LoadError
41
50
  puts 'sudo gem install rcov # if you want the rcov tasks'
42
51
 
43
52
  end
44
- end
53
+ end
data/lib/tasks/flay.rake CHANGED
@@ -1,9 +1,6 @@
1
- FLAY_DIR = File.join(MetricFu::BASE_DIRECTORY, 'flay')
2
-
3
1
  namespace :metrics do
4
2
  desc "Generate code duplication report with flay"
5
3
  task :flay do
6
- MetricFu::FlayReporter.generate_report(FLAY_DIR)
7
- system("open #{FLAY_DIR}/index.html") if PLATFORM['darwin']
4
+ MetricFu.generate_flay_report
8
5
  end
9
6
  end
data/lib/tasks/flog.rake CHANGED
@@ -1,11 +1,13 @@
1
1
  begin
2
- FLOG_DIR = File.join(MetricFu::BASE_DIRECTORY, 'flog')
3
2
 
4
3
  def flog(output, directory)
4
+ metric_dir = MetricFu::Flog::Generator.metric_dir
5
5
  Dir.glob("#{directory}/**/*.rb").each do |filename|
6
- output_dir = "#{FLOG_DIR}/#{filename.split("/")[0..-2].join("/")}"
6
+ output_dir = "#{metric_dir}/#{filename.split("/")[0..-2].join("/")}"
7
7
  mkdir_p(output_dir, :verbose => false) unless File.directory?(output_dir)
8
- `flog #{filename} > #{FLOG_DIR}/#{filename.split('.')[0]}.txt` if MetricFu::MD5Tracker.file_changed?(filename, FLOG_DIR)
8
+ if MetricFu::MD5Tracker.file_changed?(filename, metric_dir)
9
+ `flog #{filename} > #{metric_dir}/#{filename.split('.')[0]}.txt`
10
+ end
9
11
  end
10
12
  end
11
13
 
@@ -16,7 +18,7 @@ begin
16
18
 
17
19
  namespace :flog do
18
20
  desc "Delete aggregate flog data."
19
- task(:clean) { rm_rf(FLOG_DIR, :verbose => false) }
21
+ task(:clean) { rm_rf(MetricFu::Flog.metric_dir, :verbose => false) }
20
22
 
21
23
  desc "Flog code in app/models"
22
24
  task :models do
@@ -40,20 +42,18 @@ begin
40
42
 
41
43
  desc "Generate a flog report from specified directories"
42
44
  task :custom do
43
- MetricFu::CODE_DIRS.each { |directory| flog(directory, directory) }
44
- MetricFu::FlogReporter::Generator.generate_report(FLOG_DIR)
45
+ MetricFu::flog[:dirs_to_flog].each { |directory| flog(directory, directory) }
46
+ MetricFu.generate_flog_report
45
47
  end
46
48
 
47
49
  desc "Generate and open flog report"
48
50
  if MetricFu::RAILS
49
51
  task :all => [:models, :controllers, :helpers, :lib] do
50
- MetricFu::FlogReporter::Generator.generate_report(FLOG_DIR)
51
- system("open #{FLOG_DIR}/index.html") if PLATFORM['darwin']
52
+ MetricFu.generate_flog_report
52
53
  end
53
54
  else
54
55
  task :all => [:custom] do
55
- MetricFu::FlogReporter::Generator.generate_report(FLOG_DIR)
56
- system("open #{FLOG_DIR}/index.html") if PLATFORM['darwin']
56
+ MetricFu.generate_flog_report
57
57
  end
58
58
  end
59
59
 
@@ -1,8 +1,11 @@
1
+ # only load configured metrics
2
+ MetricFu.metrics.each { |task| import "#{File.dirname(__FILE__)}/#{task}.rake" }
3
+
1
4
  namespace :metrics do
2
5
  if MetricFu::RAILS
3
6
 
4
- desc "Generate coverage, cyclomatic complexity, flog, stats, duplication and churn reports"
5
- task :all => [:coverage, :stats, :saikuro, :churn, :flog, :flay]
7
+ desc "Generate coverage, cyclomatic complexity, flog, flay, railroad, reek, roodi, stats and churn reports"
8
+ task :all => MetricFu.metrics
6
9
 
7
10
  task :set_testing_env do
8
11
  RAILS_ENV = 'test'
@@ -13,8 +16,9 @@ namespace :metrics do
13
16
 
14
17
  else
15
18
 
16
- desc "Generate coverage, cyclomatic complexity, flog, duplication and churn reports"
17
- task :all => [:coverage, :saikuro, :churn, :flog, :flay]
19
+ desc "Generate coverage, cyclomatic complexity, flog, flay, railroad and churn reports"
20
+ task :all => MetricFu.metrics
18
21
 
19
22
  end
23
+
20
24
  end
@@ -3,4 +3,4 @@ require 'rubygems'
3
3
  require 'rake'
4
4
 
5
5
  # Load rake files
6
- Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext }
6
+ import "#{File.dirname(__FILE__)}/metric_fu.rake"
@@ -0,0 +1,39 @@
1
+ namespace :metrics do
2
+
3
+ RAILROAD_DIR = File.join(MetricFu::BASE_DIRECTORY, 'railroad')
4
+ RAILROAD_FILE = File.join(RAILROAD_DIR, 'index.html')
5
+
6
+ task :railroad => ['railroad:all'] do
7
+ end
8
+
9
+ namespace :railroad do
10
+
11
+ desc "Create all railroad reports"
12
+ task :all => [:models, :controllers, :aasm] do
13
+ #system("open #{RAILROAD_INDEX}") if PLATFORM['darwin']
14
+ end
15
+
16
+ desc "Create a railroad models report"
17
+ task :models do
18
+ #mkdir_p(RAILROAD_DIR) unless File.directory?(RAILROAD_DIR)
19
+ `railroad -M -a -m -l -v | neato -Tpng > #{File.join(MetricFu::BASE_DIRECTORY,'model-diagram.png')}`
20
+ #`echo "<a href=\"railroad/models.png\">Model diagram</a><br />" >> #{RAILROAD_FILE}`
21
+ end
22
+
23
+ desc "Create a railroad controllers report"
24
+ task :controllers do
25
+ #mkdir_p(RAILROAD_DIR) unless File.directory?(RAILROAD_DIR)
26
+ `railroad -C -l -v | neato -Tpng > #{File.join(MetricFu::BASE_DIRECTORY,'controller-diagram.png')}`
27
+ #`echo "<a href=\"railroad/controllers.png\">Controller diagram</a><br />" >> #{RAILROAD_FILE}`
28
+ end
29
+
30
+ desc "Create a railroad acts_as_state_machine report"
31
+ task :aasm do
32
+ #mkdir_p(RAILROAD_DIR) unless File.directory?(RAILROAD_DIR)
33
+ `railroad -A -l -v | neato -Tpng > #{File.join(MetricFu::BASE_DIRECTORY,'aasm-diagram.png')}`
34
+ #`echo "<a href=\"railroad/aasm.png\">State machine diagram</a><br />" >> #{RAILROAD_FILE}`
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,6 @@
1
+ namespace :metrics do
2
+ desc "A code smell report using Reek"
3
+ task :reek do
4
+ MetricFu.generate_reek_report
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ namespace :metrics do
2
+
3
+ desc "A Ruby coding standards report using Roodi"
4
+ task :roodi do
5
+ MetricFu.generate_roodi_report
6
+ end
7
+ end
@@ -30,6 +30,6 @@ namespace :metrics do
30
30
  "#{SAIKURO_DIR}/index.html"
31
31
  end
32
32
 
33
- system("open #{SAIKURO_DIR}/index.html") if PLATFORM['darwin']
33
+ system("open #{SAIKURO_DIR}/index.html") if MetricFu.open_in_browser?
34
34
  end
35
35
  end
data/lib/tasks/stats.rake CHANGED
@@ -1,5 +1,4 @@
1
1
  namespace :metrics do
2
-
3
2
  STATS_DIR = File.join(MetricFu::BASE_DIRECTORY, 'stats')
4
3
  STATS_FILE = File.join(STATS_DIR, 'index.html')
5
4
 
@@ -9,6 +8,6 @@ namespace :metrics do
9
8
  `echo '<pre>' > #{STATS_FILE}`
10
9
  `rake stats >> #{STATS_FILE}`
11
10
  `echo '</pre>' >> #{STATS_FILE}`
12
- system("open #{STATS_FILE}") if PLATFORM['darwin']
11
+ system("open #{STATS_FILE}") if MetricFu.open_in_browser?
13
12
  end
14
- end
13
+ end if MetricFu::RAILS
@@ -2,18 +2,21 @@
2
2
  <head>
3
3
  <title>Source Control Churn Results</title>
4
4
  <style>
5
- <%= open(File.join(MetricFu::TEMPLATE_DIR, "#{template_name}.css")) { |f| f.read } %>
5
+ <%= inline_css("default.css") %>
6
6
  </style>
7
7
  </head>
8
8
 
9
9
  <body>
10
10
  <h1>Source Control Churn Results</h1>
11
- <table width="100%" border="1">
11
+ <p>Files that change a lot in your project may be bad a sign.
12
+ This task uses your source control log to identify those files.
13
+ </p>
14
+ <table>
12
15
  <tr><th>File Path</th><th>Times Changed</th></tr>
13
16
  <% @changes.to_a.sort {|x,y| y[1] <=> x[1]}.each do |change| %>
14
17
  <tr><td><%= change[0] %></td><td class='warning'><%= change[1] %></td></tr>
15
18
  <% end %>
16
-
17
19
  </table>
20
+ <p>Generated on <%= Time.now.localtime %></p>
18
21
  </body>
19
- </html>
22
+ </html>