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,108 @@
1
+ module MetricFu
2
+
3
+ # MetricFu.report memoizes access to a Report object, that will be
4
+ # used throughout the lifecycle of the MetricFu app.
5
+ def self.report
6
+ @report ||= Report.new
7
+ end
8
+
9
+ # = Report
10
+ #
11
+ # The Report class is responsible two things:
12
+ #
13
+ # It adds information to the yaml report, produced by the system
14
+ # as a whole, for each of the generators used in this test run.
15
+ #
16
+ # It also handles passing the information from each generator used
17
+ # in this test run out to the template class set in
18
+ # MetricFu::Configuration.
19
+ class Report
20
+
21
+ # Renders the result of the report_hash into a yaml serialization
22
+ # ready for writing out to a file.
23
+ #
24
+ # @return YAML
25
+ # A YAML object containing the results of the report generation
26
+ # process
27
+ def to_yaml
28
+ report_hash.to_yaml
29
+ end
30
+
31
+ def per_file_data
32
+ @per_file_data ||= {}
33
+ end
34
+
35
+ def report_hash #:nodoc:
36
+ @report_hash ||= {}
37
+ end
38
+
39
+ # Instantiates a new template class based on the configuration set
40
+ # in MetricFu::Configuration, or through the MetricFu.config block
41
+ # in your rake file (defaults to the included AwesomeTemplate),
42
+ # assigns the report_hash to the report_hash in the template, and
43
+ # tells the template to to write itself out.
44
+ def save_templatized_report
45
+ @template = MetricFu.template_class.new
46
+ @template.report = report_hash
47
+ @template.per_file_data = per_file_data
48
+ @template.write
49
+ end
50
+
51
+ # Adds a hash from a passed report, produced by one of the Generator
52
+ # classes to the aggregate report_hash managed by this hash.
53
+ #
54
+ # @param report_type Hash
55
+ # The hash to add to the aggregate report_hash
56
+ def add(report_type)
57
+ clazz = MetricFu.const_get(report_type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
58
+ inst = clazz.new
59
+
60
+ report_hash.merge!(inst.generate_report)
61
+
62
+ inst.per_file_info(per_file_data) if inst.respond_to?(:per_file_info)
63
+ end
64
+
65
+ # Saves the passed in content to the passed in directory. If
66
+ # a filename is passed in it will be used as the name of the
67
+ # file, otherwise it will default to 'index.html'
68
+ #
69
+ # @param content String
70
+ # A string containing the content (usually html) to be written
71
+ # to the file.
72
+ #
73
+ # @param dir String
74
+ # A dir containing the path to the directory to write the file in.
75
+ #
76
+ # @param file String
77
+ # A filename to save the path as. Defaults to 'index.html'.
78
+ #
79
+ def save_output(content, dir, file='index.html')
80
+ open("#{dir}/#{file}", "w") do |f|
81
+ f.puts content
82
+ end
83
+ end
84
+
85
+ # Checks to discover whether we should try and open the results
86
+ # of the report in the browser on this system. We only try and open
87
+ # in the browser if we're on OS X and we're not running in a
88
+ # CruiseControl.rb environment. See MetricFu.configuration for more
89
+ # details about how we make those guesses.
90
+ #
91
+ # @return Boolean
92
+ # Should we open in the browser or not?
93
+ def open_in_browser?
94
+ MetricFu.configuration.platform.include?('darwin') &&
95
+ ! MetricFu.configuration.is_cruise_control_rb?
96
+ end
97
+
98
+ # Shows 'index.html' from the passed directory in the browser
99
+ # if we're able to open the browser on this platform.
100
+ #
101
+ # @param dir String
102
+ # The directory path where the 'index.html' we want to open is
103
+ # stored
104
+ def show_in_browser(dir)
105
+ system("open #{dir}/index.html") if open_in_browser?
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,37 @@
1
+ class RoodiAnalyzer
2
+ include ScoringStrategies
3
+
4
+ COLUMNS = %w{problems}
5
+
6
+ def columns
7
+ COLUMNS
8
+ end
9
+
10
+ def name
11
+ :roodi
12
+ end
13
+
14
+ def map(row)
15
+ ScoringStrategies.present(row)
16
+ end
17
+
18
+ def reduce(scores)
19
+ ScoringStrategies.sum(scores)
20
+ end
21
+
22
+ def score(metric_ranking, item)
23
+ ScoringStrategies.percentile(metric_ranking, item)
24
+ end
25
+
26
+ def generate_records(data, table)
27
+ return if data==nil
28
+ Array(data[:problems]).each do |problem|
29
+ table << {
30
+ "metric" => name,
31
+ "problems" => problem[:problem],
32
+ "file_path" => problem[:file]
33
+ }
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,48 @@
1
+ class SaikuroAnalyzer
2
+ include ScoringStrategies
3
+
4
+ COLUMNS = %w{lines complexity}
5
+
6
+ def columns
7
+ COLUMNS
8
+ end
9
+
10
+ def name
11
+ :saikuro
12
+ end
13
+
14
+ def map(row)
15
+ row.complexity
16
+ end
17
+
18
+ def reduce(scores)
19
+ ScoringStrategies.average(scores)
20
+ end
21
+
22
+ def score(metric_ranking, item)
23
+ ScoringStrategies.identity(metric_ranking, item)
24
+ end
25
+
26
+ def generate_records(data, table)
27
+ return if data == nil
28
+ data[:files].each do |file|
29
+ file_name = file[:filename]
30
+ file[:classes].each do |klass|
31
+ location = MetricFu::Location.for(klass[:class_name])
32
+ offending_class = location.class_name
33
+ klass[:methods].each do |match|
34
+ offending_method = MetricFu::Location.for(match[:name]).method_name
35
+ table << {
36
+ "metric" => name,
37
+ "lines" => match[:lines],
38
+ "complexity" => match[:complexity],
39
+ "class_name" => offending_class,
40
+ "method_name" => offending_method,
41
+ "file_path" => file_name,
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,29 @@
1
+ module ScoringStrategies
2
+
3
+ def percentile(ranking, item)
4
+ ranking.percentile(item) # per project score percentile
5
+ end
6
+
7
+ def identity(ranking, item)
8
+ ranking[item] # Use the score you got (ex flog score of 20 is not bad even if it is the top one in project)
9
+ end
10
+
11
+ def present(row)
12
+ 1 # If present it's a one, not present it's a zero - For things like Reek that don't have a number
13
+ end
14
+
15
+ def sum(scores)
16
+ scores.inject(0) {|s,x| s+x}
17
+ end
18
+
19
+ def average(scores)
20
+ # remove dependency on statarray
21
+ # scores.to_statarray.mean
22
+ score_length = scores.length
23
+ sum = 0
24
+ sum = scores.inject( nil ) { |sum,x| sum ? sum+x : x }
25
+ (sum.to_f / score_length.to_f)
26
+ end
27
+
28
+ extend self
29
+ end
@@ -0,0 +1,37 @@
1
+ class StatsAnalyzer
2
+
3
+ COLUMNS = %w{stat_name stat_value}
4
+
5
+ def columns
6
+ COLUMNS
7
+ end
8
+
9
+ def name
10
+ :stats
11
+ end
12
+
13
+ def map(row)
14
+ 0
15
+ end
16
+
17
+ def reduce(scores)
18
+ 0
19
+ end
20
+
21
+ def score(metric_ranking, item)
22
+ 0
23
+ end
24
+
25
+ def generate_records(data, table)
26
+ return if data == nil
27
+ data.each do |key, value|
28
+ next if value.kind_of?(Array)
29
+ table << {
30
+ "metric" => name,
31
+ "stat_name" => key,
32
+ "stat_value" => value
33
+ }
34
+ end
35
+ end
36
+
37
+ end
data/lib/base/table.rb ADDED
@@ -0,0 +1,102 @@
1
+ class Table
2
+
3
+ def initialize(opts = {})
4
+ @rows = []
5
+ @columns = opts.fetch(:column_names)
6
+
7
+ @make_index = opts.fetch(:make_index) {true}
8
+ @metric_index = {}
9
+ end
10
+
11
+ def <<(row)
12
+ record = nil
13
+ if row.is_a?(Record) || row.is_a?(CodeIssue)
14
+ record = row
15
+ else
16
+ record = Record.new(row, @columns)
17
+ end
18
+ @rows << record
19
+ updated_key_index(record) if @make_index
20
+ end
21
+
22
+ def each
23
+ @rows.each do |row|
24
+ yield row
25
+ end
26
+ end
27
+
28
+ def size
29
+ length
30
+ end
31
+
32
+ def length
33
+ @rows.length
34
+ end
35
+
36
+ def [](index)
37
+ @rows[index]
38
+ end
39
+
40
+ def column(column_name)
41
+ arr = []
42
+ @rows.each do |row|
43
+ arr << row[column_name]
44
+ end
45
+ arr
46
+ end
47
+
48
+ def group_by_metric
49
+ @metric_index.to_a
50
+ end
51
+
52
+ def rows_with(conditions)
53
+ if optimized_conditions?(conditions)
54
+ optimized_select(conditions)
55
+ else
56
+ slow_select(conditions)
57
+ end
58
+ end
59
+
60
+ def delete_at(index)
61
+ @rows.delete_at(index)
62
+ end
63
+
64
+ def to_a
65
+ @rows
66
+ end
67
+
68
+ def map
69
+ new_table = Table.new(:column_names => @columns)
70
+ @rows.map do |row|
71
+ new_table << (yield row)
72
+ end
73
+ new_table
74
+ end
75
+
76
+ private
77
+
78
+ def optimized_conditions?(conditions)
79
+ conditions.keys.length == 1 && conditions.keys.first.to_sym == :metric
80
+ end
81
+
82
+ def optimized_select(conditions)
83
+ metric = (conditions['metric'] || conditions[:metric]).to_s
84
+ @metric_index[metric].to_a.clone
85
+ end
86
+
87
+ def slow_select(conditions)
88
+ @rows.select do |row|
89
+ conditions.all? do |key, value|
90
+ row.has_key?(key.to_s) && row[key.to_s] == value
91
+ end
92
+ end
93
+ end
94
+
95
+ def updated_key_index(record)
96
+ if record.has_key?('metric')
97
+ @metric_index[record.metric] ||= Table.new(:column_names => @columns, :make_index => false)
98
+ @metric_index[record.metric] << record
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,28 @@
1
+ module MetricFu
2
+
3
+ class Churn < Generator
4
+
5
+ def initialize(options={})
6
+ super
7
+ end
8
+
9
+ def emit
10
+ @output = `churn --yaml`
11
+ yaml_start = @output.index("---")
12
+ @output = @output[yaml_start...@output.length] if yaml_start
13
+ end
14
+
15
+ def analyze
16
+ if @output.match(/Churning requires a subversion or git repo/)
17
+ @churn = [:churn => {}]
18
+ else
19
+ @churn = YAML::load(@output)
20
+ end
21
+ end
22
+
23
+ def to_h
24
+ {:churn => @churn[:churn]}
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,31 @@
1
+ require 'flay'
2
+
3
+ module MetricFu
4
+
5
+ class Flay < Generator
6
+
7
+ def emit
8
+ mimimum_score_parameter = MetricFu.flay[:minimum_score] ? "--mass #{MetricFu.flay[:minimum_score]} " : ""
9
+
10
+ @output = `flay #{mimimum_score_parameter} #{MetricFu.flay[:dirs_to_flay].join(" ")}`
11
+ end
12
+
13
+ def analyze
14
+ @matches = @output.chomp.split("\n\n").map{|m| m.split("\n ") }
15
+ end
16
+
17
+ def to_h
18
+ target = []
19
+ total_score = @matches.shift.first.split('=').last.strip
20
+ @matches.each do |problem|
21
+ reason = problem.shift.strip
22
+ lines_info = problem.map do |full_line|
23
+ name, line = full_line.split(":")
24
+ {:name => name.strip, :line => line.strip}
25
+ end
26
+ target << [:reason => reason, :matches => lines_info]
27
+ end
28
+ {:flay => {:total_score => total_score, :matches => target.flatten}}
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,111 @@
1
+ require 'pathname'
2
+ require 'optparse'
3
+ require 'flog'
4
+
5
+ module MetricFu
6
+
7
+ class Flog < Generator
8
+
9
+ def emit
10
+ files = []
11
+ MetricFu.flog[:dirs_to_flog].each do |directory|
12
+ directory = "." if directory=='./'
13
+ dir_files = Dir.glob("#{directory}/**/*.rb")
14
+ dir_files = remove_excluded_files(dir_files)
15
+ files += dir_files
16
+ end
17
+ options = ::Flog.parse_options ["--all", "--details"]
18
+
19
+ @flogger = ::Flog.new options
20
+ @flogger.flog files
21
+
22
+ rescue LoadError
23
+ if RUBY_PLATFORM =~ /java/
24
+ puts 'running in jruby - flog tasks not available'
25
+ end
26
+ end
27
+
28
+ def analyze
29
+ @method_containers = {}
30
+ @flogger.calls.each do |full_method_name, operators|
31
+ container_name = full_method_name.split('#').first
32
+ path = @flogger.method_locations[full_method_name]
33
+ if @method_containers[container_name]
34
+ @method_containers[container_name].add_method(full_method_name, operators, @flogger.totals[full_method_name], path)
35
+ @method_containers[container_name].add_path(path)
36
+ else
37
+ mc = MethodContainer.new(container_name, path)
38
+ mc.add_method(full_method_name, operators, @flogger.totals[full_method_name], path)
39
+ @method_containers[container_name] = mc
40
+ end
41
+ end
42
+ end
43
+
44
+ def to_h
45
+ sorted_containers = @method_containers.values.sort_by {|c| c.highest_score}.reverse
46
+ {:flog => { :total => @flogger.total,
47
+ :average => @flogger.average,
48
+ :method_containers => sorted_containers.map {|method_container| method_container.to_h}}}
49
+ end
50
+
51
+ def per_file_info(out)
52
+ @method_containers.each_pair do |klass, container|
53
+ container.methods.each_pair do |method_name, data|
54
+ next if data[:path].nil?
55
+
56
+ file, line = data[:path].split(':')
57
+
58
+ out[file] ||= {}
59
+ out[file][line] ||= []
60
+ out[file][line] << {:type => :flog, :description => "Score of %.2f" % data[:score]}
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ class MethodContainer
67
+ attr_reader :methods
68
+
69
+ def initialize(name, path)
70
+ @name = name
71
+ add_path path
72
+ @methods = {}
73
+ end
74
+
75
+ def add_path path
76
+ return unless path
77
+ @path ||= path.split(':').first
78
+ end
79
+
80
+ def add_method(full_method_name, operators, score, path)
81
+ @methods[full_method_name] = {:operators => operators, :score => score, :path => path}
82
+ end
83
+
84
+ def to_h
85
+ { :name => @name,
86
+ :path => @path || '',
87
+ :total_score => total_score,
88
+ :highest_score => highest_score,
89
+ :average_score => average_score,
90
+ :methods => @methods}
91
+ end
92
+
93
+ def highest_score
94
+ method_scores.max
95
+ end
96
+
97
+ private
98
+
99
+ def method_scores
100
+ @method_scores ||= @methods.values.map {|v| v[:score] }
101
+ end
102
+
103
+ def total_score
104
+ @total_score ||= method_scores.inject(0) {|sum, score| sum += score}
105
+ end
106
+
107
+ def average_score
108
+ total_score / method_scores.size.to_f
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,52 @@
1
+ module MetricFu
2
+
3
+ class Hotspots < Generator
4
+
5
+ def initialize(options={})
6
+ super
7
+ end
8
+
9
+ def self.verify_dependencies!
10
+ true
11
+ end
12
+
13
+ def emit
14
+ @analyzer = MetricAnalyzer.new(MetricFu.report.report_hash)
15
+ end
16
+
17
+ def analyze
18
+ num = nil
19
+ worst_items = {}
20
+ if @analyzer
21
+ worst_items[:files] =
22
+ @analyzer.worst_files(num).inject([]) do |array, worst_file|
23
+ array <<
24
+ {:location => @analyzer.location(:file, worst_file),
25
+ :details => @analyzer.problems_with(:file, worst_file)}
26
+ array
27
+ end
28
+ worst_items[:classes] = @analyzer.worst_classes(num).inject([]) do |array, class_name|
29
+ location = @analyzer.location(:class, class_name)
30
+ array <<
31
+ {:location => location,
32
+ :details => @analyzer.problems_with(:class, class_name)}
33
+ array
34
+ end
35
+ worst_items[:methods] = @analyzer.worst_methods(num).inject([]) do |array, method_name|
36
+ location = @analyzer.location(:method, method_name)
37
+ array <<
38
+ {:location => location,
39
+ :details => @analyzer.problems_with(:method, method_name)}
40
+ array
41
+ end
42
+ end
43
+
44
+ @hotspots = worst_items
45
+ end
46
+
47
+ def to_h
48
+ {:hotspots => @hotspots}
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,53 @@
1
+ module MetricFu
2
+ class RailsBestPractices < Generator
3
+
4
+ def emit
5
+ @output = `rails_best_practices --without-color .`
6
+ end
7
+
8
+ def analyze
9
+ @matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
10
+ total = @matches.pop
11
+ cleanup_color_switches(total.first)
12
+
13
+ 2.times { @matches.pop } # ignore wiki link
14
+ @matches.reject! {|array| array.empty? }
15
+ @matches.map! do |match|
16
+ file, line = match[0].split(':')
17
+ problem = match[1]
18
+
19
+ cleanup_color_switches(file)
20
+ cleanup_color_switches(problem)
21
+
22
+ file.gsub!(/^\.\//, '')
23
+
24
+ {:file => file, :line => line, :problem => problem}
25
+ end
26
+ @rails_best_practices_results = {:total => total, :problems => @matches}
27
+ end
28
+
29
+ def cleanup_color_switches(str)
30
+ return if str.nil?
31
+ str.gsub!(%r{^\e\[3[1|2]m}, '')
32
+ str.gsub!(%r{\e\[0m$}, '')
33
+ end
34
+
35
+ def to_h
36
+ {:rails_best_practices => @rails_best_practices_results}
37
+ end
38
+
39
+ def per_file_info(out)
40
+ @rails_best_practices_results[:problems].each do |problem|
41
+ next if problem[:file] == '' || problem[:problem].nil?
42
+
43
+ out[problem[:file]] ||= {}
44
+
45
+ lines = problem[:line].split(/\s*,\s*/)
46
+ lines.each do |line|
47
+ out[problem[:file]][line] ||= []
48
+ out[problem[:file]][line] << {:type => :rails_best_practices, :description => problem[:problem]}
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end