indirect-metric_fu 0.8.2 → 0.9.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.
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>