cdd-metric_fu 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 (112) hide show
  1. data/.document +7 -0
  2. data/.gitignore +24 -0
  3. data/HISTORY +182 -0
  4. data/MIT-LICENSE +22 -0
  5. data/Manifest.txt +25 -0
  6. data/README.rdoc +20 -0
  7. data/Rakefile +59 -0
  8. data/TODO +9 -0
  9. data/VERSION +1 -0
  10. data/cdd-metric_fu.gemspec +200 -0
  11. data/home_page/back_all.jpg +0 -0
  12. data/home_page/churn.gif +0 -0
  13. data/home_page/flay.gif +0 -0
  14. data/home_page/flog.gif +0 -0
  15. data/home_page/footer.gif +0 -0
  16. data/home_page/header.jpg +0 -0
  17. data/home_page/img09.gif +0 -0
  18. data/home_page/index.html +277 -0
  19. data/home_page/rcov.gif +0 -0
  20. data/home_page/reek.gif +0 -0
  21. data/home_page/roodi.gif +0 -0
  22. data/home_page/saikuro.gif +0 -0
  23. data/home_page/stats.gif +0 -0
  24. data/home_page/styles.css +245 -0
  25. data/home_page/title.gif +0 -0
  26. data/home_page/title_back.gif +0 -0
  27. data/lib/base/base_template.rb +145 -0
  28. data/lib/base/configuration.rb +188 -0
  29. data/lib/base/generator.rb +167 -0
  30. data/lib/base/graph.rb +47 -0
  31. data/lib/base/md5_tracker.rb +52 -0
  32. data/lib/base/report.rb +100 -0
  33. data/lib/generators/churn.rb +34 -0
  34. data/lib/generators/flay.rb +35 -0
  35. data/lib/generators/flog.rb +172 -0
  36. data/lib/generators/rcov.rb +82 -0
  37. data/lib/generators/reek.rb +64 -0
  38. data/lib/generators/roodi.rb +33 -0
  39. data/lib/generators/saikuro.rb +221 -0
  40. data/lib/generators/stats.rb +59 -0
  41. data/lib/graphs/engines/bluff.rb +101 -0
  42. data/lib/graphs/engines/gchart.rb +120 -0
  43. data/lib/graphs/flay_grapher.rb +19 -0
  44. data/lib/graphs/flog_grapher.rb +39 -0
  45. data/lib/graphs/grapher.rb +18 -0
  46. data/lib/graphs/rails_best_practices_grapher.rb +24 -0
  47. data/lib/graphs/rcov_grapher.rb +19 -0
  48. data/lib/graphs/reek_grapher.rb +31 -0
  49. data/lib/graphs/roodi_grapher.rb +19 -0
  50. data/lib/graphs/stats_grapher.rb +22 -0
  51. data/lib/metric_fu.rb +32 -0
  52. data/lib/metric_fu/railtie.rb +10 -0
  53. data/lib/tasks/metric_fu.rake +22 -0
  54. data/lib/templates/awesome/awesome_template.rb +37 -0
  55. data/lib/templates/awesome/churn.html.erb +58 -0
  56. data/lib/templates/awesome/css/buttons.css +82 -0
  57. data/lib/templates/awesome/css/default.css +91 -0
  58. data/lib/templates/awesome/css/integrity.css +335 -0
  59. data/lib/templates/awesome/css/reset.css +7 -0
  60. data/lib/templates/awesome/flay.html.erb +34 -0
  61. data/lib/templates/awesome/flog.html.erb +53 -0
  62. data/lib/templates/awesome/index.html.erb +31 -0
  63. data/lib/templates/awesome/layout.html.erb +30 -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 +53 -0
  76. data/lib/templates/standard/index.html.erb +38 -0
  77. data/lib/templates/standard/rcov.html.erb +43 -0
  78. data/lib/templates/standard/reek.html.erb +42 -0
  79. data/lib/templates/standard/roodi.html.erb +29 -0
  80. data/lib/templates/standard/saikuro.html.erb +84 -0
  81. data/lib/templates/standard/standard_template.rb +26 -0
  82. data/lib/templates/standard/stats.html.erb +55 -0
  83. data/spec/base/base_template_spec.rb +161 -0
  84. data/spec/base/configuration_spec.rb +274 -0
  85. data/spec/base/generator_spec.rb +244 -0
  86. data/spec/base/graph_spec.rb +32 -0
  87. data/spec/base/md5_tracker_spec.rb +57 -0
  88. data/spec/base/report_spec.rb +139 -0
  89. data/spec/generators/churn_spec.rb +43 -0
  90. data/spec/generators/flay_spec.rb +110 -0
  91. data/spec/generators/flog_spec.rb +262 -0
  92. data/spec/generators/rcov_spec.rb +159 -0
  93. data/spec/generators/reek_spec.rb +125 -0
  94. data/spec/generators/saikuro_spec.rb +58 -0
  95. data/spec/generators/stats_spec.rb +74 -0
  96. data/spec/graphs/engines/bluff_spec.rb +17 -0
  97. data/spec/graphs/engines/gchart_spec.rb +109 -0
  98. data/spec/graphs/flay_grapher_spec.rb +37 -0
  99. data/spec/graphs/flog_grapher_spec.rb +45 -0
  100. data/spec/graphs/grapher_spec.rb +29 -0
  101. data/spec/graphs/rcov_grapher_spec.rb +37 -0
  102. data/spec/graphs/reek_grapher_spec.rb +47 -0
  103. data/spec/graphs/roodi_grapher_spec.rb +37 -0
  104. data/spec/graphs/stats_grapher_spec.rb +44 -0
  105. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
  106. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
  107. data/spec/resources/saikuro/index_cyclo.html +155 -0
  108. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
  109. data/spec/resources/yml/20090630.yml +7913 -0
  110. data/spec/spec.opts +6 -0
  111. data/spec/spec_helper.rb +7 -0
  112. metadata +285 -0
