edouard-metric_fu 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/HISTORY +41 -0
  2. data/TODO +0 -4
  3. data/lib/base/base_template.rb +1 -2
  4. data/lib/base/configuration.rb +5 -33
  5. data/lib/base/generator.rb +7 -0
  6. data/lib/base/graph.rb +5 -3
  7. data/lib/base/report.rb +3 -3
  8. data/lib/generators/churn.rb +0 -1
  9. data/lib/generators/flay.rb +2 -2
  10. data/lib/generators/flog.rb +34 -7
  11. data/lib/generators/rcov.rb +2 -2
  12. data/lib/generators/reek.rb +28 -1
  13. data/lib/generators/roodi.rb +2 -1
  14. data/lib/generators/saikuro.rb +3 -7
  15. data/lib/graphs/engines/bluff.rb +86 -0
  16. data/lib/graphs/engines/gchart.rb +119 -0
  17. data/lib/graphs/flay_grapher.rb +6 -20
  18. data/lib/graphs/flog_grapher.rb +25 -22
  19. data/lib/graphs/grapher.rb +11 -0
  20. data/lib/graphs/rcov_grapher.rb +2 -16
  21. data/lib/graphs/reek_grapher.rb +12 -24
  22. data/lib/graphs/roodi_grapher.rb +6 -20
  23. data/lib/metric_fu.rb +13 -6
  24. data/lib/templates/awesome/awesome_template.rb +7 -0
  25. data/lib/templates/awesome/css/buttons.css +82 -0
  26. data/lib/templates/awesome/{default.css → css/default.css} +16 -0
  27. data/lib/templates/awesome/css/integrity.css +335 -0
  28. data/lib/templates/awesome/css/reset.css +7 -0
  29. data/lib/templates/awesome/flay.html.erb +7 -1
  30. data/lib/templates/awesome/flog.html.erb +8 -1
  31. data/lib/templates/awesome/layout.html.erb +7 -4
  32. data/lib/templates/awesome/rcov.html.erb +7 -1
  33. data/lib/templates/awesome/reek.html.erb +7 -1
  34. data/lib/templates/awesome/roodi.html.erb +7 -1
  35. data/lib/templates/javascripts/bluff-min.js +1 -0
  36. data/lib/templates/javascripts/excanvas.js +35 -0
  37. data/lib/templates/javascripts/js-class.js +1 -0
  38. data/spec/base/configuration_spec.rb +18 -52
  39. data/spec/base/generator_spec.rb +61 -1
  40. data/spec/base/graph_spec.rb +24 -0
  41. data/spec/generators/flog_spec.rb +19 -0
  42. data/spec/generators/reek_spec.rb +66 -0
  43. data/spec/graphs/engines/bluff_spec.rb +15 -0
  44. data/spec/graphs/engines/gchart_spec.rb +14 -0
  45. data/spec/graphs/flay_grapher_spec.rb +37 -0
  46. data/spec/graphs/flog_grapher_spec.rb +40 -10
  47. data/spec/graphs/rcov_grapher_spec.rb +37 -0
  48. data/spec/graphs/reek_grapher_spec.rb +46 -0
  49. data/spec/graphs/roodi_grapher_spec.rb +37 -0
  50. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +1 -0
  51. data/spec/resources/yml/20090630.yml +7844 -0
  52. data/spec/spec_helper.rb +1 -23
  53. data/tasks/metric_fu.rake +1 -1
  54. metadata +34 -16
  55. data/vendor/_fonts/monaco.ttf +0 -0
  56. data/vendor/saikuro/saikuro.rb +0 -1214
