nielsm-metric_fu 1.1.1 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/HISTORY +46 -0
  2. data/Rakefile +4 -7
  3. data/TODO +0 -4
  4. data/lib/base/base_template.rb +1 -2
  5. data/lib/base/configuration.rb +9 -35
  6. data/lib/base/generator.rb +9 -1
  7. data/lib/base/graph.rb +5 -3
  8. data/lib/base/report.rb +3 -3
  9. data/lib/generators/churn.rb +15 -72
  10. data/lib/generators/flay.rb +3 -2
  11. data/lib/generators/flog.rb +33 -6
  12. data/lib/generators/rcov.rb +32 -37
  13. data/lib/generators/reek.rb +28 -1
  14. data/lib/generators/roodi.rb +2 -1
  15. data/lib/generators/saikuro.rb +53 -40
  16. data/lib/generators/stats.rb +29 -13
  17. data/lib/graphs/engines/bluff.rb +86 -0
  18. data/lib/graphs/engines/gchart.rb +118 -0
  19. data/lib/graphs/flay_grapher.rb +6 -20
  20. data/lib/graphs/flog_grapher.rb +25 -22
  21. data/lib/graphs/grapher.rb +11 -0
  22. data/lib/graphs/rcov_grapher.rb +2 -16
  23. data/lib/graphs/reek_grapher.rb +12 -24
  24. data/lib/graphs/roodi_grapher.rb +6 -20
  25. data/lib/metric_fu.rb +13 -6
  26. data/lib/templates/awesome/awesome_template.rb +7 -0
  27. data/lib/templates/awesome/churn.html.erb +39 -0
  28. data/lib/templates/awesome/css/buttons.css +82 -0
  29. data/lib/templates/awesome/{default.css → css/default.css} +16 -0
  30. data/lib/templates/awesome/css/integrity.css +335 -0
  31. data/lib/templates/awesome/css/reset.css +7 -0
  32. data/lib/templates/awesome/flay.html.erb +8 -1
  33. data/lib/templates/awesome/flog.html.erb +8 -1
  34. data/lib/templates/awesome/layout.html.erb +7 -4
  35. data/lib/templates/awesome/rcov.html.erb +7 -1
  36. data/lib/templates/awesome/reek.html.erb +7 -1
  37. data/lib/templates/awesome/roodi.html.erb +7 -1
  38. data/lib/templates/javascripts/bluff-min.js +1 -0
  39. data/lib/templates/javascripts/excanvas.js +35 -0
  40. data/lib/templates/javascripts/js-class.js +1 -0
  41. data/spec/base/configuration_spec.rb +21 -54
  42. data/spec/base/generator_spec.rb +63 -0
  43. data/spec/base/graph_spec.rb +24 -0
  44. data/spec/generators/churn_spec.rb +16 -125
  45. data/spec/generators/flay_spec.rb +11 -5
  46. data/spec/generators/flog_spec.rb +19 -0
  47. data/spec/generators/reek_spec.rb +65 -0
  48. data/spec/graphs/engines/bluff_spec.rb +16 -0
  49. data/spec/graphs/engines/gchart_spec.rb +108 -0
  50. data/spec/graphs/flay_grapher_spec.rb +37 -0
  51. data/spec/graphs/flog_grapher_spec.rb +40 -10
  52. data/spec/graphs/rcov_grapher_spec.rb +37 -0
  53. data/spec/graphs/reek_grapher_spec.rb +46 -0
  54. data/spec/graphs/roodi_grapher_spec.rb +37 -0
  55. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +0 -0
  56. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +0 -0
  57. data/spec/resources/saikuro/index_cyclo.html +0 -0
  58. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +1 -0
  59. data/spec/resources/yml/20090630.yml +7844 -0
  60. data/spec/spec.opts +2 -4
  61. data/spec/spec_helper.rb +2 -23
  62. data/tasks/metric_fu.rake +1 -1
  63. metadata +72 -14
  64. data/vendor/_fonts/monaco.ttf +0 -0
  65. data/vendor/saikuro/saikuro.rb +0 -1214