@@ -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
+ options_string = MetricFu.saikuro.inject("") do |options, option|
7
+ options + "--#{option.join(' ')} " unless option == :input_directory
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
+ my_file[:filename] = file.filename
36
+ my_file
37
+ end
38
+ {:saikuro => {:files => files,
39
+ :classes => @classes.map {|c| c.to_h},
40
+ :methods => @meths.map {|m| m.to_h}
41
+ }
42
+ }
43
+ end
44
+
45
+ private
46
+ def sort_methods(methods)
47
+ methods.sort_by {|method| method.complexity.to_i}.reverse
48
+ end
49
+
50
+ def assemble_methods(files)
51
+ methods = []
52
+ files.each do |file|
53
+ file.elements.each do |element|
54
+ element.defs.each do |defn|
55
+ defn.name = "#{element.name}##{defn.name}"
56
+ methods << defn
57
+ end
58
+ end
59
+ end
60
+ methods
61
+ end
62
+
63
+ def sort_classes(classes)
64
+ classes.sort_by {|k| k.complexity.to_i}.reverse
65
+ end
66
+
67
+ def assemble_classes(files)
68
+ files.map {|f| f.elements}.flatten
69
+ end
70
+
71
+ def sort_files(files)
72
+ files.sort_by do |file|
73
+ file.elements.
74
+ max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
75
+ complexity.to_i
76
+ end.reverse
77
+ end
78
+
79
+ def assemble_files
80
+ files = []
81
+ Dir.glob("#{metric_directory}/**/*.html").each do |path|
82
+ if Saikuro::SFile.is_valid_text_file?(path)
83
+ file = Saikuro::SFile.new(path)
84
+ if file
85
+ files << file
86
+ end
87
+ end
88
+ end
89
+ files
90
+ end
91
+
92
+ end
93
+
94
+ class Saikuro::SFile
95
+
96
+ attr_reader :elements
97
+
98
+ def initialize(path)
99
+ @path = path
100
+ set_elements
101
+ end
102
+
103
+ def self.is_valid_text_file?(path)
104
+ File.open(path, "r") do |f|
105
+ if f.eof? || !f.readline.match(/--/)
106
+ return false
107
+ else
108
+ return true
109
+ end
110
+ end
111
+ end
112
+
113
+ def filename
114
+ File.basename(@path, '_cyclo.html')
115
+ end
116
+
117
+ def to_h
118
+ merge_classes
119
+ {:classes => @elements}
120
+ end
121
+
122
+ def set_elements
123
+ @elements = []
124
+ File.open(@path, "r") do |file|
125
+ while (line = file.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.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
+ end
143
+ rescue EOFError
144
+ nil
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
@@ -0,0 +1,101 @@
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
+ end