data/HISTORY CHANGED
@@ -1,3 +1,44 @@
1
+ === Edge
2
+
3
+ * Upgrade Bluff to 0.3.6
4
+ * Add tooltips to graphs
5
+ * Fix `activesupport` deprecation
6
+ * Vendor Saikuro as a gem dependency
7
+
8
+ === MetricFu 1.2.0 / 2010-01-09
9
+
10
+ * ftools isn't supported by 1.9 so moved to fileutils.
11
+ * Overhauled the graphing to use Gruff or Google Charts so we no longer depend on ImageMagick/rmagick -- thanks to Carl Youngblood.
12
+ * Stopped relying on Github gems as they will be going away.
13
+
14
+ === MetricFu 1.1.6 / 2009-12-14
15
+
16
+ * Now compatible with Reek 1.2x thanks to Kevin Rutherford
17
+ * Fixed problem with deleted files still showing up in Flog reports thanks to Dan Mayer
18
+
19
+ === MetricFu 1.1.5 / 2009-8-13
20
+
21
+ * Previous Ruby 1.9 fix was not quite fix-y enough
22
+
23
+ === MetricFu 1.1.4 / 2009-7-13
24
+
25
+ * Fixed another Ruby 1.9x bug
26
+
27
+ === MetricFu 1.1.3 / 2009-7-10
28
+
29
+ * MetricFu is now Ruby 1.9x compatible
30
+ * Removed the check for deprecated ways of configuring metric_fu as the tests were causing Ruby 1.9x problems and it's been forever since they were supported.
31
+ * Removed total flog score from graph (which will always go up and so doesn't mean much) and replacing it with top_five_percent_average which is an average of the worst 5 percent of your methods.
32
+ * Sort Flog by highest score in the class which I feel is more important than the total flog flog score.
33
+
34
+ === MetricFu 1.1.2 / 2009-7-09
35
+
36
+ * Removed dependency on gruff and rmagick (unless the user wants graphs, of course).
37
+ * New look for styling -- Edouard Brière
38
+ * Extra param in rcov call was causing problems -- Stewart Welbourne
39
+ * Preventing rake task from being run multiple times when other rake tasks switch the environment -- Matthew Van Horn
40
+ * Typo in Rcov dependency verification and fixing parsing Saikuro nested information -- Mark Wilden
41
+
1
42
  === MetricFu 1.1.1 / 2009-6-29
2
43
 
3
44
  * Fix for empty flog files
data/TODO CHANGED
@@ -2,10 +2,6 @@
2
2
 
3
3
  * Color code flog results with scale from: http://jakescruggs.blogspot.com/2008/08/whats-good-flog-score.html
4
4
  * Integrate Flog, Saikuro, and Coverage into one report so you can see methods that have high complexity and low coverage (this is a big one)
5
- * Move HTML out of code and into templates/
6
- * Replace #generate_report with #new on each metric class
7
- * Make each class descend from MetricFu::CodeMetric
8
- * Generate metrics:* rake tasks for each of CodeMetric's descendants
9
5
  * Update flog specs so that they actually run flog
10
6
  * Add flay specs that run flay
11
7
  * Convert readme to markdown and rename to README.mkdn so github will render it
@@ -139,8 +139,7 @@ module MetricFu
139
139
  def cycle(first_value, second_value, iteration)
140
140
  return first_value if iteration % 2 == 0
141
141
  return second_value
142
- end
143
-
142
+ end
144
143
 
145
144
  end
146
145
  end
@@ -9,6 +9,7 @@ module MetricFu
9
9
  :roodi, :saikuro, :rcov]
10
10
 
11
11
  AVAILABLE_GRAPHS = [:flog, :flay, :reek, :roodi, :rcov]
12
+ AVAILABLE_GRAPH_ENGINES = [:gchart, :bluff]
12
13
 
13
14
  # The @@configuration class variable holds a global type configuration
14
15
  # object for any parts of the system to use.
@@ -51,7 +52,6 @@ module MetricFu
51
52
  class Configuration
52
53
 
53
54
  def initialize #:nodoc:#
54
- warn_about_deprecated_config_options
55
55
  reset
56
56
  add_attr_accessors_to_self
57
57
  add_class_methods_to_metric_fu
@@ -82,25 +82,6 @@ module MetricFu
82
82
  end
83
83
  end
84
84
 
85
- # Check if certain constants that are deprecated have been
86
- # assigned. If so, warn the user about them, and the
87
- # fact that they will have no effect.
88
- def warn_about_deprecated_config_options
89
- if defined?(::MetricFu::CHURN_OPTIONS)
90
- raise("Use config.churn instead of MetricFu::CHURN_OPTIONS")
91
- end
92
- if defined?(::MetricFu::DIRECTORIES_TO_FLOG)
93
- raise("Use config.flog[:dirs_to_flog] "+
94
- "instead of MetricFu::DIRECTORIES_TO_FLOG")
95
- end
96
- if defined?(::MetricFu::SAIKURO_OPTIONS)
97
- raise("Use config.saikuro instead of MetricFu::SAIKURO_OPTIONS")
98
- end
99
- if defined?(SAIKURO_OPTIONS)
100
- raise("Use config.saikuro instead of SAIKURO_OPTIONS")
101
- end
102
- end
103
-
104
85
  # This allows us to have a nice syntax like:
