bf4-metric_fu 2.1.3.1

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 (127) hide show
  1. data/HISTORY +252 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.md +49 -0
  4. data/Rakefile +22 -0
  5. data/TODO +6 -0
  6. data/lib/base/base_template.rb +182 -0
  7. data/lib/base/churn_analyzer.rb +38 -0
  8. data/lib/base/code_issue.rb +100 -0
  9. data/lib/base/configuration.rb +219 -0
  10. data/lib/base/flay_analyzer.rb +50 -0
  11. data/lib/base/flog_analyzer.rb +43 -0
  12. data/lib/base/generator.rb +166 -0
  13. data/lib/base/graph.rb +44 -0
  14. data/lib/base/grouping.rb +42 -0
  15. data/lib/base/line_numbers.rb +79 -0
  16. data/lib/base/location.rb +87 -0
  17. data/lib/base/md5_tracker.rb +52 -0
  18. data/lib/base/metric_analyzer.rb +331 -0
  19. data/lib/base/ranking.rb +34 -0
  20. data/lib/base/rcov_analyzer.rb +43 -0
  21. data/lib/base/record.rb +43 -0
  22. data/lib/base/reek_analyzer.rb +164 -0
  23. data/lib/base/report.rb +110 -0
  24. data/lib/base/roodi_analyzer.rb +37 -0
  25. data/lib/base/saikuro_analyzer.rb +48 -0
  26. data/lib/base/scoring_strategies.rb +29 -0
  27. data/lib/base/stats_analyzer.rb +37 -0
  28. data/lib/base/table.rb +108 -0
  29. data/lib/generators/churn.rb +28 -0
  30. data/lib/generators/flay.rb +31 -0
  31. data/lib/generators/flog.rb +113 -0
  32. data/lib/generators/hotspots.rb +52 -0
  33. data/lib/generators/rails_best_practices.rb +53 -0
  34. data/lib/generators/rcov.rb +124 -0
  35. data/lib/generators/reek.rb +81 -0
  36. data/lib/generators/roodi.rb +35 -0
  37. data/lib/generators/saikuro.rb +259 -0
  38. data/lib/generators/stats.rb +58 -0
  39. data/lib/graphs/engines/bluff.rb +113 -0
  40. data/lib/graphs/engines/gchart.rb +157 -0
  41. data/lib/graphs/flay_grapher.rb +18 -0
  42. data/lib/graphs/flog_grapher.rb +57 -0
  43. data/lib/graphs/grapher.rb +11 -0
  44. data/lib/graphs/rails_best_practices_grapher.rb +19 -0
  45. data/lib/graphs/rcov_grapher.rb +18 -0
  46. data/lib/graphs/reek_grapher.rb +30 -0
  47. data/lib/graphs/roodi_grapher.rb +18 -0
  48. data/lib/graphs/stats_grapher.rb +20 -0
  49. data/lib/metric_fu.rb +80 -0
  50. data/lib/tasks/metric_fu.rake +36 -0
  51. data/lib/templates/awesome/awesome_template.rb +92 -0
  52. data/lib/templates/awesome/churn.html.erb +58 -0
  53. data/lib/templates/awesome/css/buttons.css +82 -0
  54. data/lib/templates/awesome/css/default.css +91 -0
  55. data/lib/templates/awesome/css/integrity.css +334 -0
  56. data/lib/templates/awesome/css/reset.css +7 -0
  57. data/lib/templates/awesome/css/syntax.css +19 -0
  58. data/lib/templates/awesome/flay.html.erb +34 -0
  59. data/lib/templates/awesome/flog.html.erb +55 -0
  60. data/lib/templates/awesome/hotspots.html.erb +62 -0
  61. data/lib/templates/awesome/index.html.erb +34 -0
  62. data/lib/templates/awesome/layout.html.erb +30 -0
  63. data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
  64. data/lib/templates/awesome/rcov.html.erb +42 -0
  65. data/lib/templates/awesome/reek.html.erb +40 -0
  66. data/lib/templates/awesome/roodi.html.erb +27 -0
  67. data/lib/templates/awesome/saikuro.html.erb +71 -0
  68. data/lib/templates/awesome/stats.html.erb +51 -0
  69. data/lib/templates/javascripts/bluff-min.js +1 -0
  70. data/lib/templates/javascripts/excanvas.js +35 -0
  71. data/lib/templates/javascripts/js-class.js +1 -0
  72. data/lib/templates/standard/churn.html.erb +31 -0
  73. data/lib/templates/standard/default.css +64 -0
  74. data/lib/templates/standard/flay.html.erb +34 -0
  75. data/lib/templates/standard/flog.html.erb +57 -0
  76. data/lib/templates/standard/hotspots.html.erb +54 -0
  77. data/lib/templates/standard/index.html.erb +41 -0
  78. data/lib/templates/standard/rails_best_practices.html.erb +29 -0
  79. data/lib/templates/standard/rcov.html.erb +43 -0
  80. data/lib/templates/standard/reek.html.erb +42 -0
  81. data/lib/templates/standard/roodi.html.erb +29 -0
  82. data/lib/templates/standard/saikuro.html.erb +84 -0
  83. data/lib/templates/standard/standard_template.rb +27 -0
  84. data/lib/templates/standard/stats.html.erb +55 -0
  85. data/spec/base/base_template_spec.rb +194 -0
  86. data/spec/base/configuration_spec.rb +277 -0
  87. data/spec/base/generator_spec.rb +223 -0
  88. data/spec/base/graph_spec.rb +61 -0
  89. data/spec/base/line_numbers_spec.rb +62 -0
  90. data/spec/base/location_spec.rb +127 -0
  91. data/spec/base/md5_tracker_spec.rb +57 -0
  92. data/spec/base/metric_analyzer_spec.rb +452 -0
  93. data/spec/base/ranking_spec.rb +42 -0
  94. data/spec/base/report_spec.rb +146 -0
  95. data/spec/base/table_spec.rb +36 -0
  96. data/spec/generators/churn_spec.rb +41 -0
  97. data/spec/generators/flay_spec.rb +105 -0
  98. data/spec/generators/flog_spec.rb +70 -0
  99. data/spec/generators/hotspots_spec.rb +88 -0
  100. data/spec/generators/rails_best_practices_spec.rb +52 -0
  101. data/spec/generators/rcov_spec.rb +180 -0
  102. data/spec/generators/reek_spec.rb +134 -0
  103. data/spec/generators/roodi_spec.rb +24 -0
  104. data/spec/generators/saikuro_spec.rb +74 -0
  105. data/spec/generators/stats_spec.rb +74 -0
  106. data/spec/graphs/engines/bluff_spec.rb +19 -0
  107. data/spec/graphs/engines/gchart_spec.rb +156 -0
  108. data/spec/graphs/flay_grapher_spec.rb +56 -0
  109. data/spec/graphs/flog_grapher_spec.rb +108 -0
  110. data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
  111. data/spec/graphs/rcov_grapher_spec.rb +56 -0
  112. data/spec/graphs/reek_grapher_spec.rb +65 -0
  113. data/spec/graphs/roodi_grapher_spec.rb +56 -0
  114. data/spec/graphs/stats_grapher_spec.rb +68 -0
  115. data/spec/resources/line_numbers/foo.rb +33 -0
  116. data/spec/resources/line_numbers/module.rb +11 -0
  117. data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
  118. data/spec/resources/line_numbers/two_classes.rb +11 -0
  119. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
  120. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
  121. data/spec/resources/saikuro/index_cyclo.html +155 -0
  122. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
  123. data/spec/resources/yml/20090630.yml +7922 -0
  124. data/spec/resources/yml/metric_missing.yml +1 -0
  125. data/spec/spec.opts +6 -0
  126. data/spec/spec_helper.rb +7 -0
  127. metadata +560 -0