@@ -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
@@ -27,36 +23,9 @@ module MetricFu
27
23
  end
28
24
 
29
25
  def analyze
30
- @files = []
31
- saikuro_results.each do |path|
32
- if Saikuro::SFile.is_valid_text_file?(path)
33
- file = Saikuro::SFile.new(path)
34
- if file
35
- @files << file
36
- end
37
- end
38
- end
39
- @files = @files.sort_by do |file|
40
- file.elements.
41
- max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
42
- complexity.to_i
43
- end
44
- @files.reverse!
45
- klasses = []
46
- @files.each {|f| klasses << f.elements}
47
- klasses.flatten!
48
- @classes = klasses.sort_by {|k| k.complexity.to_i}
49
- @classes.reverse!
50
- meths = []
51
- @files.each {|f|
52
- f.elements.each {|el|
53
- el.defs.each {|defn|
54
- defn.name = "#{el.name}##{defn.name}"
55
- meths << defn}
56
- }
57
- }
58
- meths = meths.sort_by {|meth| meth.complexity.to_i}
59
- @meths = meths.reverse
26
+ @files = sort_files(assemble_files)
27
+ @classes = sort_classes(assemble_classes(@files))
28
+ @meths = sort_methods(assemble_methods(@files))
60
29
  end
61
30
 
62
31
  def to_h
@@ -71,10 +40,54 @@ module MetricFu
71
40
  }
72
41
  }
73
42
  end
74
-
75
- def saikuro_results
76
- Dir.glob("#{metric_directory}/**/*.html")
43
+
44
+ private
45
+ def sort_methods(methods)
46
+ methods.sort_by {|method| method.complexity.to_i}.reverse
47
+ end
48
+
49
+ def assemble_methods(files)
50
+ methods = []
51
+ files.each do |file|
52
+ file.elements.each do |element|
53
+ element.defs.each do |defn|
54
+ defn.name = "#{element.name}##{defn.name}"
55
+ methods << defn
56
+ end
57
+ end
58
+ end
59
+ methods
60
+ end
61
+
62
+ def sort_classes(classes)
63
+ classes.sort_by {|k| k.complexity.to_i}.reverse
64
+ end
65
+
66
+ def assemble_classes(files)
67
+ files.map {|f| f.elements}.flatten
68
+ end
69
+
70
+ def sort_files(files)
71
+ files.sort_by do |file|
72
+ file.elements.
73
+ max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
74
+ complexity.to_i
75
+ end.reverse
76
+ end
77
+
78
+ def assemble_files
79
+ files = []
80
+ Dir.glob("#{metric_directory}/**/*.html").each do |path|
81
+ if Saikuro::SFile.is_valid_text_file?(path)
82
+ file = Saikuro::SFile.new(path)
83
+ if file
84
+ files << file
85
+ end
86
+ end
87
+ end
88
+ files
77
89
  end
90
+
78
91
  end
79
92
 
80
93
  class Saikuro::SFile
@@ -120,10 +133,10 @@ module MetricFu
120
133
  line = @file_handle.readline
121
134
  element = Saikuro::ParsingElement.new(line)
122
135
  elsif line.match /END/
123
- @elements << element unless element.nil?
136
+ @elements << element if element
124
137
  element = nil
125
138
  else
126
- element << line
139
+ element << line if element
127
140
  end
128
141
  end
129
142
  rescue EOFError
@@ -8,18 +8,39 @@ module MetricFu
8
8
 
9
9
  def analyze
10
10
  output = File.open(metric_directory + '/stats.txt').read
11
- output = output.split("\n")
12
- output = output.find_all {|line| line[0].chr != "+" }
13
- output = output.find_all {|line| line[0].chr != "(" }
14
- output.shift
15
- totals = output.pop
16
- totals = totals.split(" ").find_all {|el| ! el.empty? }
11
+ lines = remove_noise(output)
12
+
17
13
  @stats = {}
