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.
- data/HISTORY +237 -0
- data/MIT-LICENSE +22 -0
- data/README +29 -0
- data/Rakefile +18 -0
- data/TODO +6 -0
- data/lib/base/base_template.rb +172 -0
- data/lib/base/churn_analyzer.rb +38 -0
- data/lib/base/code_issue.rb +97 -0
- data/lib/base/configuration.rb +199 -0
- data/lib/base/flay_analyzer.rb +50 -0
- data/lib/base/flog_analyzer.rb +43 -0
- data/lib/base/generator.rb +166 -0
- data/lib/base/graph.rb +44 -0
- data/lib/base/line_numbers.rb +74 -0
- data/lib/base/location.rb +85 -0
- data/lib/base/md5_tracker.rb +52 -0
- data/lib/base/metric_analyzer.rb +404 -0
- data/lib/base/ranking.rb +34 -0
- data/lib/base/rcov_analyzer.rb +43 -0
- data/lib/base/reek_analyzer.rb +163 -0
- data/lib/base/report.rb +108 -0
- data/lib/base/roodi_analyzer.rb +37 -0
- data/lib/base/saikuro_analyzer.rb +48 -0
- data/lib/base/scoring_strategies.rb +29 -0
- data/lib/base/stats_analyzer.rb +37 -0
- data/lib/base/table.rb +102 -0
- data/lib/generators/churn.rb +28 -0
- data/lib/generators/flay.rb +31 -0
- data/lib/generators/flog.rb +111 -0
- data/lib/generators/hotspots.rb +52 -0
- data/lib/generators/rails_best_practices.rb +53 -0
- data/lib/generators/rcov.rb +122 -0
- data/lib/generators/reek.rb +81 -0
- data/lib/generators/roodi.rb +35 -0
- data/lib/generators/saikuro.rb +256 -0
- data/lib/generators/stats.rb +58 -0
- data/lib/graphs/engines/bluff.rb +113 -0
- data/lib/graphs/engines/gchart.rb +157 -0
- data/lib/graphs/flay_grapher.rb +18 -0
- data/lib/graphs/flog_grapher.rb +57 -0
- data/lib/graphs/grapher.rb +11 -0
- data/lib/graphs/rails_best_practices_grapher.rb +19 -0
- data/lib/graphs/rcov_grapher.rb +18 -0
- data/lib/graphs/reek_grapher.rb +30 -0
- data/lib/graphs/roodi_grapher.rb +18 -0
- data/lib/graphs/stats_grapher.rb +20 -0
- data/lib/metric_fu.rb +40 -0
- data/lib/templates/awesome/awesome_template.rb +73 -0
- data/lib/templates/awesome/churn.html.erb +58 -0
- data/lib/templates/awesome/css/buttons.css +82 -0
- data/lib/templates/awesome/css/default.css +91 -0
- data/lib/templates/awesome/css/integrity.css +334 -0
- data/lib/templates/awesome/css/reset.css +7 -0
- data/lib/templates/awesome/css/syntax.css +19 -0
- data/lib/templates/awesome/flay.html.erb +34 -0
- data/lib/templates/awesome/flog.html.erb +55 -0
- data/lib/templates/awesome/hotspots.html.erb +62 -0
- data/lib/templates/awesome/index.html.erb +34 -0
- data/lib/templates/awesome/layout.html.erb +30 -0
- data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
- data/lib/templates/awesome/rcov.html.erb +42 -0
- data/lib/templates/awesome/reek.html.erb +40 -0
- data/lib/templates/awesome/roodi.html.erb +27 -0
- data/lib/templates/awesome/saikuro.html.erb +71 -0
- data/lib/templates/awesome/stats.html.erb +51 -0
- data/lib/templates/javascripts/bluff-min.js +1 -0
- data/lib/templates/javascripts/excanvas.js +35 -0
- data/lib/templates/javascripts/js-class.js +1 -0
- data/lib/templates/standard/churn.html.erb +31 -0
- data/lib/templates/standard/default.css +64 -0
- data/lib/templates/standard/flay.html.erb +34 -0
- data/lib/templates/standard/flog.html.erb +57 -0
- data/lib/templates/standard/hotspots.html.erb +54 -0
- data/lib/templates/standard/index.html.erb +41 -0
- data/lib/templates/standard/rails_best_practices.html.erb +29 -0
- data/lib/templates/standard/rcov.html.erb +43 -0
- data/lib/templates/standard/reek.html.erb +42 -0
- data/lib/templates/standard/roodi.html.erb +29 -0
- data/lib/templates/standard/saikuro.html.erb +84 -0
- data/lib/templates/standard/standard_template.rb +26 -0
- data/lib/templates/standard/stats.html.erb +55 -0
- data/spec/base/base_template_spec.rb +177 -0
- data/spec/base/configuration_spec.rb +276 -0
- data/spec/base/generator_spec.rb +223 -0
- data/spec/base/graph_spec.rb +61 -0
- data/spec/base/line_numbers_spec.rb +62 -0
- data/spec/base/md5_tracker_spec.rb +57 -0
- data/spec/base/report_spec.rb +146 -0
- data/spec/generators/churn_spec.rb +41 -0
- data/spec/generators/flay_spec.rb +105 -0
- data/spec/generators/flog_spec.rb +70 -0
- data/spec/generators/rails_best_practices_spec.rb +52 -0
- data/spec/generators/rcov_spec.rb +180 -0
- data/spec/generators/reek_spec.rb +134 -0
- data/spec/generators/roodi_spec.rb +24 -0
- data/spec/generators/saikuro_spec.rb +74 -0
- data/spec/generators/stats_spec.rb +74 -0
- data/spec/graphs/engines/bluff_spec.rb +19 -0
- data/spec/graphs/engines/gchart_spec.rb +156 -0
- data/spec/graphs/flay_grapher_spec.rb +56 -0
- data/spec/graphs/flog_grapher_spec.rb +108 -0
- data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
- data/spec/graphs/rcov_grapher_spec.rb +56 -0
- data/spec/graphs/reek_grapher_spec.rb +65 -0
- data/spec/graphs/roodi_grapher_spec.rb +56 -0
- data/spec/graphs/stats_grapher_spec.rb +68 -0
- data/spec/resources/line_numbers/foo.rb +33 -0
- data/spec/resources/line_numbers/module.rb +11 -0
- data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
- data/spec/resources/line_numbers/two_classes.rb +11 -0
- data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
- data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
- data/spec/resources/saikuro/index_cyclo.html +155 -0
- data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
- data/spec/resources/yml/20090630.yml +7922 -0
- data/spec/resources/yml/metric_missing.yml +1 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/metric_fu.rake +22 -0
- metadata +448 -0
data/lib/base/report.rb
ADDED
@@ -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
|