rferraz-metric_fu 2.1.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 (120) hide show
  1. data/HISTORY +237 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README +29 -0
  4. data/Rakefile +18 -0
  5. data/TODO +6 -0
  6. data/lib/base/base_template.rb +172 -0
  7. data/lib/base/churn_analyzer.rb +38 -0
  8. data/lib/base/code_issue.rb +97 -0
  9. data/lib/base/configuration.rb +199 -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/line_numbers.rb +74 -0
  15. data/lib/base/location.rb +85 -0
  16. data/lib/base/md5_tracker.rb +52 -0
  17. data/lib/base/metric_analyzer.rb +404 -0
  18. data/lib/base/ranking.rb +34 -0
  19. data/lib/base/rcov_analyzer.rb +43 -0
  20. data/lib/base/reek_analyzer.rb +163 -0
  21. data/lib/base/report.rb +108 -0
  22. data/lib/base/roodi_analyzer.rb +37 -0
  23. data/lib/base/saikuro_analyzer.rb +48 -0
  24. data/lib/base/scoring_strategies.rb +29 -0
  25. data/lib/base/stats_analyzer.rb +37 -0
  26. data/lib/base/table.rb +102 -0
  27. data/lib/generators/churn.rb +28 -0
  28. data/lib/generators/flay.rb +31 -0
  29. data/lib/generators/flog.rb +111 -0
  30. data/lib/generators/hotspots.rb +52 -0
  31. data/lib/generators/rails_best_practices.rb +53 -0
  32. data/lib/generators/rcov.rb +122 -0
  33. data/lib/generators/reek.rb +81 -0
  34. data/lib/generators/roodi.rb +35 -0
  35. data/lib/generators/saikuro.rb +256 -0
  36. data/lib/generators/stats.rb +58 -0
  37. data/lib/graphs/engines/bluff.rb +113 -0
  38. data/lib/graphs/engines/gchart.rb +157 -0
  39. data/lib/graphs/flay_grapher.rb +18 -0
  40. data/lib/graphs/flog_grapher.rb +57 -0
  41. data/lib/graphs/grapher.rb +11 -0
  42. data/lib/graphs/rails_best_practices_grapher.rb +19 -0
  43. data/lib/graphs/rcov_grapher.rb +18 -0
  44. data/lib/graphs/reek_grapher.rb +30 -0
  45. data/lib/graphs/roodi_grapher.rb +18 -0
  46. data/lib/graphs/stats_grapher.rb +20 -0
  47. data/lib/metric_fu.rb +40 -0
  48. data/lib/templates/awesome/awesome_template.rb +73 -0
  49. data/lib/templates/awesome/churn.html.erb +58 -0
  50. data/lib/templates/awesome/css/buttons.css +82 -0
  51. data/lib/templates/awesome/css/default.css +91 -0
  52. data/lib/templates/awesome/css/integrity.css +334 -0
  53. data/lib/templates/awesome/css/reset.css +7 -0
  54. data/lib/templates/awesome/css/syntax.css +19 -0
  55. data/lib/templates/awesome/flay.html.erb +34 -0
  56. data/lib/templates/awesome/flog.html.erb +55 -0
  57. data/lib/templates/awesome/hotspots.html.erb +62 -0
  58. data/lib/templates/awesome/index.html.erb +34 -0
  59. data/lib/templates/awesome/layout.html.erb +30 -0
  60. data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
  61. data/lib/templates/awesome/rcov.html.erb +42 -0
  62. data/lib/templates/awesome/reek.html.erb +40 -0
  63. data/lib/templates/awesome/roodi.html.erb +27 -0
  64. data/lib/templates/awesome/saikuro.html.erb +71 -0
  65. data/lib/templates/awesome/stats.html.erb +51 -0
  66. data/lib/templates/javascripts/bluff-min.js +1 -0
  67. data/lib/templates/javascripts/excanvas.js +35 -0
  68. data/lib/templates/javascripts/js-class.js +1 -0
  69. data/lib/templates/standard/churn.html.erb +31 -0
  70. data/lib/templates/standard/default.css +64 -0
  71. data/lib/templates/standard/flay.html.erb +34 -0
  72. data/lib/templates/standard/flog.html.erb +57 -0
  73. data/lib/templates/standard/hotspots.html.erb +54 -0
  74. data/lib/templates/standard/index.html.erb +41 -0
  75. data/lib/templates/standard/rails_best_practices.html.erb +29 -0
  76. data/lib/templates/standard/rcov.html.erb +43 -0
  77. data/lib/templates/standard/reek.html.erb +42 -0
  78. data/lib/templates/standard/roodi.html.erb +29 -0
  79. data/lib/templates/standard/saikuro.html.erb +84 -0
  80. data/lib/templates/standard/standard_template.rb +26 -0
  81. data/lib/templates/standard/stats.html.erb +55 -0
  82. data/spec/base/base_template_spec.rb +177 -0
  83. data/spec/base/configuration_spec.rb +276 -0
  84. data/spec/base/generator_spec.rb +223 -0
  85. data/spec/base/graph_spec.rb +61 -0
  86. data/spec/base/line_numbers_spec.rb +62 -0
  87. data/spec/base/md5_tracker_spec.rb +57 -0
  88. data/spec/base/report_spec.rb +146 -0
  89. data/spec/generators/churn_spec.rb +41 -0
  90. data/spec/generators/flay_spec.rb +105 -0
  91. data/spec/generators/flog_spec.rb +70 -0
  92. data/spec/generators/rails_best_practices_spec.rb +52 -0
  93. data/spec/generators/rcov_spec.rb +180 -0
  94. data/spec/generators/reek_spec.rb +134 -0
  95. data/spec/generators/roodi_spec.rb +24 -0
  96. data/spec/generators/saikuro_spec.rb +74 -0
  97. data/spec/generators/stats_spec.rb +74 -0
  98. data/spec/graphs/engines/bluff_spec.rb +19 -0
  99. data/spec/graphs/engines/gchart_spec.rb +156 -0
  100. data/spec/graphs/flay_grapher_spec.rb +56 -0
  101. data/spec/graphs/flog_grapher_spec.rb +108 -0
  102. data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
  103. data/spec/graphs/rcov_grapher_spec.rb +56 -0
  104. data/spec/graphs/reek_grapher_spec.rb +65 -0
  105. data/spec/graphs/roodi_grapher_spec.rb +56 -0
  106. data/spec/graphs/stats_grapher_spec.rb +68 -0
  107. data/spec/resources/line_numbers/foo.rb +33 -0
  108. data/spec/resources/line_numbers/module.rb +11 -0
  109. data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
  110. data/spec/resources/line_numbers/two_classes.rb +11 -0
  111. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
  112. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
  113. data/spec/resources/saikuro/index_cyclo.html +155 -0
  114. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
  115. data/spec/resources/yml/20090630.yml +7922 -0
  116. data/spec/resources/yml/metric_missing.yml +1 -0
  117. data/spec/spec.opts +6 -0
  118. data/spec/spec_helper.rb +7 -0
  119. data/tasks/metric_fu.rake +22 -0
  120. metadata +448 -0