14
+
15
+ set_global_stats(lines.pop)
16
+ set_granular_stats(lines)
17
+
18
+ @stats
19
+ end
20
+
21
+ def to_h
22
+ {:stats => @stats}
23
+ end
24
+
25
+ private
26
+
27
+ def remove_noise(output)
28
+ lines = output.split("\n")
29
+ lines = lines.find_all {|line| line[0].chr != "+" }
30
+ lines = lines.find_all {|line| line[0].chr != "(" }
31
+ lines.shift
32
+ lines
33
+ end
34
+
35
+ def set_global_stats(totals)
36
+ totals = totals.split(" ").find_all {|el| ! el.empty? }
18
37
  @stats[:codeLOC] = totals[0].match(/\d.*/)[0].to_i
19
38
  @stats[:testLOC] = totals[1].match(/\d.*/)[0].to_i
20
39
  @stats[:code_to_test_ratio] = totals[2].match(/1\:(\d.*)/)[1].to_f
21
-
22
- @stats[:lines] = output.map do |line|
40
+ end
41
+
42
+ def set_granular_stats(lines)
43
+ @stats[:lines] = lines.map do |line|
23
44
  elements = line.split("|")
24
45
  elements.map! {|el| el.strip }
25
46
  elements = elements.find_all {|el| ! el.empty? }
@@ -32,11 +53,6 @@ module MetricFu
32
53
  end
33
54
  info_line
34
55
  end
35
- @stats
36
- end
37
-
38
- def to_h
39
- {:stats => @stats}
40
56
  end
41
57
 
42
58
  end