105
86
  #
106
87
  # MetricFu.run do |config|
@@ -150,19 +131,10 @@ module MetricFu
150
131
  "--profile",
151
132
  "--rails",
152
133
  "--exclude /gems/,/Library/,/usr/,spec"]}
134
+
135
+ @file_globs_to_ignore = []
153
136
 
154
- @graph_theme = { :colors => %w(orange purple green white red blue pink yellow),
155
- :marker_color => 'blue',
156
- :background_colors => %w(white white)}
157
-
158
- relative_font_path = [File.dirname(__FILE__), '..', '..', 'vendor', '_fonts', 'monaco.ttf']
159
- @graph_font = File.expand_path(File.join(relative_font_path))
160
- @graph_size = "1000x400"
161
- @graph_title_font_size = 12
162
- @graph_legend_box_size = 12
163
- @graph_legend_font_size = 10
164
- @graph_marker_font_size = 10
165
-
137
+ @graph_engine = :bluff # can be :bluff or :gchart
166
138
  end
167
139
 
168
140
  # Perform a simple check to try and guess if we're running
@@ -197,7 +169,7 @@ module MetricFu
197
169
  end
198
170
 
199
171
  def platform #:nodoc:
200
- return PLATFORM
172
+ return RUBY_PLATFORM
201
173
  end
202
174
 
203
175
  def is_cruise_control_rb?
@@ -97,6 +97,13 @@ module MetricFu
97
97
  self.class.metric_directory
98
98
  end
99
99
 
100
+ def remove_excluded_files(paths, globs_to_remove = MetricFu.file_globs_to_ignore)
101
+ files_to_remove = []
102
+ globs_to_remove.each do |glob|
103
+ files_to_remove.concat(Dir[glob])
104
+ end
105
+ paths - files_to_remove
106
+ end
100
107
 
101
108
  # Defines some hook methods for the concrete classes to hook into.
102
109
  %w[emit analyze].each do |meth|
data/lib/base/graph.rb CHANGED
@@ -12,21 +12,23 @@ module MetricFu
12
12
  self.clazz = []
13
13
  end
14
14
 
15
- def add(graph_type)
16
- grapher_name = graph_type.to_s.capitalize + "Grapher"
15
+ def add(graph_type, graph_engine)
16
+ grapher_name = graph_type.to_s.capitalize + graph_engine.to_s.capitalize + "Grapher"
17
17
  self.clazz.push MetricFu.const_get(grapher_name).new
18
18
  end
19
19
 
20
20
 
21
21
  def generate
22
+ return if self.clazz.empty?
22
23
  puts "Generating graphs"
23
24
  Dir[File.join(MetricFu.data_directory, '*.yml')].sort.each do |metric_file|
24
25
  puts "Generating graphs for #{metric_file}"
25
26
  date = metric_file.split('/')[3].split('.')[0]
27
+ y, m, d = date[0..3].to_i, date[4..5].to_i, date[6..7].to_i
26
28
  metrics = YAML::load(File.open(metric_file))
27
29
 
28
30
  self.clazz.each do |grapher|
29
- grapher.get_metrics(metrics, date)
31
+ grapher.get_metrics(metrics, "#{m}/#{d}")
30
32
  end
31
33
  end
32
34
  self.clazz.each do |grapher|
data/lib/base/report.rb CHANGED
@@ -35,9 +35,9 @@ module MetricFu
35
35
 
36
36
  # Instantiates a new template class based on the configuration set
37
37
  # in MetricFu::Configuration, or through the MetricFu.config block
38
- # in your rake file (defaults to the included StandardTemplate) and
39
- # assigns the report_hash to the report_hash to the template and
40
- # asks itself to write itself out.
38
+ # in your rake file (defaults to the included AwesomeTemplate),
39
+ # assigns the report_hash to the report_hash in the template, and
40
+ # tells the template to to write itself out.
41
41
  def save_templatized_report
