flyerhzm-metric_fu 1.0.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 +164 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +25 -0
- data/README.textile +27 -0
- data/Rakefile +18 -0
- data/TODO +9 -0
- data/lib/base/base_template.rb +145 -0
- data/lib/base/configuration.rb +180 -0
- data/lib/base/generator.rb +167 -0
- data/lib/base/graph.rb +39 -0
- data/lib/base/md5_tracker.rb +52 -0
- data/lib/base/report.rb +100 -0
- data/lib/generators/churn.rb +90 -0
- data/lib/generators/flay.rb +34 -0
- data/lib/generators/flog.rb +167 -0
- data/lib/generators/rails_best_practices.rb +31 -0
- data/lib/generators/rcov.rb +87 -0
- data/lib/generators/reek.rb +64 -0
- data/lib/generators/roodi.rb +32 -0
- data/lib/generators/saikuro.rb +208 -0
- data/lib/generators/stats.rb +43 -0
- data/lib/graphs/engines/bluff.rb +98 -0
- data/lib/graphs/engines/gchart.rb +119 -0
- data/lib/graphs/flay_grapher.rb +20 -0
- data/lib/graphs/flog_grapher.rb +40 -0
- data/lib/graphs/grapher.rb +11 -0
- data/lib/graphs/rails_best_practices_grapher.rb +20 -0
- data/lib/graphs/rcov_grapher.rb +20 -0
- data/lib/graphs/reek_grapher.rb +32 -0
- data/lib/graphs/roodi_grapher.rb +20 -0
- data/lib/metric_fu.rb +31 -0
- data/lib/templates/awesome/awesome_template.rb +37 -0
- data/lib/templates/awesome/churn.html.erb +19 -0
- data/lib/templates/awesome/css/buttons.css +82 -0
- data/lib/templates/awesome/css/default.css +75 -0
- data/lib/templates/awesome/css/integrity.css +335 -0
- data/lib/templates/awesome/css/reset.css +7 -0
- data/lib/templates/awesome/flay.html.erb +33 -0
- data/lib/templates/awesome/flog.html.erb +53 -0
- data/lib/templates/awesome/index.html.erb +31 -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 +41 -0
- data/lib/templates/javascripts/bluff-min.js +1 -0
- data/lib/templates/javascripts/excanvas.js +19 -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 +53 -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 +161 -0
- data/spec/base/configuration_spec.rb +269 -0
- data/spec/base/generator_spec.rb +244 -0
- data/spec/base/graph_spec.rb +24 -0
- data/spec/base/md5_tracker_spec.rb +57 -0
- data/spec/base/report_spec.rb +139 -0
- data/spec/generators/churn_spec.rb +152 -0
- data/spec/generators/flay_spec.rb +104 -0
- data/spec/generators/flog_spec.rb +238 -0
- data/spec/generators/reek_spec.rb +126 -0
- data/spec/generators/saikuro_spec.rb +58 -0
- data/spec/generators/stats_spec.rb +74 -0
- data/spec/graphs/engines/bluff_spec.rb +15 -0
- data/spec/graphs/engines/gchart_spec.rb +15 -0
- data/spec/graphs/flay_grapher_spec.rb +37 -0
- data/spec/graphs/flog_grapher_spec.rb +45 -0
- data/spec/graphs/rcov_grapher_spec.rb +37 -0
- data/spec/graphs/reek_grapher_spec.rb +46 -0
- data/spec/graphs/roodi_grapher_spec.rb +37 -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 +7844 -0
- data/spec/spec.opts +8 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/metric_fu.rake +22 -0
- data/vendor/_fonts/monaco.ttf +0 -0
- data/vendor/saikuro/saikuro.rb +1219 -0
- metadata +234 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module MetricFu
|
2
|
+
|
3
|
+
class Flay < Generator
|
4
|
+
|
5
|
+
def self.verify_dependencies!
|
6
|
+
`flay --help`
|
7
|
+
raise 'sudo gem install flay # if you want the flay tasks' unless $?.success?
|
8
|
+
end
|
9
|
+
|
10
|
+
def emit
|
11
|
+
files_to_flay = MetricFu.flay[:dirs_to_flay].map{|dir| Dir[File.join(dir, "**/*.rb")] }
|
12
|
+
files = remove_excluded_files(files_to_flay.flatten)
|
13
|
+
@output = `flay #{files.join(" ")}`
|
14
|
+
end
|
15
|
+
|
16
|
+
def analyze
|
17
|
+
@matches = @output.chomp.split("\n\n").map{|m| m.split("\n ") }
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
target = []
|
22
|
+
total_score = @matches.shift.first.split('=').last.strip
|
23
|
+
@matches.each do |problem|
|
24
|
+
reason = problem.shift.strip
|
25
|
+
lines_info = problem.map do |full_line|
|
26
|
+
name, line = full_line.split(":")
|
27
|
+
{:name => name.strip, :line => line.strip}
|
28
|
+
end
|
29
|
+
target << [:reason => reason, :matches => lines_info]
|
30
|
+
end
|
31
|
+
{:flay => {:total_score => total_score, :matches => target.flatten}}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module MetricFu
|
4
|
+
|
5
|
+
class Flog < Generator
|
6
|
+
attr_reader :pages
|
7
|
+
|
8
|
+
def self.verify_dependencies!
|
9
|
+
`flog --help`
|
10
|
+
raise 'sudo gem install flog # if you want the flog tasks' unless $?.success?
|
11
|
+
end
|
12
|
+
|
13
|
+
SCORE_FORMAT = "%0.2f"
|
14
|
+
METHOD_LINE_REGEX = /(\d+\.\d+):\s+([A-Za-z:]+#.*)/
|
15
|
+
OPERATOR_LINE_REGEX = /\s*(\d+\.\d+):\s(.*)$/
|
16
|
+
|
17
|
+
def emit
|
18
|
+
metric_dir = MetricFu::Flog.metric_directory
|
19
|
+
MetricFu.flog[:dirs_to_flog].each do |directory|
|
20
|
+
directory = "." if directory=='./'
|
21
|
+
files = Dir.glob("#{directory}/**/*.rb")
|
22
|
+
files = remove_excluded_files(files)
|
23
|
+
files.each do |filename|
|
24
|
+
output_dir = "#{metric_dir}/#{filename.split("/")[0..-2].join("/")}"
|
25
|
+
mkdir_p(output_dir, :verbose => false) unless File.directory?(output_dir)
|
26
|
+
pathname = Pathname.new(filename)
|
27
|
+
if MetricFu::MD5Tracker.file_changed?(filename, metric_dir)
|
28
|
+
base_name = pathname.basename.to_s.gsub(/\..*$/,'.txt')
|
29
|
+
editted_filename = File.join(pathname.dirname.to_s, base_name)
|
30
|
+
`flog -ad #{filename} > #{metric_dir}/#{editted_filename}`
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
if RUBY_PLATFORM =~ /java/
|
36
|
+
puts 'running in jruby - flog tasks not available'
|
37
|
+
else
|
38
|
+
puts 'sudo gem install flog # if you want the flog tasks'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse(text)
|
43
|
+
summary, methods_summary = text.split "\n\n"
|
44
|
+
return unless summary
|
45
|
+
score, average = summary.split("\n").map {|line| line[OPERATOR_LINE_REGEX, 1]}
|
46
|
+
return nil unless score && methods_summary
|
47
|
+
page = Flog::Page.new(score, average)
|
48
|
+
methods_summary.each_line do |method_line|
|
49
|
+
if match = method_line.match(METHOD_LINE_REGEX)
|
50
|
+
page.scanned_methods << ScannedMethod.new(match[2], match[1])
|
51
|
+
elsif match = method_line.match(OPERATOR_LINE_REGEX)
|
52
|
+
return if page.scanned_methods.empty?
|
53
|
+
page.scanned_methods.last.operators << Operator.new(match[1], match[2])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
page
|
57
|
+
end
|
58
|
+
|
59
|
+
def analyze
|
60
|
+
@pages = []
|
61
|
+
flog_results.each do |path|
|
62
|
+
page = parse(open(path, "r") { |f| f.read })
|
63
|
+
if page
|
64
|
+
page.path = path.sub(metric_directory, "").sub(".txt", ".rb")
|
65
|
+
#don't include old cached flog results for files that no longer exist.
|
66
|
+
if is_file_current?(page.path.to_s)
|
67
|
+
@pages << page
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_h
|
74
|
+
number_of_methods = @pages.inject(0) {|count, page| count += page.scanned_methods.size}
|
75
|
+
total_flog_score = @pages.inject(0) {|total, page| total += page.score}
|
76
|
+
sorted_pages = @pages.sort_by {|page| page.highest_score }.reverse
|
77
|
+
{:flog => { :total => total_flog_score,
|
78
|
+
:average => average_score(total_flog_score, number_of_methods),
|
79
|
+
:pages => sorted_pages.map {|page| page.to_h}}}
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def is_file_current?(pathname)
|
85
|
+
pathname = pathname.gsub(/^\//,'')
|
86
|
+
local_pathname = "./#{pathname}"
|
87
|
+
exists = false
|
88
|
+
|
89
|
+
MetricFu.flog[:dirs_to_flog].each do |directory|
|
90
|
+
directory = "." if directory=='./'
|
91
|
+
files = Dir.glob("#{directory}/**/*.rb")
|
92
|
+
if files.include?(pathname) || files.include?(local_pathname)
|
93
|
+
exists = true
|
94
|
+
break
|
95
|
+
end
|
96
|
+
end
|
97
|
+
exists
|
98
|
+
end
|
99
|
+
|
100
|
+
def average_score(total_flog_score, number_of_methods)
|
101
|
+
return 0 if total_flog_score == 0
|
102
|
+
round_to_tenths(total_flog_score/number_of_methods)
|
103
|
+
end
|
104
|
+
|
105
|
+
def flog_results
|
106
|
+
Dir.glob("#{metric_directory}/**/*.txt")
|
107
|
+
end
|
108
|
+
|
109
|
+
class Operator
|
110
|
+
attr_accessor :score, :operator
|
111
|
+
|
112
|
+
def initialize(score, operator)
|
113
|
+
@score = score.to_f
|
114
|
+
@operator = operator
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_h
|
118
|
+
{:score => @score, :operator => @operator}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class ScannedMethod
|
123
|
+
attr_accessor :name, :score, :operators
|
124
|
+
|
125
|
+
def initialize(name, score, operators = [])
|
126
|
+
@name = name
|
127
|
+
@score = score.to_f
|
128
|
+
@operators = operators
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_h
|
132
|
+
{:name => @name,
|
133
|
+
:score => @score,
|
134
|
+
:operators => @operators.map {|o| o.to_h}}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
class Flog::Page < MetricFu::Generator
|
141
|
+
attr_accessor :path, :score, :scanned_methods, :average_score
|
142
|
+
|
143
|
+
def initialize(score, average_score, scanned_methods = [])
|
144
|
+
@score = score.to_f
|
145
|
+
@scanned_methods = scanned_methods
|
146
|
+
@average_score = average_score.to_f
|
147
|
+
end
|
148
|
+
|
149
|
+
def filename
|
150
|
+
File.basename(path, ".txt")
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_h
|
154
|
+
{:score => @score,
|
155
|
+
:scanned_methods => @scanned_methods.map {|sm| sm.to_h},
|
156
|
+
:highest_score => highest_score,
|
157
|
+
:average_score => average_score,
|
158
|
+
:path => path}
|
159
|
+
end
|
160
|
+
|
161
|
+
def highest_score
|
162
|
+
scanned_methods.inject(0) do |highest, m|
|
163
|
+
m.score > highest ? m.score : highest
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MetricFu
|
2
|
+
class RailsBestPractices < Generator
|
3
|
+
|
4
|
+
def self.verify_dependencies!
|
5
|
+
`rails_best_practices --help`
|
6
|
+
raise 'sudo gem install rails_best_practices # if you want the rails_best_practices tasks' unless $?.success?
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def emit
|
11
|
+
@output = `rails_best_practices .`
|
12
|
+
end
|
13
|
+
|
14
|
+
def analyze
|
15
|
+
@matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
|
16
|
+
total = @matches.pop
|
17
|
+
2.times { @matches.pop } # ignore wiki link
|
18
|
+
@matches.reject! {|array| array.empty? }
|
19
|
+
@matches.map! do |match|
|
20
|
+
file, line = match[0].split(':')
|
21
|
+
problem = match[1]
|
22
|
+
{:file => file, :line => line, :problem => problem}
|
23
|
+
end
|
24
|
+
@rails_best_practices_results = {:total => total, :problems => @matches}
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
{:rails_best_practices => @rails_best_practices_results}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module MetricFu
|
4
|
+
|
5
|
+
class Rcov < Generator
|
6
|
+
NEW_FILE_MARKER = ("=" * 80) + "\n"
|
7
|
+
|
8
|
+
def self.verify_dependencies!
|
9
|
+
`rcov --help`
|
10
|
+
unless $?.success?
|
11
|
+
if RUBY_PLATFORM =~ /java/
|
12
|
+
raise 'running in jruby - rcov tasks not available'
|
13
|
+
else
|
14
|
+
raise 'sudo gem install rcov # if you want the rcov tasks'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Line
|
20
|
+
attr_accessor :content, :was_run
|
21
|
+
|
22
|
+
def initialize(content, was_run)
|
23
|
+
@content = content
|
24
|
+
@was_run = was_run
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
{:content => @content, :was_run => @was_run}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def emit
|
33
|
+
begin
|
34
|
+
FileUtils.rm_rf(MetricFu::Rcov.metric_directory, :verbose => false)
|
35
|
+
Dir.mkdir(MetricFu::Rcov.metric_directory)
|
36
|
+
test_files = FileList[*MetricFu.rcov[:test_files]].join(' ')
|
37
|
+
rcov_opts = MetricFu.rcov[:rcov_opts].join(' ')
|
38
|
+
output = ">> #{MetricFu::Rcov.metric_directory}/rcov.txt"
|
39
|
+
`rcov #{test_files} #{rcov_opts} #{output}`
|
40
|
+
rescue LoadError
|
41
|
+
if RUBY_PLATFORM =~ /java/
|
42
|
+
puts 'running in jruby - rcov tasks not available'
|
43
|
+
else
|
44
|
+
puts 'sudo gem install rcov # if you want the rcov tasks'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def analyze
|
51
|
+
output = File.open(MetricFu::Rcov.metric_directory + '/rcov.txt').read
|
52
|
+
output = output.split(NEW_FILE_MARKER)
|
53
|
+
# Throw away the first entry - it's the execution time etc.
|
54
|
+
output.shift
|
55
|
+
files = {}
|
56
|
+
output.each_slice(2) {|out| files[out.first.strip] = out.last}
|
57
|
+
files.each_pair {|fname, content| files[fname] = content.split("\n") }
|
58
|
+
files.each_pair do |fname, content|
|
59
|
+
content.map! do |raw_line|
|
60
|
+
if raw_line.match(/^!!/)
|
61
|
+
line = Line.new(raw_line.gsub('!!', ' '), false).to_h
|
62
|
+
else
|
63
|
+
line = Line.new(raw_line, true).to_h
|
64
|
+
end
|
65
|
+
end
|
66
|
+
files[fname] = {:lines => content}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Calculate the percentage of lines run in each file
|
70
|
+
@global_total_lines = 0
|
71
|
+
@global_total_lines_run = 0
|
72
|
+
files.each_pair do |fname, content|
|
73
|
+
lines = content[:lines]
|
74
|
+
@global_total_lines_run += lines_run = lines.find_all {|line| line[:was_run] == true }.length
|
75
|
+
@global_total_lines += total_lines = lines.length
|
76
|
+
percent_run = ((lines_run.to_f / total_lines.to_f) * 100).round
|
77
|
+
files[fname][:percent_run] = percent_run
|
78
|
+
end
|
79
|
+
@rcov = files
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_h
|
83
|
+
global_percent_run = ((@global_total_lines_run.to_f / @global_total_lines.to_f) * 100)
|
84
|
+
{:rcov => @rcov.merge({:global_percent_run => round_to_tenths(global_percent_run) })}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module MetricFu
|
2
|
+
|
3
|
+
class Reek < Generator
|
4
|
+
REEK_REGEX = /^(\S+) (.*) \((.*)\)$/
|
5
|
+
|
6
|
+
def self.verify_dependencies!
|
7
|
+
`reek --help`
|
8
|
+
raise 'sudo gem install reek # if you want the reek tasks' unless $?.success?
|
9
|
+
end
|
10
|
+
|
11
|
+
def emit
|
12
|
+
files_to_reek = MetricFu.reek[:dirs_to_reek].map{|dir| Dir[File.join(dir, "**/*.rb")] }
|
13
|
+
files = remove_excluded_files(files_to_reek.flatten)
|
14
|
+
@output = `reek #{files.join(" ")}`
|
15
|
+
@output = massage_for_reek_12 if reek_12?
|
16
|
+
end
|
17
|
+
|
18
|
+
def reek_12?
|
19
|
+
return false if @output.length == 0
|
20
|
+
(@output =~ /^"/) != 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def massage_for_reek_12
|
24
|
+
section_break = ''
|
25
|
+
@output.split("\n").map do |line|
|
26
|
+
case line
|
27
|
+
when /^ /
|
28
|
+
"#{line.gsub(/^ /, '')}\n"
|
29
|
+
else
|
30
|
+
parts = line.split(" -- ")
|
31
|
+
if parts[1].nil?
|
32
|
+
"#{line}\n"
|
33
|
+
else
|
34
|
+
warnings = parts[1].gsub(/ \(.*\):/, ':')
|
35
|
+
result = "#{section_break}\"#{parts[0]}\" -- #{warnings}\n"
|
36
|
+
section_break = "\n"
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end.join
|
41
|
+
end
|
42
|
+
|
43
|
+
def analyze
|
44
|
+
@matches = @output.chomp.split("\n\n").map{|m| m.split("\n") }
|
45
|
+
@matches = @matches.map do |match|
|
46
|
+
file_path = match.shift.split('--').first
|
47
|
+
file_path = file_path.gsub('"', ' ').strip
|
48
|
+
code_smells = match.map do |smell|
|
49
|
+
match_object = smell.match(REEK_REGEX)
|
50
|
+
next unless match_object
|
51
|
+
{:method => match_object[1].strip,
|
52
|
+
:message => match_object[2].strip,
|
53
|
+
:type => match_object[3].strip}
|
54
|
+
end.compact
|
55
|
+
{:file_path => file_path, :code_smells => code_smells}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_h
|
60
|
+
{:reek => {:matches => @matches}}
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module MetricFu
|
2
|
+
class Roodi < Generator
|
3
|
+
|
4
|
+
def self.verify_dependencies!
|
5
|
+
`roodi --help`
|
6
|
+
raise 'sudo gem install roodi # if you want the roodi tasks' unless $?.success?
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def emit
|
11
|
+
files_to_analyze = MetricFu.roodi[:dirs_to_roodi].map{|dir| Dir[File.join(dir, "**/*.rb")] }
|
12
|
+
files = remove_excluded_files(files_to_analyze.flatten)
|
13
|
+
@output = `roodi #{files.join(" ")}`
|
14
|
+
end
|
15
|
+
|
16
|
+
def analyze
|
17
|
+
@matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
|
18
|
+
total = @matches.pop
|
19
|
+
@matches.reject! {|array| array.empty? }
|
20
|
+
@matches.map! do |match|
|
21
|
+
file, line = match[0].split(':')
|
22
|
+
problem = match[1]
|
23
|
+
{:file => file, :line => line, :problem => problem}
|
24
|
+
end
|
25
|
+
@roodi_results = {:total => total, :problems => @matches}
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
{:roodi => @roodi_results}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module MetricFu
|
2
|
+
|
3
|
+
class Saikuro < Generator
|
4
|
+
|
5
|
+
def emit
|
6
|
+
relative_path = [File.dirname(__FILE__), '..', '..',
|
7
|
+
'vendor', 'saikuro', 'saikuro.rb']
|
8
|
+
saikuro = File.expand_path(File.join(relative_path))
|
9
|
+
|
10
|
+
MetricFu.saikuro[:input_directory] = format_directories
|
11
|
+
|
12
|
+
options_string = MetricFu.saikuro.inject("") do |options, option|
|
13
|
+
options + "--#{option.join(' ')} "
|
14
|
+
end
|
15
|
+
|
16
|
+
sh %{ruby "#{saikuro}" #{options_string}} do |ok, response|
|
17
|
+
unless ok
|
18
|
+
puts "Saikuro failed with exit status: #{response.exitstatus}"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def format_directories
|
25
|
+
dirs = MetricFu.saikuro[:input_directory].join(" | ")
|
26
|
+
"\"#{dirs}\""
|
27
|
+
end
|
28
|
+
|
29
|
+
def analyze
|
30
|
+
@files = []
|
31
|
+
saikuro_results.each do |path|
|
32
|
+
if Saikuro::SFile.is_valid_text_file?(path)
|
33
|
+
file = Saikuro::SFile.new(path)
|
34
|
+
if file
|
35
|
+
@files << file
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
@files = @files.sort_by do |file|
|
40
|
+
file.elements.
|
41
|
+
max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
|
42
|
+
complexity.to_i
|
43
|
+
end
|
44
|
+
@files.reverse!
|
45
|
+
klasses = []
|
46
|
+
@files.each {|f| klasses << f.elements}
|
47
|
+
klasses.flatten!
|
48
|
+
@classes = klasses.sort_by {|k| k.complexity.to_i}
|
49
|
+
@classes.reverse!
|
50
|
+
meths = []
|
51
|
+
@files.each {|f|
|
52
|
+
f.elements.each {|el|
|
53
|
+
el.defs.each {|defn|
|
54
|
+
defn.name = "#{el.name}##{defn.name}"
|
55
|
+
meths << defn}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
meths = meths.sort_by {|meth| meth.complexity.to_i}
|
59
|
+
@meths = meths.reverse
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_h
|
63
|
+
files = @files.map do |file|
|
64
|
+
my_file = file.to_h
|
65
|
+
my_file[:filename] = file.filename
|
66
|
+
my_file
|
67
|
+
end
|
68
|
+
{:saikuro => {:files => files,
|
69
|
+
:classes => @classes.map {|c| c.to_h},
|
70
|
+
:methods => @meths.map {|m| m.to_h}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def saikuro_results
|
76
|
+
Dir.glob("#{metric_directory}/**/*.html")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Saikuro::SFile
|
81
|
+
|
82
|
+
attr_reader :elements
|
83
|
+
|
84
|
+
def initialize(path)
|
85
|
+
@path = path
|
86
|
+
@file_handle = File.open(@path, "r")
|
87
|
+
@elements = []
|
88
|
+
get_elements
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.is_valid_text_file?(path)
|
92
|
+
File.open(path, "r") do |f|
|
93
|
+
if f.eof? || !f.readline.match(/--/)
|
94
|
+
return false
|
95
|
+
else
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def filename
|
102
|
+
File.basename(@path, '_cyclo.html')
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_h
|
106
|
+
merge_classes
|
107
|
+
{:classes => @elements}
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_elements
|
111
|
+
begin
|
112
|
+
while (line = @file_handle.readline) do
|
113
|
+
return [] if line.nil? || line !~ /\S/
|
114
|
+
element ||= nil
|
115
|
+
if line.match /START/
|
116
|
+
unless element.nil?
|
117
|
+
@elements << element
|
118
|
+
element = nil
|
119
|
+
end
|
120
|
+
line = @file_handle.readline
|
121
|
+
element = Saikuro::ParsingElement.new(line)
|
122
|
+
elsif line.match /END/
|
123
|
+
@elements << element if element
|
124
|
+
element = nil
|
125
|
+
else
|
126
|
+
element << line if element
|
127
|
+
end
|
128
|
+
end
|
129
|
+
rescue EOFError
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
def merge_classes
|
136
|
+
new_elements = []
|
137
|
+
get_class_names.each do |target_class|
|
138
|
+
elements = @elements.find_all {|el| el.name == target_class }
|
139
|
+
complexity = 0
|
140
|
+
lines = 0
|
141
|
+
defns = []
|
142
|
+
elements.each do |el|
|
143
|
+
complexity += el.complexity.to_i
|
144
|
+
lines += el.lines.to_i
|
145
|
+
defns << el.defs
|
146
|
+
end
|
147
|
+
|
148
|
+
new_element = {:class_name => target_class,
|
149
|
+
:complexity => complexity,
|
150
|
+
:lines => lines,
|
151
|
+
:methods => defns.flatten.map {|d| d.to_h}}
|
152
|
+
new_element[:methods] = new_element[:methods].
|
153
|
+
sort_by {|x| x[:complexity] }.
|
154
|
+
reverse
|
155
|
+
|
156
|
+
new_elements << new_element
|
157
|
+
end
|
158
|
+
@elements = new_elements if new_elements
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_class_names
|
162
|
+
class_names = []
|
163
|
+
@elements.each do |element|
|
164
|
+
unless class_names.include?(element.name)
|
165
|
+
class_names << element.name
|
166
|
+
end
|
167
|
+
end
|
168
|
+
class_names
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
class Saikuro::ParsingElement
|
174
|
+
TYPE_REGEX=/Type:(.*) Name/
|
175
|
+
NAME_REGEX=/Name:(.*) Complexity/
|
176
|
+
COMPLEXITY_REGEX=/Complexity:(.*) Lines/
|
177
|
+
LINES_REGEX=/Lines:(.*)/
|
178
|
+
|
179
|
+
attr_reader :complexity, :lines, :defs, :element_type
|
180
|
+
attr_accessor :name
|
181
|
+
|
182
|
+
def initialize(line)
|
183
|
+
@line = line
|
184
|
+
@element_type = line.match(TYPE_REGEX)[1].strip
|
185
|
+
@name = line.match(NAME_REGEX)[1].strip
|
186
|
+
@complexity = line.match(COMPLEXITY_REGEX)[1].strip
|
187
|
+
@lines = line.match(LINES_REGEX)[1].strip
|
188
|
+
@defs = []
|
189
|
+
end
|
190
|
+
|
191
|
+
def <<(line)
|
192
|
+
@defs << Saikuro::ParsingElement.new(line)
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_h
|
196
|
+
base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
|
197
|
+
unless @defs.empty?
|
198
|
+
defs = @defs.map do |my_def|
|
199
|
+
my_def = my_def.to_h
|
200
|
+
my_def.delete(:defs)
|
201
|
+
my_def
|
202
|
+
end
|
203
|
+
base[:defs] = defs
|
204
|
+
end
|
205
|
+
return base
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|