@@ -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
@@ -0,0 +1,118 @@
1
+ module MetricFu
2
+ module GchartGrapher
3
+ COLORS = %w{009999 FF7400 A60000 008500 E6399B 344AD7 00B860 D5CCB9}
4
+ GCHART_GRAPH_SIZE = "1000x300" # maximum permitted image size is 300000 pixels
5
+
6
+ NUMBER_OF_TICKS = 6
7
+ def determine_y_axis_scale(values)
8
+ values.collect! {|val| val || 0.0 }
9
+ if values.empty?
10
+ @max_value = 10
11
+ @yaxis = [0, 2, 4, 6, 8, 10]
12
+ else
13
+ @max_value = values.max + Integer(0.1 * values.max)
14
+ portion_size = (@max_value / (NUMBER_OF_TICKS - 1).to_f).ceil
15
+ @yaxis = []
16
+ NUMBER_OF_TICKS.times {|n| @yaxis << Integer(portion_size * n) }
17
+ @max_value = @yaxis.last
18
+ end
19
+ end
20
+ end
21
+
22
+ class Grapher
23
+ include MetricFu::GchartGrapher
24
+
25
+ def self.require_graphing_gem
26
+ require 'gchart'
27
+ rescue LoadError
28
+ puts "#"*99 + "\n" +
29
+ "If you want to use google charts for graphing, you'll need to install the googlecharts rubygem." +
30
+ "\n" + "#"*99
31
+ end
32
+ end
33
+
34
+ class FlayGchartGrapher < FlayGrapher
35
+ def graph!
36
+ determine_y_axis_scale(@flay_score)
37
+ url = Gchart.line(
38
+ :size => GCHART_GRAPH_SIZE,
39
+ :title => URI.escape("Flay: duplication"),
40
+ :data => @flay_score,
41
+ :max_value => @max_value,
42
+ :axis_with_labels => 'x,y',
43
+ :axis_labels => [@labels.values, @yaxis],
44
+ :format => 'file',
45
+ :filename => File.join(MetricFu.output_directory, 'flay.png'))
46
+ end
47
+ end
48
+
49
+ class FlogGchartGrapher < FlogGrapher
50
+ def graph!
51
+ determine_y_axis_scale(@top_five_percent_average + @flog_average)
52
+ url = Gchart.line(
53
+ :size => GCHART_GRAPH_SIZE,
54
+ :title => URI.escape("Flog: code complexity"),
55
+ :data => [@flog_average, @top_five_percent_average],
56
+ :stacked => false,
57
+ :bar_colors => COLORS[0..1],
58
+ :legend => ['average', 'top 5%25 average'],
59
+ :max_value => @max_value,
60
+ :axis_with_labels => 'x,y',
61
+ :axis_labels => [@labels.values, @yaxis],
62
+ :format => 'file',
63
+ :filename => File.join(MetricFu.output_directory, 'flog.png'))
64
+ end
65
+ end
66
+
67
+ class RcovGchartGrapher < RcovGrapher
68
+ def graph!
69
+ url = Gchart.line(
70
+ :size => GCHART_GRAPH_SIZE,
71
+ :title => URI.escape("Rcov: code coverage"),
72
+ :data => self.rcov_percent,
73
+ :max_value => 101,
74
+ :axis_with_labels => 'x,y',
75
+ :axis_labels => [self.labels.values, [0,20,40,60,80,100]],
76
+ :format => 'file',
77
+ :filename => File.join(MetricFu.output_directory, 'rcov.png')
78
+ )
79
+ end
80
+ end
81
+
82
+ class ReekGchartGrapher < ReekGrapher
83
+ def graph!
84
+ determine_y_axis_scale(@reek_count.values.flatten.uniq)
85
+ values = []
86
+ legend = @reek_count.keys.sort
87
+ legend.collect {|k| values << @reek_count[k]}
88
+
89
+ url = Gchart.line(
90
+ :size => GCHART_GRAPH_SIZE,
91
+ :title => URI.escape("Reek: code smells"),
92
+ :data => values,
93
+ :stacked => false,
94
+ :bar_colors => COLORS,
95
+ :legend => legend,
96
+ :max_value => @max_value,
97
+ :axis_with_labels => 'x,y',
98
+ :axis_labels => [@labels.values, @yaxis],
99
+ :format => 'file',
100
+ :filename => File.join(MetricFu.output_directory, 'reek.png'))
101
+ end
102
+ end
103
+
104
+ class RoodiGchartGrapher < RoodiGrapher
105
+ def graph!
106
+ determine_y_axis_scale(@roodi_count)
107
+ url = Gchart.line(
108
+ :size => GCHART_GRAPH_SIZE,
109
+ :title => URI.escape("Roodi: potential design problems"),
110
+ :data => @roodi_count,
111
+ :max_value => @max_value,
112
+ :axis_with_labels => 'x,y',
113
+ :axis_labels => [@labels.values, @yaxis],
114
+ :format => 'file',
115
+ :filename => File.join(MetricFu.output_directory, 'roodi.png'))
116
+ end
117
+ end
118
+ end
@@ -1,32 +1,18 @@
1
- require 'gruff'
2
1
  module MetricFu
3
2
 
4
- class FlayGrapher
3
+ class FlayGrapher < Grapher
5
4
 
6
5
  attr_accessor :flay_score, :labels
7
6
 
8
7
  def initialize
9
- self.flay_score = []
10
- self.labels = {}
8
+ super
9
+ @flay_score = []
10
+ @labels = {}
11
11
  end
12
12
 
13
13
  def get_metrics(metrics, date)
14
- self.flay_score.push(metrics[:flay][:total_score].to_i)
15
- self.labels.update( { self.labels.size => date })
16
- end
17
-
18
- def graph!
19
- g = Gruff::Line.new(MetricFu.graph_size)
20
- g.title = "Flay: duplication"
21
- g.theme = MetricFu.graph_theme
22
- g.font = MetricFu.graph_font
23
- g.data('flay', self.flay_score)
24
- g.labels = self.labels
25
- g.title_font_size = MetricFu.graph_title_font_size
26
- g.legend_box_size = MetricFu.graph_legend_box_size
27
- g.legend_font_size = MetricFu.graph_legend_font_size
28
- g.marker_font_size = MetricFu.graph_marker_font_size
29
- g.write(File.join(MetricFu.output_directory, 'flay.png'))
14
+ @flay_score.push(metrics[:flay][:total_score].to_i)
15
+ @labels.update( { @labels.size => date })
30
16
  end
31
17
 
32
18
  end