@@ -0,0 +1,122 @@
1
+ require 'enumerator'
2
+
3
+ module MetricFu
4
+
5
+ class Rcov < Generator
6
+ NEW_FILE_MARKER = /^={80}$/.freeze
7
+
8
+ class Line
9
+ attr_accessor :content, :was_run
10
+
11
+ def initialize(content, was_run)
12
+ @content = content
13
+ @was_run = was_run
14
+ end
15
+
16
+ def to_h
17
+ {:content => @content, :was_run => @was_run}
18
+ end
19
+ end
20
+
21
+ def emit
22
+ unless MetricFu.rcov[:external]
23
+ FileUtils.rm_rf(MetricFu::Rcov.metric_directory, :verbose => false)
24
+ Dir.mkdir(MetricFu::Rcov.metric_directory)
25
+ test_files = FileList[*MetricFu.rcov[:test_files]].join(' ')
26
+ rcov_opts = MetricFu.rcov[:rcov_opts].join(' ')
27
+ output = ">> #{MetricFu::Rcov.metric_directory}/rcov.txt"
28
+ puts "** Running the specs/tests in the [#{MetricFu.rcov[:environment]}] environment"
29
+ `RAILS_ENV=#{MetricFu.rcov[:environment]} rcov #{test_files} #{rcov_opts} #{output}`
30
+ end
31
+ end
32
+
33
+
34
+ def analyze
35
+ output_file = MetricFu.rcov[:external] ? MetricFu.rcov[:external] : MetricFu::Rcov.metric_directory + '/rcov.txt'
36
+ output = File.open(output_file).read
37
+ output = output.split(NEW_FILE_MARKER)
38
+
39
+ output.shift # Throw away the first entry - it's the execution time etc.
40
+
41
+ files = assemble_files(output)
42
+
43
+ @global_total_lines = 0
44
+ @global_total_lines_run = 0
45
+
46
+ @rcov = add_coverage_percentage(files)
47
+ end
48
+
49
+ def to_h
50
+ global_percent_run = ((@global_total_lines_run.to_f / @global_total_lines.to_f) * 100)
51
+ add_method_data
52
+ {:rcov => @rcov.merge({:global_percent_run => round_to_tenths(global_percent_run) })}
53
+ end
54
+
55
+ private
56
+
57
+ def add_method_data
58
+ @rcov.each_pair do |file_path, info|
59
+ file_contents = ""
60
+ coverage = []
61
+
62
+ info[:lines].each_with_index do |line, index|
63
+ file_contents << "#{line[:content]}\n"
64
+ coverage << line[:was_run]
65
+ end
66
+
67
+ begin
68
+ line_numbers = MetricFu::LineNumbers.new(file_contents)
69
+ rescue StandardError => e
70
+ raise e unless e.message =~ /you shouldn't be able to get here/
71
+ puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Rcov information for this file."
72
+ next
73
+ end
74
+
75
+ method_coverage_map = {}
76
+ coverage.each_with_index do |covered, index|
77
+ line_number = index + 1
78
+ if line_numbers.in_method?(line_number)
79
+ method_name = line_numbers.method_at_line(line_number)
80
+ method_coverage_map[method_name] ||= {}
81
+ method_coverage_map[method_name][:total] ||= 0
82
+ method_coverage_map[method_name][:total] += 1
83
+ method_coverage_map[method_name][:uncovered] ||= 0
84
+ method_coverage_map[method_name][:uncovered] += 1 if !covered
85
+ end
86
+ end
87
+
88
+ @rcov[file_path][:methods] = {}
89
+
90
+ method_coverage_map.each do |method_name, coverage_data|
91
+ @rcov[file_path][:methods][method_name] = (coverage_data[:uncovered] / coverage_data[:total].to_f) * 100.0
92
+ end
93
+
94
+ end
95
+ end
96
+
97
+ def assemble_files(output)
98
+ files = {}
99
+ output.each_slice(2) {|out| files[out.first.strip] = out.last}
100
+ files.each_pair {|fname, content| files[fname] = content.split("\n") }
101
+ files.each_pair do |fname, content|
102
+ content.map! do |raw_line|
103
+ line = Line.new(raw_line[3..-1], !raw_line.match(/^!!/)).to_h
104
+ end
105
+ content.reject! {|line| line[:content].blank? }
106
+ files[fname] = {:lines => content}
107
+ end
108
+ files
109
+ end
110
+
111
+ def add_coverage_percentage(files)
112
+ files.each_pair do |fname, content|
113
+ lines = content[:lines]
114
+ @global_total_lines_run += lines_run = lines.find_all {|line| line[:was_run] == true }.length
115
+ @global_total_lines += total_lines = lines.length
116
+ percent_run = ((lines_run.to_f / total_lines.to_f) * 100).round
117
+ files[fname][:percent_run] = percent_run
118
+ end
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,81 @@
1
+ module MetricFu
2
+
3
+ class Reek < Generator
4
+ REEK_REGEX = /^(\S+) (.*) \((.*)\)$/
5
+
6
+ def emit
7
+ files_to_reek = MetricFu.reek[:dirs_to_reek].map{|dir| Dir[File.join(dir, "**/*.rb")] }
8
+ files = remove_excluded_files(files_to_reek.flatten)
9
+ config_file_param = MetricFu.reek[:config_file_pattern] ? "--config #{MetricFu.reek[:config_file_pattern]}" : ''
10
+ @output = `reek #{config_file_param} #{files.join(" ")}`
11
+ @output = massage_for_reek_12 if reek_12?
12
+ end
13
+
14
+ def reek_12?
15
+ return false if @output.length == 0
16
+ (@output =~ /^"/) != 0
17
+ end
18
+
19
+ def massage_for_reek_12
20
+ section_break = ''
21
+ @output.split("\n").map do |line|
22
+ case line
23
+ when /^ /
24
+ "#{line.gsub(/^ /, '')}\n"
25
+ else
26
+ parts = line.split(" -- ")
27
+ if parts[1].nil?
28
+ "#{line}\n"
29
+ else
30
+ warnings = parts[1].gsub(/ \(.*\):/, ':')
31
+ result = "#{section_break}\"#{parts[0]}\" -- #{warnings}\n"
32
+ section_break = "\n"
33
+ result
34
+ end
35
+ end
36
+ end.join
37
+ end
38
+
39
+ def analyze
40
+ @matches = @output.chomp.split("\n\n").map{|m| m.split("\n") }
41
+ @matches = @matches.map do |match|
42
+ file_path = match.shift.split('--').first
43
+ file_path = file_path.gsub('"', ' ').strip
44
+ code_smells = match.map do |smell|
45
+ match_object = smell.match(REEK_REGEX)
46
+ next unless match_object
47
+ {:method => match_object[1].strip,
48
+ :message => match_object[2].strip,
49
+ :type => match_object[3].strip}
50
+ end.compact
51
+ {:file_path => file_path, :code_smells => code_smells}
52
+ end
53
+ end
54
+
55
+ def to_h
56
+ {:reek => {:matches => @matches}}
57
+ end
58
+
59
+ def per_file_info(out)
60
+ @matches.each do |file_data|
61
+ next if File.extname(file_data[:file_path]) == '.erb'
62
+ begin
63
+ line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:file_path], 'r').read)
64
+ rescue StandardError => e
65
+ raise e unless e.message =~ /you shouldn't be able to get here/
66
+ puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level reek information for this file."
67
+ next
68
+ end
69
+
70
+ out[file_data[:file_path]] ||= {}
71
+ file_data[:code_smells].each do |smell_data|
72
+ line = line_numbers.start_line_for_method(smell_data[:method])
73
+ out[file_data[:file_path]][line.to_s] ||= []
74
+ out[file_data[:file_path]][line.to_s] << {:type => :reek,
75
+ :description => "#{smell_data[:type]} - #{smell_data[:message]}"}
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end
@@ -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,256 @@
1
+ module MetricFu
2
+
3
+ class Saikuro < Generator
4
+
5
+ def emit
6
+ options_string = MetricFu.saikuro.inject("") do |options, option|
7
+ option[0] == :input_directory ? options : options + "--#{option.join(' ')} "
8
+ end
9
+
10
+ MetricFu.saikuro[:input_directory].each do |input_dir|
11
+ options_string += "--input_directory #{input_dir} "
12
+ end
13
+ sh %{saikuro #{options_string}} do |ok, response|
14
+ unless ok
15
+ puts "Saikuro failed with exit status: #{response.exitstatus}"
16
+ exit 1
17
+ end
18
+ end
19
+ end
20
+
21
+ def format_directories
22
+ dirs = MetricFu.saikuro[:input_directory].join(" | ")
23
+ "\"#{dirs}\""
24
+ end
25
+
26
+ def analyze
27
+ @files = sort_files(assemble_files)
28
+ @classes = sort_classes(assemble_classes(@files))
29
+ @meths = sort_methods(assemble_methods(@files))
30
+ end
31
+
32
+ def to_h
33
+ files = @files.map do |file|
34
+ my_file = file.to_h
35
+
36
+ f = file.filepath
37
+ f.gsub!(%r{^#{metric_directory}/}, '')
38
+ f << "/#{file.filename}"
39
+
40
+ my_file[:filename] = f
41
+ my_file
42
+ end
43
+ @saikuro_data = {:files => files,
44
+ :classes => @classes.map {|c| c.to_h},
45
+ :methods => @meths.map {|m| m.to_h}
46
+ }
47
+ {:saikuro => @saikuro_data}
48
+ end
49
+
50
+ def per_file_info(out)
51
+ @saikuro_data[:files].each do |file_data|
52
+ next if File.extname(file_data[:filename]) == '.erb' || !File.exists?(file_data[:filename])
53
+ begin
54
+ line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:filename], 'r').read)
55
+ rescue StandardError => e
56
+ raise e unless e.message =~ /you shouldn't be able to get here/
57
+ puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Saikuro information for this file."
58
+ next
59
+ end
60
+
61
+ out[file_data[:filename]] ||= {}
62
+ file_data[:classes].each do |class_data|
63
+ class_data[:methods].each do |method_data|
64
+ line = line_numbers.start_line_for_method(method_data[:name])
65
+ out[file_data[:filename]][line.to_s] ||= []
66
+ out[file_data[:filename]][line.to_s] << {:type => :saikuro,
67
+ :description => "Complexity #{method_data[:complexity]}"}
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+ def sort_methods(methods)
75
+ methods.sort_by {|method| method.complexity.to_i}.reverse
76
+ end
77
+
78
+ def assemble_methods(files)
79
+ methods = []
80
+ files.each do |file|
81
+ file.elements.each do |element|
82
+ element.defs.each do |defn|
83
+ defn.name = "#{element.name}##{defn.name}"
84
+ methods << defn
85
+ end
86
+ end
87
+ end
88
+ methods
89
+ end
90
+
91
+ def sort_classes(classes)
92
+ classes.sort_by {|k| k.complexity.to_i}.reverse
93
+ end
94
+
95
+ def assemble_classes(files)
96
+ files.map {|f| f.elements}.flatten
97
+ end
98
+
99
+ def sort_files(files)
100
+ files.sort_by do |file|
101
+ file.elements.
102
+ max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
103
+ complexity.to_i
104
+ end.reverse
105
+ end
106
+
107
+ def assemble_files
108
+ files = []
109
+ Dir.glob("#{metric_directory}/**/*.html").each do |path|
110
+ if Saikuro::SFile.is_valid_text_file?(path)
111
+ file = Saikuro::SFile.new(path)
112
+ if file
113
+ files << file
114
+ end
115
+ end
116
+ end
117
+ files
118
+ end
119
+
120
+ end
121
+
122
+ class Saikuro::SFile
123
+
124
+ attr_reader :elements
125
+
126
+ def initialize(path)
127
+ @path = path
128
+ @file_handle = File.open(@path, "r")
129
+ @elements = []
130
+ get_elements
131
+ ensure
132
+ @file_handle.close if @file_handle
133
+ end
134
+
135
+ def self.is_valid_text_file?(path)
136
+ File.open(path, "r") do |f|
137
+ if f.eof? || !f.readline.match(/--/)
138
+ return false
139
+ else
140
+ return true
141
+ end
142
+ end
143
+ end
144
+
145
+ def filename
146
+ File.basename(@path, '_cyclo.html')
147
+ end
148
+
149
+ def filepath
150
+ File.dirname(@path)
151
+ end
152
+
153
+ def to_h
154
+ merge_classes
155
+ {:classes => @elements}
156
+ end
157
+
158
+ def get_elements
159
+ begin
160
+ while (line = @file_handle.readline) do
161
+ return [] if line.nil? || line !~ /\S/
162
+ element ||= nil
163
+ if line.match /START/
164
+ unless element.nil?
165
+ @elements << element
166
+ element = nil
167
+ end
168
+ line = @file_handle.readline
169
+ element = Saikuro::ParsingElement.new(line)
170
+ elsif line.match /END/
171
+ @elements << element if element
172
+ element = nil
173
+ else
174
+ element << line if element
175
+ end
176
+ end
177
+ rescue EOFError
178
+ nil
179
+ end
180
+ end
181
+
182
+
183
+ def merge_classes
184
+ new_elements = []
185
+ get_class_names.each do |target_class|
186
+ elements = @elements.find_all {|el| el.name == target_class }
187
+ complexity = 0
188
+ lines = 0
189
+ defns = []
190
+ elements.each do |el|
191
+ complexity += el.complexity.to_i
192
+ lines += el.lines.to_i
193
+ defns << el.defs
194
+ end
195
+
196
+ new_element = {:class_name => target_class,
197
+ :complexity => complexity,
198
+ :lines => lines,
199
+ :methods => defns.flatten.map {|d| d.to_h}}
200
+ new_element[:methods] = new_element[:methods].
201
+ sort_by {|x| x[:complexity] }.
202
+ reverse
203
+
204
+ new_elements << new_element
205
+ end
206
+ @elements = new_elements if new_elements
207
+ end
208
+
209
+ def get_class_names
210
+ class_names = []
211
+ @elements.each do |element|
212
+ unless class_names.include?(element.name)
213
+ class_names << element.name
214
+ end
215
+ end
216
+ class_names
217
+ end
218
+
219
+ end
220
+
221
+ class Saikuro::ParsingElement
222
+ TYPE_REGEX=/Type:(.*) Name/
223
+ NAME_REGEX=/Name:(.*) Complexity/
224
+ COMPLEXITY_REGEX=/Complexity:(.*) Lines/
225
+ LINES_REGEX=/Lines:(.*)/
226
+
227
+ attr_reader :complexity, :lines, :defs, :element_type
228
+ attr_accessor :name
229
+
230
+ def initialize(line)
231
+ @line = line
232
+ @element_type = line.match(TYPE_REGEX)[1].strip
233
+ @name = line.match(NAME_REGEX)[1].strip
234
+ @complexity = line.match(COMPLEXITY_REGEX)[1].strip
235
+ @lines = line.match(LINES_REGEX)[1].strip
236
+ @defs = []
237
+ end
238
+
239
+ def <<(line)
240
+ @defs << Saikuro::ParsingElement.new(line)
241
+ end
242
+
243
+ def to_h
244
+ base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
245
+ unless @defs.empty?
246
+ defs = @defs.map do |my_def|
247
+ my_def = my_def.to_h
248
+ my_def.delete(:defs)
249
+ my_def
250
+ end
251
+ base[:defs] = defs
252
+ end
253
+ return base
254
+ end
255
+ end
256
+ 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