danmayer-metric_fu 2.1.2
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 +462 -0
data/lib/base/ranking.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
module MetricFu
|
|
3
|
+
class Ranking
|
|
4
|
+
extend Forwardable
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@items_to_score = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def top(num=nil)
|
|
11
|
+
if(num.is_a?(Numeric))
|
|
12
|
+
sorted_items[0,num]
|
|
13
|
+
else
|
|
14
|
+
sorted_items
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def percentile(item)
|
|
19
|
+
index = sorted_items.index(item)
|
|
20
|
+
worse_item_count = (length - (index+1))
|
|
21
|
+
worse_item_count.to_f/length
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def_delegator :@items_to_score, :has_key?, :scored?
|
|
25
|
+
def_delegators :@items_to_score, :[], :[]=, :length, :each, :delete
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def sorted_items
|
|
30
|
+
@sorted_items ||= @items_to_score.sort_by {|item, score| -score}.map {|item, score| item}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class RcovAnalyzer
|
|
2
|
+
include ScoringStrategies
|
|
3
|
+
|
|
4
|
+
COLUMNS = %w{percentage_uncovered}
|
|
5
|
+
|
|
6
|
+
def columns
|
|
7
|
+
COLUMNS
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def name
|
|
11
|
+
:rcov
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def map(row)
|
|
15
|
+
row.percentage_uncovered
|
|
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.each do |file_name, info|
|
|
29
|
+
next if (file_name == :global_percent_run) || (info[:methods].nil?)
|
|
30
|
+
info[:methods].each do |method_name, percentage_uncovered|
|
|
31
|
+
location = MetricFu::Location.for(method_name)
|
|
32
|
+
table << {
|
|
33
|
+
"metric" => :rcov,
|
|
34
|
+
'file_path' => file_name,
|
|
35
|
+
'class_name' => location.class_name,
|
|
36
|
+
"method_name" => location.method_name,
|
|
37
|
+
"percentage_uncovered" => percentage_uncovered
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
class ReekAnalyzer
|
|
4
|
+
include ScoringStrategies
|
|
5
|
+
|
|
6
|
+
REEK_ISSUE_INFO = {
|
|
7
|
+
'Uncommunicative Name' =>
|
|
8
|
+
{'link' => 'http://wiki.github.com/kevinrutherford/reek/uncommunicative-name',
|
|
9
|
+
'info' => 'An Uncommunicative Name is a name that doesn’t communicate its intent well enough.'},
|
|
10
|
+
'Class Variable' =>
|
|
11
|
+
{'link' => 'http://wiki.github.com/kevinrutherford/reek/class-variable',
|
|
12
|
+
'info' => 'Class variables form part of the global runtime state, and as such make it ' +
|
|
13
|
+
'easy for one part of the system to accidentally or inadvertently depend on ' +
|
|
14
|
+
'another part of the system.'},
|
|
15
|
+
'Duplication' =>
|
|
16
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/duplication',
|
|
17
|
+
'info' => 'Duplication occurs when two fragments of code look nearly identical, or when ' +
|
|
18
|
+
'two fragments of code have nearly identical effects at some conceptual level.'},
|
|
19
|
+
'Low Cohesion' =>
|
|
20
|
+
{'link' => 'http://en.wikipedia.org/wiki/Cohesion_(computer_science)',
|
|
21
|
+
'info' => 'Low cohesion is associated with undesirable traits such as being difficult to ' +
|
|
22
|
+
'maintain, difficult to test, difficult to reuse, and even difficult to understand.'},
|
|
23
|
+
'Nested Iterators' =>
|
|
24
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/nested-iterators',
|
|
25
|
+
'info' => 'Nested Iterator occurs when a block contains another block.'},
|
|
26
|
+
'Control Couple' =>
|
|
27
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/control-couple',
|
|
28
|
+
'info' => 'Control coupling occurs when a method or block checks the value of a parameter in ' +
|
|
29
|
+
'order to decide which execution path to take. The offending parameter is often called a “Control Couple”.'},
|
|
30
|
+
'Irresponsible Module' =>
|
|
31
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/irresponsible-module',
|
|
32
|
+
'info' => 'Classes and modules are the units of reuse and release. It is therefore considered ' +
|
|
33
|
+
'good practice to annotate every class and module with a brief comment outlining its responsibilities.'},
|
|
34
|
+
'Long Parameter List' =>
|
|
35
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/long-parameter-list',
|
|
36
|
+
'info' => 'A Long Parameter List occurs when a method has more than one or two parameters, ' +
|
|
37
|
+
'or when a method yields more than one or two objects to an associated block.'},
|
|
38
|
+
'Data Clump' =>
|
|
39
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/data-clump',
|
|
40
|
+
'info' => 'In general, a Data Clump occurs when the same two or three items frequently appear ' +
|
|
41
|
+
'together in classes and parameter lists, or when a group of instance variable names ' +
|
|
42
|
+
'start or end with similar substrings.'},
|
|
43
|
+
'Simulated Polymorphism' =>
|
|
44
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/simulated-polymorphism',
|
|
45
|
+
'info' => 'Simulated Polymorphism occurs when, code uses a case statement (especially on a ' +
|
|
46
|
+
'type field) or code uses instance_of?, kind_of?, is_a?, or === to decide what code to execute'},
|
|
47
|
+
'Large Class' =>
|
|
48
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/large-class',
|
|
49
|
+
'info' => 'A Large Class is a class or module that has a large number of instance variables, ' +
|
|
50
|
+
'methods or lines of code in any one piece of its specification.'},
|
|
51
|
+
'Long Method' =>
|
|
52
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/long-method',
|
|
53
|
+
'info' => 'Long methods can be hard to read and understand. They often are harder to test and ' +
|
|
54
|
+
'maintain as well, which can lead to buggier code.'},
|
|
55
|
+
'Feature Envy' =>
|
|
56
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/feature-envy',
|
|
57
|
+
'info' => 'Feature Envy occurs when a code fragment references another object more often than ' +
|
|
58
|
+
'it references itself, or when several clients do the same series of manipulations ' +
|
|
59
|
+
'on a particular type of object.'},
|
|
60
|
+
'Utility Function' =>
|
|
61
|
+
{'link' =>'http://wiki.github.com/kevinrutherford/reek/utility-function',
|
|
62
|
+
'info' => 'A Utility Function is any instance method that has no dependency on the state of the ' +
|
|
63
|
+
'instance. It reduces the code’s ability to communicate intent. Code that “belongs” on ' +
|
|
64
|
+
'one class but which is located in another can be hard to find.'},
|
|
65
|
+
'Attribute' =>
|
|
66
|
+
{'link' => 'http://wiki.github.com/kevinrutherford/reek/attribute',
|
|
67
|
+
'info' => 'A class that publishes a getter or setter for an instance variable invites client ' +
|
|
68
|
+
'classes to become too intimate with its inner workings, and in particular with its ' +
|
|
69
|
+
'representation of state.'}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Note that in practice, the prefix reek__ is appended to each one
|
|
73
|
+
# This was a partially implemented idea to avoid column name collisions
|
|
74
|
+
# but it is only done in the ReekAnalyzer
|
|
75
|
+
COLUMNS = %w{type_name message value value_description comparable_message}
|
|
76
|
+
|
|
77
|
+
def self.issue_link(issue)
|
|
78
|
+
REEK_ISSUE_INFO[issue]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def columns
|
|
82
|
+
COLUMNS.map{|column| "#{name}__#{column}"}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def name
|
|
86
|
+
:reek
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def map(row)
|
|
90
|
+
ScoringStrategies.present(row)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def reduce(scores)
|
|
94
|
+
ScoringStrategies.sum(scores)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def score(metric_ranking, item)
|
|
98
|
+
ScoringStrategies.percentile(metric_ranking, item)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def generate_records(data, table)
|
|
102
|
+
return if data==nil
|
|
103
|
+
data[:matches].each do |match|
|
|
104
|
+
file_path = match[:file_path]
|
|
105
|
+
match[:code_smells].each do |smell|
|
|
106
|
+
location = MetricFu::Location.for(smell[:method])
|
|
107
|
+
smell_type = smell[:type]
|
|
108
|
+
message = smell[:message]
|
|
109
|
+
table << {
|
|
110
|
+
"metric" => name, # important
|
|
111
|
+
"file_path" => file_path, # important
|
|
112
|
+
# NOTE: ReekAnalyzer is currently different than other analyzers with regard
|
|
113
|
+
# to column name. Note the COLUMNS constant and #columns method
|
|
114
|
+
"reek__message" => message,
|
|
115
|
+
"reek__type_name" => smell_type,
|
|
116
|
+
"reek__value" => parse_value(message),
|
|
117
|
+
"reek__value_description" => build_value_description(smell_type, message),
|
|
118
|
+
"reek__comparable_message" => comparable_message(smell_type, message),
|
|
119
|
+
"class_name" => location.class_name, # important
|
|
120
|
+
"method_name" => location.method_name, # important
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.numeric_smell?(type)
|
|
127
|
+
["Large Class", "Long Method", "Long Parameter List"].include?(type)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def comparable_message(type_name, message)
|
|
133
|
+
if self.class.numeric_smell?(type_name)
|
|
134
|
+
match = message.match(/\d+/)
|
|
135
|
+
if(match)
|
|
136
|
+
match.pre_match + match.post_match
|
|
137
|
+
else
|
|
138
|
+
message
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
message
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def build_value_description(type_name, message)
|
|
146
|
+
item_type = message.match(/\d+ (.*)$/)
|
|
147
|
+
if(item_type)
|
|
148
|
+
"number of #{item_type[1]} in #{type_name.downcase}"
|
|
149
|
+
else
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def parse_value(message)
|
|
155
|
+
match = message.match(/\d+/)
|
|
156
|
+
if(match)
|
|
157
|
+
match[0].to_i
|
|
158
|
+
else
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
end
|
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
|