metric_fu 2.0.1 → 2.1.0
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 +12 -0
- data/MIT-LICENSE +1 -1
- data/lib/base/base_template.rb +8 -3
- data/lib/base/churn_analyzer.rb +0 -14
- data/lib/base/configuration.rb +6 -2
- data/lib/base/generator.rb +4 -0
- data/lib/base/line_numbers.rb +10 -2
- data/lib/base/reek_analyzer.rb +63 -16
- data/lib/base/report.rb +9 -1
- data/lib/generators/churn.rb +1 -1
- data/lib/generators/flog.rb +13 -0
- data/lib/generators/rails_best_practices.rb +29 -1
- data/lib/generators/rcov.rb +2 -6
- data/lib/generators/reek.rb +21 -0
- data/lib/generators/roodi.rb +8 -0
- data/lib/generators/saikuro.rb +39 -7
- data/lib/generators/stats.rb +1 -2
- data/lib/metric_fu.rb +1 -0
- data/lib/templates/awesome/awesome_template.rb +35 -0
- data/lib/templates/awesome/css/syntax.css +19 -0
- data/lib/templates/awesome/hotspots.html.erb +8 -0
- data/lib/templates/awesome/rails_best_practices.html.erb +1 -1
- data/spec/base/base_template_spec.rb +18 -2
- data/spec/base/report_spec.rb +8 -1
- data/spec/generators/churn_spec.rb +1 -1
- data/spec/generators/rails_best_practices_spec.rb +3 -3
- data/spec/generators/rcov_spec.rb +2 -2
- data/spec/generators/saikuro_spec.rb +17 -1
- metadata +35 -20
data/HISTORY
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
=== MetricFu 2.1.0 / 2011-03-1
|
2
|
+
|
3
|
+
* Flog gemspec version was >= 2.2.0, which was too early and didn't work. Changed to >= 2.3.0 - Chris Griego
|
4
|
+
* RCov generator now uses a regex with begin and end line anchor to avoid splitting on comments with equal signs in source files - Andrew Selder
|
5
|
+
* RCov generator now always strips the 3 leading characters from the lines when reconstruction source files so that heredocs and block comments parse successfully - Andrew Selder
|
6
|
+
* Dan Mayer ported some specs for the Hotspots code into MetricFu from Caliper's code.
|
7
|
+
* Stefan Huber fixed some problems with churn pretending not to support Svn.
|
8
|
+
* Kakutani Shintaro added the ability to opt out of opening files with TextMate (config.darwin_txmt_protocol_no_thanks = true).
|
9
|
+
* Joel Nimety and Andrew Selder fixed a problem where Saikuro was parsing a dir twice.
|
10
|
+
* Dan Sinclair added some awesome 'annotate' functionality to the Hotspots page. Click on it so see the file with problems in-line.
|
11
|
+
* Dan Sinclair added a verbose mode (config.verbose = true).
|
12
|
+
|
1
13
|
=== MetricFu 2.0.1 / 2010-11-13
|
2
14
|
|
3
15
|
* Delete trailing whitespaces - Delwyn de Villiers
|
data/MIT-LICENSE
CHANGED
data/lib/base/base_template.rb
CHANGED
@@ -8,7 +8,7 @@ module MetricFu
|
|
8
8
|
# to actually write out the template. See StandardTemplate for an
|
9
9
|
# example.
|
10
10
|
class Template
|
11
|
-
attr_accessor :report
|
11
|
+
attr_accessor :report, :per_file_data
|
12
12
|
|
13
13
|
private
|
14
14
|
# Creates a new erb evaluated result from the passed in section.
|
@@ -137,13 +137,19 @@ module MetricFu
|
|
137
137
|
def file_url(name, line) # :nodoc:
|
138
138
|
return '' unless name
|
139
139
|
filename = File.expand_path(name.gsub(/^\//, ''))
|
140
|
-
if
|
140
|
+
if render_as_txmt_protocol?
|
141
141
|
"txmt://open/?url=file://#{filename}" << (line ? "&line=#{line}" : "")
|
142
142
|
else
|
143
143
|
"file://#{filename}"
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
+
def render_as_txmt_protocol? # :nodoc:
|
148
|
+
config = MetricFu.configuration
|
149
|
+
return false unless config.platform.include?('darwin')
|
150
|
+
return !config.darwin_txmt_protocol_no_thanks
|
151
|
+
end
|
152
|
+
|
147
153
|
# Provides a brain dead way to cycle between two values during
|
148
154
|
# an iteration of some sort. Pass in the first_value, the second_value,
|
149
155
|
# and the cardinality of the iteration.
|
@@ -162,6 +168,5 @@ module MetricFu
|
|
162
168
|
return first_value if iteration % 2 == 0
|
163
169
|
return second_value
|
164
170
|
end
|
165
|
-
|
166
171
|
end
|
167
172
|
end
|
data/lib/base/churn_analyzer.rb
CHANGED
@@ -35,18 +35,4 @@ class ChurnAnalyzer
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
private
|
39
|
-
|
40
|
-
def self.update_changes(total, changed)
|
41
|
-
changed.each do |change|
|
42
|
-
#should work as has_key(change), but hash == doesn't work on 1.8.6 here for some reason it never matches
|
43
|
-
if total.has_key?(change.to_a.sort)
|
44
|
-
total[change.to_a.sort] += 1
|
45
|
-
else
|
46
|
-
total[change.to_a.sort] = 1
|
47
|
-
end
|
48
|
-
end
|
49
|
-
total
|
50
|
-
end
|
51
|
-
|
52
38
|
end
|
data/lib/base/configuration.rb
CHANGED
@@ -8,9 +8,9 @@ module MetricFu
|
|
8
8
|
AVAILABLE_METRICS = [:churn, :flog, :flay, :reek,
|
9
9
|
:roodi, :rcov,
|
10
10
|
:hotspots]
|
11
|
-
|
11
|
+
|
12
12
|
AVAILABLE_METRICS << :saikuro unless RUBY_VERSION == '1.9.2'
|
13
|
-
|
13
|
+
|
14
14
|
AVAILABLE_GRAPHS = [:flog, :flay, :reek, :roodi, :rcov, :rails_best_practices]
|
15
15
|
AVAILABLE_GRAPH_ENGINES = [:gchart, :bluff]
|
16
16
|
|
@@ -145,7 +145,11 @@ module MetricFu
|
|
145
145
|
@hotspots = {}
|
146
146
|
@file_globs_to_ignore = []
|
147
147
|
|
148
|
+
@verbose = false
|
149
|
+
|
148
150
|
@graph_engine = :bluff # can be :bluff or :gchart
|
151
|
+
|
152
|
+
@darwin_txmt_protocol_no_thanks = false
|
149
153
|
end
|
150
154
|
|
151
155
|
# Perform a simple check to try and guess if we're running
|
data/lib/base/generator.rb
CHANGED
@@ -121,6 +121,10 @@ module MetricFu
|
|
121
121
|
# methods to allow extra hooks into the processing methods, and help
|
122
122
|
# to keep the logic of your Generators clean.
|
123
123
|
def generate_report
|
124
|
+
if MetricFu.configuration.verbose
|
125
|
+
puts "Executing #{self.class.to_s.gsub(/.*::/, '')}"
|
126
|
+
end
|
127
|
+
|
124
128
|
%w[emit analyze].each do |meth|
|
125
129
|
send("before_#{meth}".to_sym)
|
126
130
|
send("#{meth}".to_sym)
|
data/lib/base/line_numbers.rb
CHANGED
@@ -4,8 +4,8 @@ module MetricFu
|
|
4
4
|
|
5
5
|
def initialize(contents)
|
6
6
|
rp = RubyParser.new
|
7
|
-
file_sexp = rp.parse(contents)
|
8
7
|
@locations = {}
|
8
|
+
file_sexp = rp.parse(contents)
|
9
9
|
case file_sexp[0]
|
10
10
|
when :class
|
11
11
|
process_class(file_sexp)
|
@@ -15,6 +15,9 @@ module MetricFu
|
|
15
15
|
file_sexp.each_of_type(:class) { |sexp| process_class(sexp) }
|
16
16
|
else
|
17
17
|
end
|
18
|
+
rescue Exception
|
19
|
+
#catch errors for files ruby_parser fails on
|
20
|
+
@locations
|
18
21
|
end
|
19
22
|
|
20
23
|
def in_method? line_number
|
@@ -31,6 +34,11 @@ module MetricFu
|
|
31
34
|
found_method_and_range.first
|
32
35
|
end
|
33
36
|
|
37
|
+
def start_line_for_method(method)
|
38
|
+
return nil unless @locations.has_key?(method)
|
39
|
+
@locations[method].first
|
40
|
+
end
|
41
|
+
|
34
42
|
private
|
35
43
|
|
36
44
|
def process_module(sexp)
|
@@ -63,4 +71,4 @@ module MetricFu
|
|
63
71
|
end
|
64
72
|
|
65
73
|
end
|
66
|
-
end
|
74
|
+
end
|
data/lib/base/reek_analyzer.rb
CHANGED
@@ -3,23 +3,70 @@
|
|
3
3
|
class ReekAnalyzer
|
4
4
|
include ScoringStrategies
|
5
5
|
|
6
|
-
REEK_ISSUE_INFO = {
|
7
|
-
|
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.'},
|
8
10
|
'Class Variable' =>
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
'
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
'
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
'
|
22
|
-
|
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.'}
|
23
70
|
}
|
24
71
|
|
25
72
|
# Note that in practice, the prefix reek__ is appended to each one
|
data/lib/base/report.rb
CHANGED
@@ -28,6 +28,9 @@ module MetricFu
|
|
28
28
|
report_hash.to_yaml
|
29
29
|
end
|
30
30
|
|
31
|
+
def per_file_data
|
32
|
+
@per_file_data ||= {}
|
33
|
+
end
|
31
34
|
|
32
35
|
def report_hash #:nodoc:
|
33
36
|
@report_hash ||= {}
|
@@ -41,6 +44,7 @@ module MetricFu
|
|
41
44
|
def save_templatized_report
|
42
45
|
@template = MetricFu.template_class.new
|
43
46
|
@template.report = report_hash
|
47
|
+
@template.per_file_data = per_file_data
|
44
48
|
@template.write
|
45
49
|
end
|
46
50
|
|
@@ -51,7 +55,11 @@ module MetricFu
|
|
51
55
|
# The hash to add to the aggregate report_hash
|
52
56
|
def add(report_type)
|
53
57
|
clazz = MetricFu.const_get(report_type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
|
54
|
-
|
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)
|
55
63
|
end
|
56
64
|
|
57
65
|
# Saves the passed in content to the passed in directory. If
|
data/lib/generators/churn.rb
CHANGED
data/lib/generators/flog.rb
CHANGED
@@ -48,6 +48,19 @@ module MetricFu
|
|
48
48
|
:method_containers => sorted_containers.map {|method_container| method_container.to_h}}}
|
49
49
|
end
|
50
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
|
51
64
|
end
|
52
65
|
|
53
66
|
class MethodContainer
|
@@ -2,24 +2,52 @@ module MetricFu
|
|
2
2
|
class RailsBestPractices < Generator
|
3
3
|
|
4
4
|
def emit
|
5
|
-
@output = `rails_best_practices .`
|
5
|
+
@output = `rails_best_practices --without-color .`
|
6
6
|
end
|
7
7
|
|
8
8
|
def analyze
|
9
9
|
@matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
|
10
10
|
total = @matches.pop
|
11
|
+
cleanup_color_switches(total.first)
|
12
|
+
|
11
13
|
2.times { @matches.pop } # ignore wiki link
|
12
14
|
@matches.reject! {|array| array.empty? }
|
13
15
|
@matches.map! do |match|
|
14
16
|
file, line = match[0].split(':')
|
15
17
|
problem = match[1]
|
18
|
+
|
19
|
+
cleanup_color_switches(file)
|
20
|
+
cleanup_color_switches(problem)
|
21
|
+
|
22
|
+
file.gsub!(/^\.\//, '')
|
23
|
+
|
16
24
|
{:file => file, :line => line, :problem => problem}
|
17
25
|
end
|
18
26
|
@rails_best_practices_results = {:total => total, :problems => @matches}
|
19
27
|
end
|
20
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
|
+
|
21
35
|
def to_h
|
22
36
|
{:rails_best_practices => @rails_best_practices_results}
|
23
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
|
24
52
|
end
|
25
53
|
end
|
data/lib/generators/rcov.rb
CHANGED
@@ -3,7 +3,7 @@ require 'enumerator'
|
|
3
3
|
module MetricFu
|
4
4
|
|
5
5
|
class Rcov < Generator
|
6
|
-
NEW_FILE_MARKER =
|
6
|
+
NEW_FILE_MARKER = /^={80}$/.freeze
|
7
7
|
|
8
8
|
class Line
|
9
9
|
attr_accessor :content, :was_run
|
@@ -100,11 +100,7 @@ module MetricFu
|
|
100
100
|
files.each_pair {|fname, content| files[fname] = content.split("\n") }
|
101
101
|
files.each_pair do |fname, content|
|
102
102
|
content.map! do |raw_line|
|
103
|
-
|
104
|
-
line = Line.new(raw_line.gsub('!!', ' '), false).to_h
|
105
|
-
else
|
106
|
-
line = Line.new(raw_line, true).to_h
|
107
|
-
end
|
103
|
+
line = Line.new(raw_line[3..-1], !raw_line.match(/^!!/)).to_h
|
108
104
|
end
|
109
105
|
content.reject! {|line| line[:content].blank? }
|
110
106
|
files[fname] = {:lines => content}
|
data/lib/generators/reek.rb
CHANGED
@@ -56,5 +56,26 @@ module MetricFu
|
|
56
56
|
{:reek => {:matches => @matches}}
|
57
57
|
end
|
58
58
|
|
59
|
+
def per_file_info(out)
|
60
|
+
@matches.each do |file_data|
|
61
|
+
next if File.extname(file_data[:file_path]) == '.erb'
|
62
|
+
begin
|
63
|
+
line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:file_path], 'r').read)
|
64
|
+
rescue StandardError => e
|
65
|
+
raise e unless e.message =~ /you shouldn't be able to get here/
|
66
|
+
puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level reek information for this file."
|
67
|
+
next
|
68
|
+
end
|
69
|
+
|
70
|
+
out[file_data[:file_path]] ||= {}
|
71
|
+
file_data[:code_smells].each do |smell_data|
|
72
|
+
line = line_numbers.start_line_for_method(smell_data[:method])
|
73
|
+
out[file_data[:file_path]][line.to_s] ||= []
|
74
|
+
out[file_data[:file_path]][line.to_s] << {:type => :reek,
|
75
|
+
:description => "#{smell_data[:type]} - #{smell_data[:message]}"}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
59
80
|
end
|
60
81
|
end
|
data/lib/generators/roodi.rb
CHANGED
@@ -23,5 +23,13 @@ module MetricFu
|
|
23
23
|
def to_h
|
24
24
|
{:roodi => @roodi_results}
|
25
25
|
end
|
26
|
+
|
27
|
+
def per_file_info(out)
|
28
|
+
@matches.each do |match|
|
29
|
+
out[match[:file]] ||= {}
|
30
|
+
out[match[:file]][match[:line]] ||= []
|
31
|
+
out[match[:file]][match[:line]] << {:type => :roodi, :description => match[:problem]}
|
32
|
+
end
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
data/lib/generators/saikuro.rb
CHANGED
@@ -4,7 +4,7 @@ module MetricFu
|
|
4
4
|
|
5
5
|
def emit
|
6
6
|
options_string = MetricFu.saikuro.inject("") do |options, option|
|
7
|
-
options + "--#{option.join(' ')} "
|
7
|
+
option[0] == :input_directory ? options : options + "--#{option.join(' ')} "
|
8
8
|
end
|
9
9
|
|
10
10
|
MetricFu.saikuro[:input_directory].each do |input_dir|
|
@@ -32,14 +32,42 @@ module MetricFu
|
|
32
32
|
def to_h
|
33
33
|
files = @files.map do |file|
|
34
34
|
my_file = file.to_h
|
35
|
-
|
35
|
+
|
36
|
+
f = file.filepath
|
37
|
+
f.gsub!(%r{^#{metric_directory}/}, '')
|
38
|
+
f << "/#{file.filename}"
|
39
|
+
|
40
|
+
my_file[:filename] = f
|
36
41
|
my_file
|
37
42
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
43
|
+
@saikuro_data = {:files => files,
|
44
|
+
:classes => @classes.map {|c| c.to_h},
|
45
|
+
:methods => @meths.map {|m| m.to_h}
|
46
|
+
}
|
47
|
+
{:saikuro => @saikuro_data}
|
48
|
+
end
|
49
|
+
|
50
|
+
def per_file_info(out)
|
51
|
+
@saikuro_data[:files].each do |file_data|
|
52
|
+
next if File.extname(file_data[:filename]) == '.erb' || !File.exists?(file_data[:filename])
|
53
|
+
begin
|
54
|
+
line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:filename], 'r').read)
|
55
|
+
rescue StandardError => e
|
56
|
+
raise e unless e.message =~ /you shouldn't be able to get here/
|
57
|
+
puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Saikuro information for this file."
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
out[file_data[:filename]] ||= {}
|
62
|
+
file_data[:classes].each do |class_data|
|
63
|
+
class_data[:methods].each do |method_data|
|
64
|
+
line = line_numbers.start_line_for_method(method_data[:name])
|
65
|
+
out[file_data[:filename]][line.to_s] ||= []
|
66
|
+
out[file_data[:filename]][line.to_s] << {:type => :saikuro,
|
67
|
+
:description => "Complexity #{method_data[:complexity]}"}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
43
71
|
end
|
44
72
|
|
45
73
|
private
|
@@ -118,6 +146,10 @@ module MetricFu
|
|
118
146
|
File.basename(@path, '_cyclo.html')
|
119
147
|
end
|
120
148
|
|
149
|
+
def filepath
|
150
|
+
File.dirname(@path)
|
151
|
+
end
|
152
|
+
|
121
153
|
def to_h
|
122
154
|
merge_classes
|
123
155
|
{:classes => @elements}
|
data/lib/generators/stats.rb
CHANGED
@@ -26,8 +26,7 @@ module MetricFu
|
|
26
26
|
|
27
27
|
def remove_noise(output)
|
28
28
|
lines = output.split("\n")
|
29
|
-
lines = lines.find_all {|line| line[
|
30
|
-
lines = lines.find_all {|line| line[0].chr != "(" }
|
29
|
+
lines = lines.find_all {|line| line =~ /^\s*[C|]/ }
|
31
30
|
lines.shift
|
32
31
|
lines
|
33
32
|
end
|
data/lib/metric_fu.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'syntax/convertors/html'
|
2
3
|
|
3
4
|
class AwesomeTemplate < MetricFu::Template
|
4
5
|
|
@@ -14,6 +15,7 @@ class AwesomeTemplate < MetricFu::Template
|
|
14
15
|
report.each_pair do |section, contents|
|
15
16
|
if template_exists?(section)
|
16
17
|
create_instance_var(section, contents)
|
18
|
+
create_instance_var(:per_file_data, per_file_data)
|
17
19
|
@html = erbify(section)
|
18
20
|
html = erbify('layout')
|
19
21
|
fn = output_filename(section)
|
@@ -28,6 +30,39 @@ class AwesomeTemplate < MetricFu::Template
|
|
28
30
|
fn = output_filename('index')
|
29
31
|
MetricFu.report.save_output(html, MetricFu.output_directory, fn)
|
30
32
|
end
|
33
|
+
|
34
|
+
write_file_data
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_file_data
|
38
|
+
convertor = Syntax::Convertors::HTML.for_syntax('ruby')
|
39
|
+
|
40
|
+
per_file_data.each_pair do |file, lines|
|
41
|
+
data = File.open(file, 'r').readlines
|
42
|
+
fn = "#{file.gsub(%r{/}, '_')}.html"
|
43
|
+
|
44
|
+
out = "<html><head><style>#{inline_css('css/syntax.css')}</style></head><body>"
|
45
|
+
out << "<table cellpadding='0' cellspacing='0' class='ruby'>"
|
46
|
+
data.each_with_index do |line, idx|
|
47
|
+
out << "<tr><td valign='top'><small>#{idx + 1}</small></td>"
|
48
|
+
out << "<td valign='top'>"
|
49
|
+
if lines.has_key?((idx + 1).to_s)
|
50
|
+
out << "<ul>"
|
51
|
+
lines[(idx + 1).to_s].each do |problem|
|
52
|
+
out << "<li>#{problem[:description]} » #{problem[:type]}</li>"
|
53
|
+
end
|
54
|
+
out << "</ul>"
|
55
|
+
else
|
56
|
+
out << " "
|
57
|
+
end
|
58
|
+
out << "</td>"
|
59
|
+
out << "<td valign='top'><a name='line#{idx + 1}'>#{convertor.convert(line)}</a></td>"
|
60
|
+
out << "</tr>"
|
61
|
+
end
|
62
|
+
out << "<table></body></html>"
|
63
|
+
|
64
|
+
MetricFu.report.save_output(out, MetricFu.output_directory, fn)
|
65
|
+
end
|
31
66
|
end
|
32
67
|
|
33
68
|
def this_directory
|
@@ -0,0 +1,19 @@
|
|
1
|
+
table { background: #fff; color: #000; }
|
2
|
+
.ruby .normal { color: #000; }
|
3
|
+
.ruby .comment { color: #005; font-style: italic; }
|
4
|
+
.ruby .keyword { color: #A44; font-weight: bold; }
|
5
|
+
.ruby .method { color: #44f; }
|
6
|
+
.ruby .class { color: #b1713d; }
|
7
|
+
.ruby .module { color: #050; }
|
8
|
+
.ruby .punct { color: #668; font-weight: bold; }
|
9
|
+
.ruby .symbol { color: #00f; }
|
10
|
+
.ruby .string { color: #4a4; }
|
11
|
+
.ruby .char { color: #F07; }
|
12
|
+
.ruby .ident { color: #000; }
|
13
|
+
.ruby .constant { color: #b1713d; }
|
14
|
+
.ruby .regex { color: #B66; background: #FEF; }
|
15
|
+
.ruby .number { color: #F99; }
|
16
|
+
.ruby .attribute { color: #f84; }
|
17
|
+
.ruby .global { color: #7FB; }
|
18
|
+
.ruby .expr { color: #227; }
|
19
|
+
.ruby .escape { color: #277; }
|
@@ -32,6 +32,14 @@ No Hotspots were found.
|
|
32
32
|
<b>
|
33
33
|
<%= display_location(item[:location], nil) %>
|
34
34
|
</b>
|
35
|
+
<% if item[:location].file_path %>
|
36
|
+
<% file, line = item[:location].file_path.split(/:/) %>
|
37
|
+
<% if per_file_data[file] %>
|
38
|
+
<small>«
|
39
|
+
<b><a href="<%= file.gsub(%r{/}, '_') %>.html<%= (line.nil? ? '' : "#line#{line}") %>">annotate</a></b>
|
40
|
+
»</small>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
35
43
|
<br/><br/>
|
36
44
|
<!-- TODO HOTSPOTS for metric fu nice metric_link method -->
|
37
45
|
<% item[:details].each do |metric, info| %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<h3>Rails Best Practices Results</h3>
|
2
2
|
|
3
|
-
<p><a href="http://github.com/flyerhzm/rails_best_practices">rails_best_practices</a>
|
3
|
+
<p><a href="http://github.com/flyerhzm/rails_best_practices">rails_best_practices</a> is a code metric tool for rails projects.</p>
|
4
4
|
|
5
5
|
<% graph_name = 'rails_best_practices' %>
|
6
6
|
<% if MetricFu.configuration.graph_engine == :gchart %>
|
@@ -88,6 +88,7 @@ describe MetricFu::Template do
|
|
88
88
|
before(:each) do
|
89
89
|
config = mock("configuration")
|
90
90
|
config.stub!(:platform).and_return('universal-darwin-9.0')
|
91
|
+
config.stub!(:darwin_txmt_protocol_no_thanks).and_return(false)
|
91
92
|
MetricFu.stub!(:configuration).and_return(config)
|
92
93
|
end
|
93
94
|
|
@@ -112,6 +113,22 @@ describe MetricFu::Template do
|
|
112
113
|
+ "/expanded/filename&line=6'>filename:6</a>")
|
113
114
|
end
|
114
115
|
|
116
|
+
describe "but no thanks for txtmt" do
|
117
|
+
before(:each) do
|
118
|
+
config = mock("configuration")
|
119
|
+
config.stub!(:platform).and_return('universal-darwin-9.0')
|
120
|
+
config.stub!(:darwin_txmt_protocol_no_thanks).and_return(true)
|
121
|
+
MetricFu.stub!(:configuration).and_return(config)
|
122
|
+
File.should_receive(:expand_path).and_return('filename')
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should return a file protocol link" do
|
126
|
+
name = "filename"
|
127
|
+
result = @template.send(:link_to_filename, name)
|
128
|
+
result.should == "<a href='file://filename'>filename</a>"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
115
132
|
describe "and given link text" do
|
116
133
|
it "should use the submitted link text" do
|
117
134
|
File.stub!(:expand_path).with('filename').and_return('/expanded/filename')
|
@@ -126,6 +143,7 @@ describe MetricFu::Template do
|
|
126
143
|
before(:each) do
|
127
144
|
config = mock("configuration")
|
128
145
|
config.should_receive(:platform).and_return('other')
|
146
|
+
config.stub!(:darwin_txmt_protocol_no_thanks).and_return(false)
|
129
147
|
MetricFu.stub!(:configuration).and_return(config)
|
130
148
|
File.should_receive(:expand_path).and_return('filename')
|
131
149
|
end
|
@@ -157,5 +175,3 @@ describe MetricFu::Template do
|
|
157
175
|
end
|
158
176
|
|
159
177
|
end
|
160
|
-
|
161
|
-
|
data/spec/base/report_spec.rb
CHANGED
@@ -34,6 +34,7 @@ describe MetricFu::Report do
|
|
34
34
|
template_class.should_receive(:new).and_return(template_class)
|
35
35
|
MetricFu.should_receive(:template_class).and_return(template_class)
|
36
36
|
template_class.should_receive(:report=)
|
37
|
+
template_class.should_receive(:per_file_data=)
|
37
38
|
template_class.should_receive(:write)
|
38
39
|
@report.save_templatized_report
|
39
40
|
end
|
@@ -43,7 +44,13 @@ describe MetricFu::Report do
|
|
43
44
|
it 'should add a passed hash to the report_hash instance variable' do
|
44
45
|
report_type = mock('report_type')
|
45
46
|
report_type.should_receive(:to_s).and_return('type')
|
46
|
-
|
47
|
+
|
48
|
+
report_inst = mock('report_inst')
|
49
|
+
report_type.should_receive(:new).and_return(report_inst)
|
50
|
+
|
51
|
+
report_inst.should_receive(:generate_report).and_return({:a => 'b'})
|
52
|
+
report_inst.should_receive(:respond_to?).and_return(false)
|
53
|
+
|
47
54
|
MetricFu.should_receive(:const_get).
|
48
55
|
with('Type').and_return(report_type)
|
49
56
|
report_hash = mock('report_hash')
|
@@ -11,7 +11,7 @@ describe Churn do
|
|
11
11
|
|
12
12
|
it "should be empty on error" do
|
13
13
|
churn = MetricFu::Churn.new
|
14
|
-
churn.instance_variable_set(:@output, "
|
14
|
+
churn.instance_variable_set(:@output, "Churning requires a subversion or git repo")
|
15
15
|
result = churn.analyze
|
16
16
|
result.should == [:churn => {}]
|
17
17
|
end
|
@@ -5,7 +5,7 @@ describe RailsBestPractices do
|
|
5
5
|
it "should gather the raw data" do
|
6
6
|
MetricFu::Configuration.run {}
|
7
7
|
practices = MetricFu::RailsBestPractices.new
|
8
|
-
practices.should_receive(:`).with("rails_best_practices .")
|
8
|
+
practices.should_receive(:`).with("rails_best_practices --without-color .")
|
9
9
|
practices.emit
|
10
10
|
end
|
11
11
|
end
|
@@ -34,10 +34,10 @@ describe RailsBestPractices do
|
|
34
34
|
@results[:problems].size.should == 2
|
35
35
|
@results[:problems].first.should == { :line => "17",
|
36
36
|
:problem => "replace instance variable with local variable",
|
37
|
-
:file => "
|
37
|
+
:file => "app/views/admin/testimonials/_form.html.erb" }
|
38
38
|
@results[:problems][1].should == { :line => "24,45,68,85",
|
39
39
|
:problem => "use before_filter for show,edit,update,destroy",
|
40
|
-
:file => "
|
40
|
+
:file => "app/controllers/admin/campaigns_controller.rb" }
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -47,12 +47,12 @@ describe MetricFu::Rcov do
|
|
47
47
|
|
48
48
|
it "should know which lines were run" do
|
49
49
|
@files["lib/templates/awesome/awesome_template.rb"][:lines].
|
50
|
-
should include({:content=>"
|
50
|
+
should include({:content=>"require 'fileutils'", :was_run=>true})
|
51
51
|
end
|
52
52
|
|
53
53
|
it "should know which lines NOT were run" do
|
54
54
|
@files["lib/templates/awesome/awesome_template.rb"][:lines].
|
55
|
-
should include({:content=>"
|
55
|
+
should include({:content=>" if template_exists?(section)", :was_run=>false})
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -12,7 +12,7 @@ describe Saikuro do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should find the filename of a file" do
|
15
|
-
@output[:saikuro][:files].first[:filename].should == 'users_controller.rb'
|
15
|
+
@output[:saikuro][:files].first[:filename].should == 'app/controllers/users_controller.rb'
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should find the name of the classes" do
|
@@ -33,6 +33,22 @@ describe Saikuro do
|
|
33
33
|
@output[:saikuro][:methods].first[:lines].should == 15
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
describe "per_file_info method" do
|
38
|
+
before :all do
|
39
|
+
MetricFu::Configuration.run {}
|
40
|
+
File.stub!(:directory?).and_return(true)
|
41
|
+
@saikuro = MetricFu::Saikuro.new
|
42
|
+
@saikuro.stub!(:metric_directory).and_return(File.join(File.dirname(__FILE__), "..", "resources", "saikuro"))
|
43
|
+
@saikuro.analyze
|
44
|
+
@output = @saikuro.to_h
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't try to get information if the file does not exist" do
|
48
|
+
File.should_receive(:exists?).at_least(:once).and_return(false)
|
49
|
+
@saikuro.per_file_info('ignore_me')
|
50
|
+
end
|
51
|
+
end
|
36
52
|
|
37
53
|
describe "format_directories method" do
|
38
54
|
it "should format the directories" do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metric_fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 2.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jake Scruggs
|
@@ -24,7 +24,7 @@ autorequire:
|
|
24
24
|
bindir: bin
|
25
25
|
cert_chain: []
|
26
26
|
|
27
|
-
date:
|
27
|
+
date: 2011-03-01 00:00:00 -06:00
|
28
28
|
default_executable:
|
29
29
|
dependencies:
|
30
30
|
- !ruby/object:Gem::Dependency
|
@@ -51,12 +51,12 @@ dependencies:
|
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
hash:
|
54
|
+
hash: 3
|
55
55
|
segments:
|
56
56
|
- 2
|
57
|
-
-
|
57
|
+
- 3
|
58
58
|
- 0
|
59
|
-
version: 2.
|
59
|
+
version: 2.3.0
|
60
60
|
type: :runtime
|
61
61
|
version_requirements: *id002
|
62
62
|
- !ruby/object:Gem::Dependency
|
@@ -116,12 +116,12 @@ dependencies:
|
|
116
116
|
requirements:
|
117
117
|
- - ">="
|
118
118
|
- !ruby/object:Gem::Version
|
119
|
-
hash:
|
119
|
+
hash: 15
|
120
120
|
segments:
|
121
121
|
- 0
|
122
|
-
-
|
123
|
-
-
|
124
|
-
version: 0.
|
122
|
+
- 6
|
123
|
+
- 4
|
124
|
+
version: 0.6.4
|
125
125
|
type: :runtime
|
126
126
|
version_requirements: *id006
|
127
127
|
- !ruby/object:Gem::Dependency
|
@@ -132,12 +132,12 @@ dependencies:
|
|
132
132
|
requirements:
|
133
133
|
- - ~>
|
134
134
|
- !ruby/object:Gem::Version
|
135
|
-
hash:
|
135
|
+
hash: 19
|
136
136
|
segments:
|
137
137
|
- 0
|
138
|
-
- 2
|
139
138
|
- 3
|
140
|
-
|
139
|
+
- 0
|
140
|
+
version: 0.3.0
|
141
141
|
type: :runtime
|
142
142
|
version_requirements: *id007
|
143
143
|
- !ruby/object:Gem::Dependency
|
@@ -189,9 +189,23 @@ dependencies:
|
|
189
189
|
type: :runtime
|
190
190
|
version_requirements: *id010
|
191
191
|
- !ruby/object:Gem::Dependency
|
192
|
-
name:
|
192
|
+
name: syntax
|
193
193
|
prerelease: false
|
194
194
|
requirement: &id011 !ruby/object:Gem::Requirement
|
195
|
+
none: false
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
hash: 3
|
200
|
+
segments:
|
201
|
+
- 0
|
202
|
+
version: "0"
|
203
|
+
type: :runtime
|
204
|
+
version_requirements: *id011
|
205
|
+
- !ruby/object:Gem::Dependency
|
206
|
+
name: rspec
|
207
|
+
prerelease: false
|
208
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
195
209
|
none: false
|
196
210
|
requirements:
|
197
211
|
- - "="
|
@@ -203,11 +217,11 @@ dependencies:
|
|
203
217
|
- 0
|
204
218
|
version: 1.3.0
|
205
219
|
type: :development
|
206
|
-
version_requirements: *
|
220
|
+
version_requirements: *id012
|
207
221
|
- !ruby/object:Gem::Dependency
|
208
222
|
name: test-construct
|
209
223
|
prerelease: false
|
210
|
-
requirement: &
|
224
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
211
225
|
none: false
|
212
226
|
requirements:
|
213
227
|
- - ">="
|
@@ -219,11 +233,11 @@ dependencies:
|
|
219
233
|
- 0
|
220
234
|
version: 1.2.0
|
221
235
|
type: :development
|
222
|
-
version_requirements: *
|
236
|
+
version_requirements: *id013
|
223
237
|
- !ruby/object:Gem::Dependency
|
224
238
|
name: googlecharts
|
225
239
|
prerelease: false
|
226
|
-
requirement: &
|
240
|
+
requirement: &id014 !ruby/object:Gem::Requirement
|
227
241
|
none: false
|
228
242
|
requirements:
|
229
243
|
- - ">="
|
@@ -233,7 +247,7 @@ dependencies:
|
|
233
247
|
- 0
|
234
248
|
version: "0"
|
235
249
|
type: :development
|
236
|
-
version_requirements: *
|
250
|
+
version_requirements: *id014
|
237
251
|
description: Code metrics from Flog, Flay, RCov, Saikuro, Churn, Reek, Roodi, Rails' stats task and Rails Best Practices
|
238
252
|
email: jake.scruggs@gmail.com
|
239
253
|
executables: []
|
@@ -296,6 +310,7 @@ files:
|
|
296
310
|
- lib/templates/awesome/css/default.css
|
297
311
|
- lib/templates/awesome/css/integrity.css
|
298
312
|
- lib/templates/awesome/css/reset.css
|
313
|
+
- lib/templates/awesome/css/syntax.css
|
299
314
|
- lib/templates/awesome/flay.html.erb
|
300
315
|
- lib/templates/awesome/flog.html.erb
|
301
316
|
- lib/templates/awesome/hotspots.html.erb
|