@@ -0,0 +1,35 @@
1
+ module MetricFu
2
+ class Roodi < Generator
3
+
4
+ def emit
5
+ files_to_analyze = MetricFu.roodi[:dirs_to_roodi].map{|dir| Dir[File.join(dir, "**/*.rb")] }
6
+ files = remove_excluded_files(files_to_analyze.flatten)
7
+ config = MetricFu.roodi[:roodi_config] ? "-config=#{MetricFu.roodi[:roodi_config]}" : ""
8
+ @output = `roodi #{config} #{files.join(" ")}`
9
+ end
10
+
11
+ def analyze
12
+ @matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
13
+ total = @matches.pop
14
+ @matches.reject! {|array| array.empty? }
15
+ @matches.map! do |match|
16
+ file, line = match[0].split(':')
17
+ problem = match[1]
18
+ {:file => file, :line => line, :problem => problem}
19
+ end
20
+ @roodi_results = {:total => total, :problems => @matches}
21
+ end
22
+
23
+ def to_h
24
+ {:roodi => @roodi_results}
25
+ end
26
+
27
+ def per_file_info(out)
28
+ @matches.each do |match|
29
+ out[match[:file]] ||= {}
30
+ out[match[:file]][match[:line]] ||= []
31
+ out[match[:file]][match[:line]] << {:type => :roodi, :description => match[:problem]}
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,259 @@
1
+ module MetricFu
2
+
3
+ class Saikuro < Generator
4
+ include Rake::DSL if defined?(Rake::DSL) # rake 0.8.7 and 0.9.2 compatible
5
+
6
+ def emit
7
+ options_string = MetricFu.saikuro.inject("") do |options, option|
8
+ option[0] == :input_directory ? options : options + "--#{option.join(' ')} "
9
+ end
10
+
11
+ MetricFu.saikuro[:input_directory].each do |input_dir|
12
+ options_string += "--input_directory #{input_dir} "
13
+ end
14
+
15
+ saikuro_bin= $:.map{|d| d+'/../bin/saikuro'}.select{|f| File.exists? f}.first || 'saikuro'
16
+ sh %{#{saikuro_bin} #{options_string}} do |ok, response|
17
+ unless ok
18
+ puts "Saikuro failed with exit status: #{response.exitstatus}"
19
+ exit 1
20
+ end
21
+ end
22
+ end
23
+
24
+ def format_directories
25
+ dirs = MetricFu.saikuro[:input_directory].join(" | ")
26
+ "\"#{dirs}\""
27
+ end
28
+
29
+ def analyze
30
+ @files = sort_files(assemble_files)
31
+ @classes = sort_classes(assemble_classes(@files))
32
+ @meths = sort_methods(assemble_methods(@files))
33
+ end
34
+
35
+ def to_h
36
+ files = @files.map do |file|
37
+ my_file = file.to_h
38
+
39
+ f = file.filepath
40
+ f.gsub!(%r{^#{metric_directory}/}, '')
41
+ f << "/#{file.filename}"
42
+
43
+ my_file[:filename] = f
44
+ my_file
45
+ end
46
+ @saikuro_data = {:files => files,
47
+ :classes => @classes.map {|c| c.to_h},
48
+ :methods => @meths.map {|m| m.to_h}
49
+ }
50
+ {:saikuro => @saikuro_data}
51
+ end
52
+
53
+ def per_file_info(out)
54
+ @saikuro_data[:files].each do |file_data|
55
+ next if File.extname(file_data[:filename]) == '.erb' || !File.exists?(file_data[:filename])
56
+ begin
57
+ line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:filename], 'r').read)
58
+ rescue StandardError => e
59
+ raise e unless e.message =~ /you shouldn't be able to get here/
60
+ puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Saikuro information for this file."
61
+ next
62
+ end
63
+
64
+ out[file_data[:filename]] ||= {}
65
+ file_data[:classes].each do |class_data|
66
+ class_data[:methods].each do |method_data|
67
+ line = line_numbers.start_line_for_method(method_data[:name])
68
+ out[file_data[:filename]][line.to_s] ||= []
69
+ out[file_data[:filename]][line.to_s] << {:type => :saikuro,
70
+ :description => "Complexity #{method_data[:complexity]}"}
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+ def sort_methods(methods)
78
+ methods.sort_by {|method| method.complexity.to_i}.reverse
79
+ end
80
+
81
+ def assemble_methods(files)
82
+ methods = []
83
+ files.each do |file|
84
+ file.elements.each do |element|
85
+ element.defs.each do |defn|
86
+ defn.name = "#{element.name}##{defn.name}"
87
+ methods << defn
88
+ end
89
+ end
90
+ end
91
+ methods
92
+ end
93
+
94
+ def sort_classes(classes)
95
+ classes.sort_by {|k| k.complexity.to_i}.reverse
96
+ end
97
+
98
+ def assemble_classes(files)
99
+ files.map {|f| f.elements}.flatten
100
+ end
101
+
102
+ def sort_files(files)
103
+ files.sort_by do |file|
104
+ file.elements.
105
+ max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
106
+ complexity.to_i
107
+ end.reverse
108
+ end
109
+
110
+ def assemble_files
111
+ files = []
112
+ Dir.glob("#{metric_directory}/**/*.html").each do |path|
113
+ if Saikuro::SFile.is_valid_text_file?(path)
114
+ file = Saikuro::SFile.new(path)
115
+ if file
116
+ files << file
117
+ end
118
+ end
119
+ end
120
+ files
121
+ end
122
+
123
+ end
124
+
125
+ class Saikuro::SFile
126
+
127
+ attr_reader :elements
128
+
129
+ def initialize(path)
130
+ @path = path
131
+ @file_handle = File.open(@path, "r")
132
+ @elements = []
133
+ get_elements
134
+ ensure
135
+ @file_handle.close if @file_handle
136
+ end
137
+
138
+ def self.is_valid_text_file?(path)
139
+ File.open(path, "r") do |f|
140
+ if f.eof? || !f.readline.match(/--/)
141
+ return false
142
+ else
143
+ return true
144
+ end
145
+ end
146
+ end
147
+
148
+ def filename
149
+ File.basename(@path, '_cyclo.html')
150
+ end
151
+
152
+ def filepath
153
+ File.dirname(@path)
154
+ end
155
+
156
+ def to_h
157
+ merge_classes
158
+ {:classes => @elements}
159
+ end
160
+
161
+ def get_elements
162
+ begin
163
+ while (line = @file_handle.readline) do
164
+ return [] if line.nil? || line !~ /\S/
165
+ element ||= nil
166
+ if line.match /START/
167
+ unless element.nil?
168
+ @elements << element
169
+ element = nil
170
+ end
171
+ line = @file_handle.readline
172
+ element = Saikuro::ParsingElement.new(line)
173
+ elsif line.match /END/
174
+ @elements << element if element
175
+ element = nil
176
+ else
177
+ element << line if element
178
+ end
179
+ end
180
+ rescue EOFError
181
+ nil
182
+ end
183
+ end
184
+
185
+
186
+ def merge_classes
187
+ new_elements = []
188
+ get_class_names.each do |target_class|
189
+ elements = @elements.find_all {|el| el.name == target_class }
190
+ complexity = 0
191
+ lines = 0
192
+ defns = []
193
+ elements.each do |el|
194
+ complexity += el.complexity.to_i
195
+ lines += el.lines.to_i
196
+ defns << el.defs
197
+ end
198
+
199
+ new_element = {:class_name => target_class,
200
+ :complexity => complexity,
201
+ :lines => lines,
202
+ :methods => defns.flatten.map {|d| d.to_h}}
203
+ new_element[:methods] = new_element[:methods].
204
+ sort_by {|x| x[:complexity] }.
205
+ reverse
206
+
207
+ new_elements << new_element
208
+ end
209
+ @elements = new_elements if new_elements
210
+ end
211
+
212
+ def get_class_names
213
+ class_names = []
214
+ @elements.each do |element|
215
+ unless class_names.include?(element.name)
216
+ class_names << element.name
217
+ end
218
+ end
219
+ class_names
220
+ end
221
+
222
+ end
223
+
224
+ class Saikuro::ParsingElement
225
+ TYPE_REGEX=/Type:(.*) Name/
226
+ NAME_REGEX=/Name:(.*) Complexity/
227
+ COMPLEXITY_REGEX=/Complexity:(.*) Lines/
228
+ LINES_REGEX=/Lines:(.*)/
229
+
230
+ attr_reader :complexity, :lines, :defs, :element_type
231
+ attr_accessor :name
232
+
233
+ def initialize(line)
234
+ @line = line
235
+ @element_type = line.match(TYPE_REGEX)[1].strip
236
+ @name = line.match(NAME_REGEX)[1].strip
237
+ @complexity = line.match(COMPLEXITY_REGEX)[1].strip
238
+ @lines = line.match(LINES_REGEX)[1].strip
239
+ @defs = []
240
+ end
241
+
242
+ def <<(line)
243
+ @defs << Saikuro::ParsingElement.new(line)
244
+ end
245
+
246
+ def to_h
247
+ base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
248
+ unless @defs.empty?
249
+ defs = @defs.map do |my_def|
250
+ my_def = my_def.to_h
251
+ my_def.delete(:defs)
252
+ my_def
253
+ end
254
+ base[:defs] = defs
255
+ end
256
+ return base
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,58 @@
1
+ module MetricFu
2
+
3
+ class Stats < Generator
4
+
5
+ def emit
6
+ `rake stats > #{metric_directory + '/stats.txt'}`
7
+ end
8
+
9
+ def analyze
10
+ output = File.open(metric_directory + '/stats.txt').read
11
+ lines = remove_noise(output)
12
+
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 =~ /^\s*[C|]/ }
30
+ lines.shift
31
+ lines
32
+ end
33
+
34
+ def set_global_stats(totals)
35
+ totals = totals.split(" ").find_all {|el| ! el.empty? }
36
+ @stats[:codeLOC] = totals[0].match(/\d.*/)[0].to_i
37
+ @stats[:testLOC] = totals[1].match(/\d.*/)[0].to_i
38
+ @stats[:code_to_test_ratio] = totals[2].match(/1\:(\d.*)/)[1].to_f
39
+ end
40
+
41
+ def set_granular_stats(lines)
42
+ @stats[:lines] = lines.map do |line|
43
+ elements = line.split("|")
44
+ elements.map! {|el| el.strip }
45
+ elements = elements.find_all {|el| ! el.empty? }
46
+ info_line = {}
47
+ info_line[:name] = elements.shift
48
+ elements.map! {|el| el.to_i }
49
+ [:lines, :loc, :classes, :methods,
50
+ :methods_per_class, :loc_per_method].each do |sym|
51
+ info_line[sym] = elements.shift
52
+ end
53
+ info_line
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,113 @@
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
+
87
+ class StatsBluffGrapher < StatsGrapher
88
+ def graph!
89
+ content = <<-EOS
90
+ #{BLUFF_DEFAULT_OPTIONS}
91
+ g.title = 'Stats: LOC & LOT';
92
+ g.data('LOC', [#{@loc_counts.join(',')}]);
93
+ g.data('LOT', [#{@lot_counts.join(',')}])
94
+ g.labels = #{@labels.to_json};
95
+ g.draw();
96
+ EOS
97
+ File.open(File.join(MetricFu.output_directory, 'stats.js'), 'w') {|f| f << content }
98
+ end
99
+ end
100
+
101
+ class RailsBestPracticesBluffGrapher < RailsBestPracticesGrapher
102
+ def graph!
103
+ content = <<-EOS
104
+ #{BLUFF_DEFAULT_OPTIONS}
105
+ g.title = 'Rails Best Practices: design problems';
106
+ g.data('rails_best_practices', [#{@rails_best_practices_count.join(',')}]);
107
+ g.labels = #{@labels.to_json};
108
+ g.draw();
109
+ EOS
110
+ File.open(File.join(MetricFu.output_directory, 'rails_best_practices.js'), 'w') {|f| f << content }
111
+ end
112
+ end
113
+ end