devver-metric_fu 1.3.3

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 (90) hide show
  1. data/HISTORY +182 -0
  2. data/MIT-LICENSE +22 -0
  3. data/Manifest.txt +25 -0
  4. data/README.textile +27 -0
  5. data/Rakefile +15 -0
  6. data/TODO +9 -0
  7. data/lib/base/base_template.rb +145 -0
  8. data/lib/base/configuration.rb +182 -0
  9. data/lib/base/generator.rb +167 -0
  10. data/lib/base/graph.rb +39 -0
  11. data/lib/base/md5_tracker.rb +52 -0
  12. data/lib/base/report.rb +100 -0
  13. data/lib/generators/churn.rb +34 -0
  14. data/lib/generators/flay.rb +35 -0
  15. data/lib/generators/flog.rb +167 -0
  16. data/lib/generators/rails_best_practices.rb +31 -0
  17. data/lib/generators/rcov.rb +82 -0
  18. data/lib/generators/reek.rb +64 -0
  19. data/lib/generators/roodi.rb +33 -0
  20. data/lib/generators/saikuro.rb +221 -0
  21. data/lib/generators/stats.rb +59 -0
  22. data/lib/graphs/engines/bluff.rb +99 -0
  23. data/lib/graphs/engines/gchart.rb +118 -0
  24. data/lib/graphs/flay_grapher.rb +20 -0
  25. data/lib/graphs/flog_grapher.rb +40 -0
  26. data/lib/graphs/grapher.rb +11 -0
  27. data/lib/graphs/rails_best_practices_grapher.rb +20 -0
  28. data/lib/graphs/rcov_grapher.rb +20 -0
  29. data/lib/graphs/reek_grapher.rb +32 -0
  30. data/lib/graphs/roodi_grapher.rb +20 -0
  31. data/lib/metric_fu.rb +31 -0
  32. data/lib/templates/awesome/awesome_template.rb +37 -0
  33. data/lib/templates/awesome/churn.html.erb +58 -0
  34. data/lib/templates/awesome/css/buttons.css +82 -0
  35. data/lib/templates/awesome/css/default.css +91 -0
  36. data/lib/templates/awesome/css/integrity.css +335 -0
  37. data/lib/templates/awesome/css/reset.css +7 -0
  38. data/lib/templates/awesome/flay.html.erb +34 -0
  39. data/lib/templates/awesome/flog.html.erb +53 -0
  40. data/lib/templates/awesome/index.html.erb +31 -0
  41. data/lib/templates/awesome/layout.html.erb +30 -0
  42. data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
  43. data/lib/templates/awesome/rcov.html.erb +42 -0
  44. data/lib/templates/awesome/reek.html.erb +40 -0
  45. data/lib/templates/awesome/roodi.html.erb +27 -0
  46. data/lib/templates/awesome/saikuro.html.erb +71 -0
  47. data/lib/templates/awesome/stats.html.erb +41 -0
  48. data/lib/templates/javascripts/bluff-min.js +1 -0
  49. data/lib/templates/javascripts/excanvas.js +35 -0
  50. data/lib/templates/javascripts/js-class.js +1 -0
  51. data/lib/templates/standard/churn.html.erb +31 -0
  52. data/lib/templates/standard/default.css +64 -0
  53. data/lib/templates/standard/flay.html.erb +34 -0
  54. data/lib/templates/standard/flog.html.erb +53 -0
  55. data/lib/templates/standard/index.html.erb +41 -0
  56. data/lib/templates/standard/rails_best_practices.html.erb +29 -0
  57. data/lib/templates/standard/rcov.html.erb +43 -0
  58. data/lib/templates/standard/reek.html.erb +42 -0
  59. data/lib/templates/standard/roodi.html.erb +29 -0
  60. data/lib/templates/standard/saikuro.html.erb +84 -0
  61. data/lib/templates/standard/standard_template.rb +26 -0
  62. data/lib/templates/standard/stats.html.erb +55 -0
  63. data/spec/base/base_template_spec.rb +161 -0
  64. data/spec/base/configuration_spec.rb +270 -0
  65. data/spec/base/generator_spec.rb +244 -0
  66. data/spec/base/graph_spec.rb +24 -0
  67. data/spec/base/md5_tracker_spec.rb +57 -0
  68. data/spec/base/report_spec.rb +139 -0
  69. data/spec/generators/churn_spec.rb +43 -0
  70. data/spec/generators/flay_spec.rb +110 -0
  71. data/spec/generators/flog_spec.rb +238 -0
  72. data/spec/generators/reek_spec.rb +125 -0
  73. data/spec/generators/saikuro_spec.rb +58 -0
  74. data/spec/generators/stats_spec.rb +74 -0
  75. data/spec/graphs/engines/bluff_spec.rb +18 -0
  76. data/spec/graphs/engines/gchart_spec.rb +108 -0
  77. data/spec/graphs/flay_grapher_spec.rb +37 -0
  78. data/spec/graphs/flog_grapher_spec.rb +45 -0
  79. data/spec/graphs/rcov_grapher_spec.rb +37 -0
  80. data/spec/graphs/reek_grapher_spec.rb +46 -0
  81. data/spec/graphs/roodi_grapher_spec.rb +37 -0
  82. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
  83. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
  84. data/spec/resources/saikuro/index_cyclo.html +155 -0
  85. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
  86. data/spec/resources/yml/20090630.yml +7844 -0
  87. data/spec/spec.opts +6 -0
  88. data/spec/spec_helper.rb +7 -0
  89. data/tasks/metric_fu.rake +22 -0
  90. metadata +253 -0