42
42
  @template = MetricFu.template_class.new
43
43
  @template.report = report_hash
@@ -1,5 +1,4 @@
1
1
  require 'chronic'
2
- require 'generator'
3
2
  module MetricFu
4
3
 
5
4
  class Churn < Generator
@@ -1,4 +1,3 @@
1
- require 'generator'
2
1
  module MetricFu
3
2
 
4
3
  class Flay < Generator
@@ -10,7 +9,8 @@ module MetricFu
10
9
 
11
10
  def emit
12
11
  files_to_flay = MetricFu.flay[:dirs_to_flay].map{|dir| Dir[File.join(dir, "**/*.rb")] }
13
- @output = `flay #{files_to_flay.join(" ")}`
12
+ files = remove_excluded_files(files_to_flay.flatten)
13
+ @output = `flay #{files.join(" ")}`
14
14
  end
15
15
 
16
16
  def analyze
@@ -1,3 +1,5 @@
1
+ require 'pathname'
2
+
1
3
  module MetricFu
2
4
 
3
5
  class Flog < Generator
@@ -9,17 +11,23 @@ module MetricFu
9
11
  end
10
12
 
11
13
  SCORE_FORMAT = "%0.2f"
12
- METHOD_LINE_REGEX = /(\d+\.\d+):\s+([A-Za-z:]+#.*)/
14
+ METHOD_LINE_REGEX = /\s*(\d+.\d+):\s+([A-Za-z]+(?:#|::).*)/
13
15
  OPERATOR_LINE_REGEX = /\s*(\d+\.\d+):\s(.*)$/
14
16
 
15
17
  def emit
16
18
  metric_dir = MetricFu::Flog.metric_directory
17
19
  MetricFu.flog[:dirs_to_flog].each do |directory|
18
- Dir.glob("#{directory}/**/*.rb").each do |filename|
20
+ directory = "." if directory=='./'
21
+ files = Dir.glob("#{directory}/**/*.rb")
22
+ files = remove_excluded_files(files)
23
+ files.each do |filename|
19
24
  output_dir = "#{metric_dir}/#{filename.split("/")[0..-2].join("/")}"
20
25
  mkdir_p(output_dir, :verbose => false) unless File.directory?(output_dir)
21
- if MetricFu::MD5Tracker.file_changed?(filename, metric_dir)
22
- `flog -ad #{filename} > #{metric_dir}/#{filename.split('.')[0]}.txt`
26
+ pathname = Pathname.new(filename)
27
+ if MetricFu::MD5Tracker.file_changed?(filename, metric_dir)
28
+ base_name = pathname.basename.to_s.gsub(/\..*$/,'.txt')
29
+ editted_filename = File.join(pathname.dirname.to_s, base_name)
30
+ `flog -ad #{filename} > #{metric_dir}/#{editted_filename}`
23
31
  end
24
32
  end
25
33
  end
@@ -54,7 +62,10 @@ module MetricFu
54
62
  page = parse(open(path, "r") { |f| f.read })
55
63
  if page
56
64
  page.path = path.sub(metric_directory, "").sub(".txt", ".rb")
57
- @pages << page
65
+ #don't include old cached flog results for files that no longer exist.
66
+ if is_file_current?(page.path.to_s)
67
+ @pages << page
68
+ end
58
69
  end
59
70
  end
60
71
  end
@@ -62,14 +73,30 @@ module MetricFu
62
73
  def to_h
63
74
  number_of_methods = @pages.inject(0) {|count, page| count += page.scanned_methods.size}
64
75
  total_flog_score = @pages.inject(0) {|total, page| total += page.score}
65
- sorted_pages = @pages.sort_by {|page| page.score }.reverse
76
+ sorted_pages = @pages.sort_by {|page| page.highest_score }.reverse
66
77
  {:flog => { :total => total_flog_score,
67
78
  :average => average_score(total_flog_score, number_of_methods),
68
79
  :pages => sorted_pages.map {|page| page.to_h}}}
69
80
  end
70
81
 
71
82
  private
72
-
83
+
84
+ def is_file_current?(pathname)
85
+ pathname = pathname.gsub(/^\//,'')
86
+ local_pathname = "./#{pathname}"
87
+ exists = false
88
+
89
+ MetricFu.flog[:dirs_to_flog].each do |directory|
90
+ directory = "." if directory=='./'
91
+ files = Dir.glob("#{directory}/**/*.rb")
92
+ if files.include?(pathname) || files.include?(local_pathname)
93
+ exists = true
94
+ break
95
+ end
96
+ end
97
+ exists
98
+ end
99
+
73
100
  def average_score(total_flog_score, number_of_methods)
74
101
  return 0 if total_flog_score == 0
75
102
  round_to_tenths(total_flog_score/number_of_methods)
@@ -6,7 +6,7 @@ module MetricFu
6
6
  NEW_FILE_MARKER = ("=" * 80) + "\n"
7
7
 
8
8
  def self.verify_dependencies!
9
- `flay --help`
9
+ `rcov --help`
10
10
  unless $?.success?
11
11
  if RUBY_PLATFORM =~ /java/
12
12
  raise 'running in jruby - rcov tasks not available'
@@ -36,7 +36,7 @@ module MetricFu
36
36
  test_files = FileList[*MetricFu.rcov[:test_files]].join(' ')
37
37
  rcov_opts = MetricFu.rcov[:rcov_opts].join(' ')
38
38
  output = ">> #{MetricFu::Rcov.metric_directory}/rcov.txt"
39
- `rcov --include-file #{test_files} #{rcov_opts} #{output}`
39
+ `rcov #{test_files} #{rcov_opts} #{output}`
40
40
  rescue LoadError
41
41
  if RUBY_PLATFORM =~ /java/
42
42
  puts 'running in jruby - rcov tasks not available'
@@ -10,7 +10,34 @@ module MetricFu
10
10
 
11
11
  def emit
12
12
  files_to_reek = MetricFu.reek[:dirs_to_reek].map{|dir| Dir[File.join(dir, "**/*.rb")] }
13
- @output = `reek #{files_to_reek.join(" ")}`
13
+ files = remove_excluded_files(files_to_reek.flatten)
14
+ @output = `reek #{files.join(" ")}`
15
+ @output = massage_for_reek_12 if reek_12?
16
+ end
17
+
18
+ def reek_12?
19
+ return false if @output.length == 0
20
+ (@output =~ /^"/) != 0
21
+ end
22
+
23
+ def massage_for_reek_12
24
+ section_break = ''
25
+ @output.split("\n").map do |line|
26
+ case line
27
+ when /^ /
28
+ "#{line.gsub(/^ /, '')}\n"
29
+ else
30
+ parts = line.split(" -- ")
31
+ if parts[1].nil?
32
+ "#{line}\n"
33
+ else
34
+ warnings = parts[1].gsub(/ \(.*\):/, ':')
35
+ result = "#{section_break}\"#{parts[0]}\" -- #{warnings}\n"
36
+ section_break = "\n"
37
+ result
38
+ end
39
+ end
40
+ end.join
14
41
  end
15
42
 
16
43
  def analyze
@@ -9,7 +9,8 @@ module MetricFu
9
9
 
10
10
  def emit
11
11
  files_to_analyze = MetricFu.roodi[:dirs_to_roodi].map{|dir| Dir[File.join(dir, "**/*.rb")] }
12
- @output = `roodi #{files_to_analyze.join(" ")}`
12
+ files = remove_excluded_files(files_to_analyze.flatten)
13
+ @output = `roodi #{files.join(" ")}`
13
14
  end
14
15
 
15
16
  def analyze
@@ -3,17 +3,13 @@ module MetricFu
3
3
  class Saikuro < Generator
4
4
 
5
5
  def emit
6
- relative_path = [File.dirname(__FILE__), '..', '..',
7
- 'vendor', 'saikuro', 'saikuro.rb']
8
- saikuro = File.expand_path(File.join(relative_path))
9
-
10
6
  MetricFu.saikuro[:input_directory] = format_directories
11
7
 
12
8
  options_string = MetricFu.saikuro.inject("") do |options, option|
13
9
  options + "--#{option.join(' ')} "
14
10
  end
15
11
 
16
- sh %{ruby "#{saikuro}" #{options_string}} do |ok, response|
12
+ sh %{saikuro #{options_string}} do |ok, response|
17
13
  unless ok
18
14
  puts "Saikuro failed with exit status: #{response.exitstatus}"
19
15
  exit 1
@@ -120,10 +116,10 @@ module MetricFu
120
116
  line = @file_handle.readline
121
117
  element = Saikuro::ParsingElement.new(line)
122
118
  elsif line.match /END/
123
- @elements << element unless element.nil?
119
+ @elements << element if element
124
120
  element = nil
125
121
  else
126
- element << line
122
+ element << line if element
127
123
  end
128
124
  end
129
125
  rescue EOFError
@@ -0,0 +1,86 @@
1
+ require 'active_support'
2
+
3
+ module MetricFu
4
+ class Grapher
5
+ BLUFF_GRAPH_SIZE = "1000x600"
6
+ BLUFF_DEFAULT_OPTIONS = <<-EOS
7
+ var g = new Bluff.Line('graph', "#{BLUFF_GRAPH_SIZE}");
8
+ g.theme_37signals();
9
+ g.tooltips = true;
10
+ g.title_font_size = "24px"
11
+ g.legend_font_size = "12px"
12
+ g.marker_font_size = "10px"
13
+ EOS
14
+ end
15
+
16
+ class FlayBluffGrapher < FlayGrapher
17
+ def graph!
18
+ content = <<-EOS
19
+ #{BLUFF_DEFAULT_OPTIONS}
20
+ g.title = 'Flay: duplication';
21
+ g.data('flay', [#{@flay_score.join(',')}]);
22
+ g.labels = #{@labels.to_json};
23
+ g.draw();
24
+ EOS
25
+ File.open(File.join(MetricFu.output_directory, 'flay.js'), 'w') {|f| f << content }
26
+ end
27
+ end
28
+
29
+ class FlogBluffGrapher < FlogGrapher
30
+ def graph!
31
+ content = <<-EOS
32
+ #{BLUFF_DEFAULT_OPTIONS}
33
+ g.title = 'Flog: code complexity';
34
+ g.data('average', [#{@flog_average.join(',')}]);
35
+ g.data('top 5% average', [#{@top_five_percent_average.join(',')}])
36
+ g.labels = #{@labels.to_json};
37
+ g.draw();
38
+ EOS
39
+ File.open(File.join(MetricFu.output_directory, 'flog.js'), 'w') {|f| f << content }
40
+ end
41
+ end
42
+
43
+ class RcovBluffGrapher < RcovGrapher
44
+ def graph!
45
+ content = <<-EOS
46
+ #{BLUFF_DEFAULT_OPTIONS}
47
+ g.title = 'Rcov: code coverage';
48
+ g.data('rcov', [#{@rcov_percent.join(',')}]);
49
+ g.labels = #{@labels.to_json};
50
+ g.draw();
51
+ EOS
52
+ File.open(File.join(MetricFu.output_directory, 'rcov.js'), 'w') {|f| f << content }
53
+ end
54
+ end
55
+
56
+ class ReekBluffGrapher < ReekGrapher
57
+ def graph!
58
+ legend = @reek_count.keys.sort
59
+ data = ""
60
+ legend.each do |name|
61
+ data += "g.data('#{name}', [#{@reek_count[name].join(',')}])\n"
62
+ end
63
+ content = <<-EOS
64
+ #{BLUFF_DEFAULT_OPTIONS}
65
+ g.title = 'Reek: code smells';
66
+ #{data}
67
+ g.labels = #{@labels.to_json};
68
+ g.draw();
69
+ EOS
70
+ File.open(File.join(MetricFu.output_directory, 'reek.js'), 'w') {|f| f << content }
71
+ end
72
+ end
73
+
74
+ class RoodiBluffGrapher < RoodiGrapher
75
+ def graph!
76
+ content = <<-EOS
77
+ #{BLUFF_DEFAULT_OPTIONS}
78
+ g.title = 'Roodi: design problems';
79
+ g.data('roodi', [#{@roodi_count.join(',')}]);
80
+ g.labels = #{@labels.to_json};
81
+ g.draw();
82
+ EOS
83
+ File.open(File.join(MetricFu.output_directory, 'roodi.js'), 'w') {|f| f << content }
84
+ end
85
+ end
86
+ end