@@ -0,0 +1,31 @@
1
+ module MetricFu
2
+ class RailsBestPractices < Generator
3
+
4
+ def self.verify_dependencies!
5
+ `rails_best_practices --help`
6
+ raise 'sudo gem install rails_best_practices # if you want the rails_best_practices tasks' unless $?.success?
7
+ end
8
+
9
+
10
+ def emit
11
+ @output = `rails_best_practices .`
12
+ end
13
+
14
+ def analyze
15
+ @matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
16
+ total = @matches.pop
17
+ 2.times { @matches.pop } # ignore wiki link
18
+ @matches.reject! {|array| array.empty? }
19
+ @matches.map! do |match|
20
+ file, line = match[0].split(':')
21
+ problem = match[1]
22
+ {:file => file, :line => line, :problem => problem}
23
+ end
24
+ @rails_best_practices_results = {:total => total, :problems => @matches}
25
+ end
26
+
27
+ def to_h
28
+ {:rails_best_practices => @rails_best_practices_results}
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,82 @@
1
+ require 'enumerator'
2
+
3
+ module MetricFu
4
+
5
+ class Rcov < Generator
6
+ NEW_FILE_MARKER = ("=" * 80) + "\n"
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
+ FileUtils.rm_rf(MetricFu::Rcov.metric_directory, :verbose => false)
23
+ Dir.mkdir(MetricFu::Rcov.metric_directory)
24
+ test_files = FileList[*MetricFu.rcov[:test_files]].join(' ')
25
+ rcov_opts = MetricFu.rcov[:rcov_opts].join(' ')
26
+ output = ">> #{MetricFu::Rcov.metric_directory}/rcov.txt"
27
+ puts "** Running the specs/tests in the [#{MetricFu.rcov[:environment]}] environment"
28
+ `RAILS_ENV=#{MetricFu.rcov[:environment]} rcov #{test_files} #{rcov_opts} #{output}`
29
+ end
30
+
31
+
32
+ def analyze
33
+ output = File.open(MetricFu::Rcov.metric_directory + '/rcov.txt').read
34
+ output = output.split(NEW_FILE_MARKER)
35
+
36
+ output.shift # Throw away the first entry - it's the execution time etc.
37
+
38
+ files = assemble_files(output)
39
+
40
+ @global_total_lines = 0
41
+ @global_total_lines_run = 0
42
+
43
+ @rcov = add_coverage_percentage(files)
44
+ end
45
+
46
+ def to_h
47
+ global_percent_run = ((@global_total_lines_run.to_f / @global_total_lines.to_f) * 100)
48
+ {:rcov => @rcov.merge({:global_percent_run => round_to_tenths(global_percent_run) })}
49
+ end
50
+
51
+ private
52
+
53
+ def assemble_files(output)
54
+ files = {}
55
+ output.each_slice(2) {|out| files[out.first.strip] = out.last}
56
+ files.each_pair {|fname, content| files[fname] = content.split("\n") }
57
+ files.each_pair do |fname, content|
58
+ content.map! do |raw_line|
59
+ if raw_line.match(/^!!/)
60
+ line = Line.new(raw_line.gsub('!!', ' '), false).to_h
61
+ else
62
+ line = Line.new(raw_line, true).to_h
63
+ end
64
+ end
65
+ content.reject! {|line| line[:content].blank? }
66
+ files[fname] = {:lines => content}
67
+ end
68
+ files
69
+ end
70
+
71
+ def add_coverage_percentage(files)
72
+ files.each_pair do |fname, content|
73
+ lines = content[:lines]
74
+ @global_total_lines_run += lines_run = lines.find_all {|line| line[:was_run] == true }.length
75
+ @global_total_lines += total_lines = lines.length
76
+ percent_run = ((lines_run.to_f / total_lines.to_f) * 100).round
77
+ files[fname][:percent_run] = percent_run
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,64 @@
1
+ module MetricFu
2
+
3
+ class Reek < Generator
4
+ REEK_REGEX = /^(\S+) (.*) \((.*)\)$/
5
+
6
+ def self.verify_dependencies!
7
+ `reek --help`
8
+ raise 'sudo gem install reek # if you want the reek tasks' unless $?.success?
9
+ end
10
+
11
+ def emit
12
+ files_to_reek = MetricFu.reek[:dirs_to_reek].map{|dir| Dir[File.join(dir, "**/*.rb")] }
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
41
+ end
42
+
43
+ def analyze
44
+ @matches = @output.chomp.split("\n\n").map{|m| m.split("\n") }
45
+ @matches = @matches.map do |match|
46
+ file_path = match.shift.split('--').first
47
+ file_path = file_path.gsub('"', ' ').strip
48
+ code_smells = match.map do |smell|
49
+ match_object = smell.match(REEK_REGEX)
50
+ next unless match_object
51
+ {:method => match_object[1].strip,
52
+ :message => match_object[2].strip,
53
+ :type => match_object[3].strip}
54
+ end.compact
55
+ {:file_path => file_path, :code_smells => code_smells}
56
+ end
57
+ end
58
+
59
+ def to_h
60
+ {:reek => {:matches => @matches}}
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ module MetricFu
2
+ class Roodi < Generator
3
+
4
+ def self.verify_dependencies!
5
+ `roodi --help`
6
+ raise 'sudo gem install roodi # if you want the roodi tasks' unless $?.success?
7
+ end
8
+
9
+
10
+ def emit
11
+ files_to_analyze = MetricFu.roodi[:dirs_to_roodi].map{|dir| Dir[File.join(dir, "**/*.rb")] }
12
+ files = remove_excluded_files(files_to_analyze.flatten)
13
+ config = MetricFu.roodi[:roodi_config] ? "-config=#{MetricFu.roodi[:roodi_config]}" : ""
14
+ @output = `roodi #{config} #{files.join(" ")}`
15
+ end
16
+
17
+ def analyze
18
+ @matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
19
+ total = @matches.pop
20
+ @matches.reject! {|array| array.empty? }
21
+ @matches.map! do |match|
22
+ file, line = match[0].split(':')
23
+ problem = match[1]
24
+ {:file => file, :line => line, :problem => problem}
25
+ end
26
+ @roodi_results = {:total => total, :problems => @matches}
27
+ end
28
+
29
+ def to_h
30
+ {:roodi => @roodi_results}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,221 @@
1
+ module MetricFu
2
+
3
+ class Saikuro < Generator
4
+
5
+ def emit
6
+ MetricFu.saikuro[:input_directory] = format_directories
7
+
8
+ options_string = MetricFu.saikuro.inject("") do |options, option|
9
+ options + "--#{option.join(' ')} "
10
+ end
11
+
12
+ sh %{saikuro #{options_string}} do |ok, response|
13
+ unless ok
14
+ puts "Saikuro failed with exit status: #{response.exitstatus}"
15
+ exit 1
16
+ end
17
+ end
18
+ end
19
+
20
+ def format_directories
21
+ dirs = MetricFu.saikuro[:input_directory].join(" | ")
22
+ "\"#{dirs}\""
23
+ end
24
+
25
+ def analyze
26
+ @files = sort_files(assemble_files)
27
+ @classes = sort_classes(assemble_classes(@files))
28
+ @meths = sort_methods(assemble_methods(@files))
29
+ end
30
+
31
+ def to_h
32
+ files = @files.map do |file|
33
+ my_file = file.to_h
34
+ my_file[:filename] = file.filename
35
+ my_file
36
+ end
37
+ {:saikuro => {:files => files,
38
+ :classes => @classes.map {|c| c.to_h},
39
+ :methods => @meths.map {|m| m.to_h}
40
+ }
41
+ }
42
+ end
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
89
+ end
90
+
91
+ end
92
+
93
+ class Saikuro::SFile
94
+
95
+ attr_reader :elements
96
+
97
+ def initialize(path)
98
+ @path = path
99
+ @file_handle = File.open(@path, "r")
100
+ @elements = []
101
+ get_elements
102
+ end
103
+
104
+ def self.is_valid_text_file?(path)
105
+ File.open(path, "r") do |f|
106
+ if f.eof? || !f.readline.match(/--/)
107
+ return false
108
+ else
109
+ return true
110
+ end
111
+ end
112
+ end
113
+
114
+ def filename
115
+ File.basename(@path, '_cyclo.html')
116
+ end
117
+
118
+ def to_h
119
+ merge_classes
120
+ {:classes => @elements}
121
+ end
122
+
123
+ def get_elements
124
+ begin
125
+ while (line = @file_handle.readline) do
126
+ return [] if line.nil? || line !~ /\S/
127
+ element ||= nil
128
+ if line.match /START/
129
+ unless element.nil?
130
+ @elements << element
131
+ element = nil
132
+ end
133
+ line = @file_handle.readline
134
+ element = Saikuro::ParsingElement.new(line)
135
+ elsif line.match /END/
136
+ @elements << element if element
137
+ element = nil
138
+ else
139
+ element << line if element
140
+ end
141
+ end
142
+ rescue EOFError
143
+ nil
144
+ end
145
+ end
146
+
147
+
148
+ def merge_classes
149
+ new_elements = []
150
+ get_class_names.each do |target_class|
151
+ elements = @elements.find_all {|el| el.name == target_class }
152
+ complexity = 0
153
+ lines = 0
154
+ defns = []
155
+ elements.each do |el|
156
+ complexity += el.complexity.to_i
157
+ lines += el.lines.to_i
158
+ defns << el.defs
159
+ end
160
+
161
+ new_element = {:class_name => target_class,
162
+ :complexity => complexity,
163
+ :lines => lines,
164
+ :methods => defns.flatten.map {|d| d.to_h}}
165
+ new_element[:methods] = new_element[:methods].
166
+ sort_by {|x| x[:complexity] }.
167
+ reverse
168
+
169
+ new_elements << new_element
170
+ end
171
+ @elements = new_elements if new_elements
172
+ end
173
+
174
+ def get_class_names
175
+ class_names = []
176
+ @elements.each do |element|
177
+ unless class_names.include?(element.name)
178
+ class_names << element.name
179
+ end
180
+ end
181
+ class_names
182
+ end
183
+
184
+ end
185
+
186
+ class Saikuro::ParsingElement
187
+ TYPE_REGEX=/Type:(.*) Name/
188
+ NAME_REGEX=/Name:(.*) Complexity/
189
+ COMPLEXITY_REGEX=/Complexity:(.*) Lines/
190
+ LINES_REGEX=/Lines:(.*)/
191
+
192
+ attr_reader :complexity, :lines, :defs, :element_type
193
+ attr_accessor :name
194
+
195
+ def initialize(line)
196
+ @line = line
197
+ @element_type = line.match(TYPE_REGEX)[1].strip
198
+ @name = line.match(NAME_REGEX)[1].strip
199
+ @complexity = line.match(COMPLEXITY_REGEX)[1].strip
200
+ @lines = line.match(LINES_REGEX)[1].strip
201
+ @defs = []
202
+ end
203
+
204
+ def <<(line)
205
+ @defs << Saikuro::ParsingElement.new(line)
206
+ end
207
+
208
+ def to_h
209
+ base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
210
+ unless @defs.empty?
211
+ defs = @defs.map do |my_def|
212
+ my_def = my_def.to_h
213
+ my_def.delete(:defs)
214
+ my_def
215
+ end
216
+ base[:defs] = defs
217
+ end
218
+ return base
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,59 @@
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[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? }
37
+ @stats[:codeLOC] = totals[0].match(/\d.*/)[0].to_i
38
+ @stats[:testLOC] = totals[1].match(/\d.*/)[0].to_i
39
+ @stats[:code_to_test_ratio] = totals[2].match(/1\:(\d.*)/)[1].to_f
40
+ end
41
+
42
+ def set_granular_stats(lines)
43
+ @stats[:lines] = lines.map do |line|
44
+ elements = line.split("|")
45
+ elements.map! {|el| el.strip }
46
+ elements = elements.find_all {|el| ! el.empty? }
47
+ info_line = {}
48
+ info_line[:name] = elements.shift
49
+ elements.map! {|el| el.to_i }
50
+ [:lines, :loc, :classes, :methods,
51
+ :methods_per_class, :loc_per_method].each do |sym|
52
+ info_line[sym] = elements.shift
53
+ end
54
+ info_line
55
+ end
56
+ end
57
+
58
+ end
59
+ end