metric_fu-Saikuro 1.1.1.0 → 1.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.
- checksums.yaml +7 -0
- data/README +52 -54
- data/lib/saikuro.rb +21 -1082
- data/lib/saikuro/base_formatter.rb +30 -0
- data/lib/saikuro/endable_parse_state.rb +12 -0
- data/lib/saikuro/filter.rb +22 -0
- data/lib/saikuro/html_stylesheet.rb +82 -0
- data/lib/saikuro/html_token_counter_formatter.rb +45 -0
- data/lib/saikuro/parse_block.rb +33 -0
- data/lib/saikuro/parse_class.rb +27 -0
- data/lib/saikuro/parse_comment.rb +15 -0
- data/lib/saikuro/parse_cond.rb +6 -0
- data/lib/saikuro/parse_def.rb +59 -0
- data/lib/saikuro/parse_do_cond.rb +24 -0
- data/lib/saikuro/parse_module.rb +6 -0
- data/lib/saikuro/parse_state.rb +286 -0
- data/lib/saikuro/parse_state_formatter.rb +27 -0
- data/lib/saikuro/parse_symbol.rb +13 -0
- data/lib/saikuro/result_index_generator.rb +87 -0
- data/lib/saikuro/saikuro_cmd_line_runner.rb +166 -0
- data/lib/saikuro/state_html_complexity_formatter.rb +38 -0
- data/lib/saikuro/token_counter.rb +51 -0
- data/lib/saikuro/token_counter_formatter.rb +35 -0
- data/lib/saikuro/usage.rb +0 -37
- data/lib/saikuro/version.rb +1 -1
- metadata +35 -24
- data/tests/large_example.rb +0 -70
- data/tests/samples.rb +0 -315
@@ -0,0 +1,27 @@
|
|
1
|
+
class ParseStateFormater < BaseFormater
|
2
|
+
|
3
|
+
def start(new_out=nil)
|
4
|
+
reset_data
|
5
|
+
@out = new_out if new_out
|
6
|
+
end
|
7
|
+
|
8
|
+
def end
|
9
|
+
end
|
10
|
+
|
11
|
+
def start_class_compute_state(type_name,name,complexity,lines)
|
12
|
+
@current = name
|
13
|
+
@out.puts "-- START #{name} --"
|
14
|
+
@out.puts "Type:#{type_name} Name:#{name} Complexity:#{complexity} Lines:#{lines}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def end_class_compute_state(name)
|
18
|
+
@out.puts "-- END #{name} --"
|
19
|
+
end
|
20
|
+
|
21
|
+
def def_compute_state(name,complexity,lines)
|
22
|
+
return if @filter.ignore?(complexity)
|
23
|
+
warn_error?(complexity, name)
|
24
|
+
@out.puts "Type:Def Name:#{name} Complexity:#{complexity} Lines:#{lines}"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class ParseSymbol < ParseState
|
2
|
+
def initialize(lexer, parent = nil)
|
3
|
+
super
|
4
|
+
STDOUT.puts "STARTING SYMBOL" if $VERBOSE
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse_token(token)
|
8
|
+
STDOUT.puts "Symbol's token is #{token.class}" if $VERBOSE
|
9
|
+
# Consume the next token and stop
|
10
|
+
@run = false
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ResultIndexGenerator
|
2
|
+
def summarize_errors_and_warnings(enw, header)
|
3
|
+
return "" if enw.empty?
|
4
|
+
f = StringIO.new
|
5
|
+
erval = Hash.new { |h,k| h[k] = Array.new }
|
6
|
+
wval = Hash.new { |h,k| h[k] = Array.new }
|
7
|
+
|
8
|
+
enw.each do |fname, warnings, errors|
|
9
|
+
errors.each do |c,m,v|
|
10
|
+
erval[v] << [fname, c, m]
|
11
|
+
end
|
12
|
+
warnings.each do |c,m,v|
|
13
|
+
wval[v] << [fname, c, m]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
f.puts "<h2 class=\"class_name\">Errors and Warnings</h2>"
|
18
|
+
f.puts "<table width=\"100%\" border=\"1\">"
|
19
|
+
f.puts header
|
20
|
+
|
21
|
+
f.puts print_summary_table_rows(erval, "error")
|
22
|
+
f.puts print_summary_table_rows(wval, "warning")
|
23
|
+
f.puts "</table>"
|
24
|
+
|
25
|
+
f.string
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_summary_table_rows(ewvals, klass_type)
|
29
|
+
f = StringIO.new
|
30
|
+
ewvals.sort { |a,b| b <=> a}.each do |v, vals|
|
31
|
+
vals.sort.each do |fname, c, m|
|
32
|
+
f.puts "<tr><td><a href=\"./#{fname}\">#{c}</a></td><td>#{m}</td>"
|
33
|
+
f.puts "<td class=\"#{klass_type}\">#{v}</td></tr>"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
f.string
|
37
|
+
end
|
38
|
+
|
39
|
+
def list_analyzed_files(files)
|
40
|
+
f = StringIO.new
|
41
|
+
f.puts "<h2 class=\"class_name\">Analyzed Files</h2>"
|
42
|
+
f.puts "<ul>"
|
43
|
+
files.each do |fname, warnings, errors|
|
44
|
+
readname = fname.split("_")[0...-1].join("_")
|
45
|
+
f.puts "<li>"
|
46
|
+
f.puts "<p class=\"file_name\"><a href=\"./#{fname}\">#{readname}</a>"
|
47
|
+
f.puts "</li>"
|
48
|
+
end
|
49
|
+
f.puts "</ul>"
|
50
|
+
f.string
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_index(files, filename, title, header)
|
54
|
+
return if files.empty?
|
55
|
+
|
56
|
+
File.open(filename,"w") do |f|
|
57
|
+
f.puts "<html><head><title>#{title}</title></head>"
|
58
|
+
f.puts "#{HTMLStyleSheet.style_sheet}\n<body>"
|
59
|
+
f.puts "<h1>#{title}</h1>"
|
60
|
+
|
61
|
+
enw = files.find_all { |fn,w,e| (!w.empty? || !e.empty?) }
|
62
|
+
|
63
|
+
f.puts summarize_errors_and_warnings(enw, header)
|
64
|
+
|
65
|
+
f.puts "<hr/>"
|
66
|
+
f.puts list_analyzed_files(files)
|
67
|
+
f.puts "</body></html>"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def write_cyclo_index(files, output_dir)
|
72
|
+
header = "<tr><th>Class</th><th>Method</th><th>Complexity</th></tr>"
|
73
|
+
write_index(files,
|
74
|
+
"#{output_dir}/index_cyclo.html",
|
75
|
+
"Index for cyclomatic complexity",
|
76
|
+
header)
|
77
|
+
end
|
78
|
+
|
79
|
+
def write_token_index(files, output_dir)
|
80
|
+
header = "<tr><th>File</th><th>Line #</th><th>Tokens</th></tr>"
|
81
|
+
write_index(files,
|
82
|
+
"#{output_dir}/index_token.html",
|
83
|
+
"Index for tokens per line",
|
84
|
+
header)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
class SaikuroCMDLineRunner
|
2
|
+
require 'stringio'
|
3
|
+
require 'getoptlong'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'find'
|
6
|
+
|
7
|
+
include ResultIndexGenerator
|
8
|
+
|
9
|
+
attr_accessor :formater, :output_dir, :comp_state, :comp_token,
|
10
|
+
:state_formater, :token_count_formater
|
11
|
+
def initialize
|
12
|
+
@opt = GetoptLong.new(
|
13
|
+
["-o","--output_directory", GetoptLong::REQUIRED_ARGUMENT],
|
14
|
+
["-h","--help", GetoptLong::NO_ARGUMENT],
|
15
|
+
["-f","--formater", GetoptLong::REQUIRED_ARGUMENT],
|
16
|
+
["-c","--cyclo", GetoptLong::NO_ARGUMENT],
|
17
|
+
["-t","--token", GetoptLong::NO_ARGUMENT],
|
18
|
+
["-y","--filter_cyclo", GetoptLong::REQUIRED_ARGUMENT],
|
19
|
+
["-k","--filter_token", GetoptLong::REQUIRED_ARGUMENT],
|
20
|
+
["-w","--warn_cyclo", GetoptLong::REQUIRED_ARGUMENT],
|
21
|
+
["-s","--warn_token", GetoptLong::REQUIRED_ARGUMENT],
|
22
|
+
["-e","--error_cyclo", GetoptLong::REQUIRED_ARGUMENT],
|
23
|
+
["-d","--error_token", GetoptLong::REQUIRED_ARGUMENT],
|
24
|
+
["-p","--parse_file", GetoptLong::REQUIRED_ARGUMENT],
|
25
|
+
["-i","--input_directory", GetoptLong::REQUIRED_ARGUMENT],
|
26
|
+
["-v","--verbose", GetoptLong::NO_ARGUMENT]
|
27
|
+
)
|
28
|
+
self.output_dir = "./"
|
29
|
+
self.formater = "html"
|
30
|
+
self.comp_state = self.comp_token = false
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_ruby_files(path)
|
34
|
+
files = Array.new
|
35
|
+
Find.find(path) do |f|
|
36
|
+
if !FileTest.directory?(f)
|
37
|
+
if f =~ /rb$/
|
38
|
+
files<< f
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
files
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
files = Array.new
|
47
|
+
state_filter = Filter.new(5)
|
48
|
+
token_filter = Filter.new(10, 25, 50)
|
49
|
+
|
50
|
+
parse_opts(state_filter, token_filter, files)
|
51
|
+
set_formatters(state_filter, token_filter)
|
52
|
+
|
53
|
+
idx_states, idx_tokens = analyze(files)
|
54
|
+
write_results(idx_states, idx_tokens)
|
55
|
+
end
|
56
|
+
|
57
|
+
def analyze(files)
|
58
|
+
Saikuro.analyze(files,
|
59
|
+
state_formater,
|
60
|
+
token_count_formater,
|
61
|
+
output_dir)
|
62
|
+
end
|
63
|
+
|
64
|
+
def write_results(idx_states, idx_tokens)
|
65
|
+
write_cyclo_index(idx_states, output_dir)
|
66
|
+
write_token_index(idx_tokens, output_dir)
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_opts(state_filter, token_filter, files)
|
70
|
+
@opt.each do |arg,val|
|
71
|
+
case arg
|
72
|
+
when "-o" then self.output_dir = val
|
73
|
+
when "-h" then usage('help')
|
74
|
+
when "-f" then self.formater = val
|
75
|
+
when "-c" then self.comp_state = true
|
76
|
+
when "-t" then self.comp_token = true
|
77
|
+
when "-k" then token_filter.limit = val.to_i
|
78
|
+
when "-s" then token_filter.warn = val.to_i
|
79
|
+
when "-d" then token_filter.error = val.to_i
|
80
|
+
when "-y" then state_filter.limit = val.to_i
|
81
|
+
when "-w" then state_filter.warn = val.to_i
|
82
|
+
when "-e" then state_filter.error = val.to_i
|
83
|
+
when "-p" then files<< val
|
84
|
+
when "-i" then files.concat(get_ruby_files(val))
|
85
|
+
when "-v"
|
86
|
+
STDOUT.puts "Verbose mode on"
|
87
|
+
$VERBOSE = true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
usage('no complexity token or state set') if no_complexity_token_or_state?
|
91
|
+
rescue => err
|
92
|
+
usage([err.class,err.message,err.backtrace[0..15]].join(', '))
|
93
|
+
end
|
94
|
+
|
95
|
+
def usage(message)
|
96
|
+
usage = <<-USAGE
|
97
|
+
== Usage
|
98
|
+
|
99
|
+
saikuro [ -h ] [-o output_directory] [-f type] [ -c, -t ]
|
100
|
+
[ -y, -w, -e, -k, -s, -d - number ] ( -p file | -i directory )
|
101
|
+
|
102
|
+
== Help
|
103
|
+
|
104
|
+
-o, --output_directory (directory) : A directory to ouput the results in.
|
105
|
+
The current directory is used if this option is not passed.
|
106
|
+
|
107
|
+
-h, --help : This help message.
|
108
|
+
|
109
|
+
-f, --formater (html | text) : The format to output the results in.
|
110
|
+
The default is html
|
111
|
+
|
112
|
+
-c, --cyclo : Compute the cyclomatic complexity of the input.
|
113
|
+
|
114
|
+
-t, --token : Count the number of tokens per line of the input.
|
115
|
+
|
116
|
+
-y, --filter_cyclo (number) : Filter the output to only include methods
|
117
|
+
whose cyclomatic complexity are greater than the passed number.
|
118
|
+
|
119
|
+
-w, --warn_cyclo (number) : Highlight with a warning methods whose
|
120
|
+
cyclomatic complexity are greather than or equal to the passed number.
|
121
|
+
|
122
|
+
|
123
|
+
-e, --error_cyclo (number) : Highligh with an error methods whose
|
124
|
+
cyclomatic complexity are greather than or equal to the passed number.
|
125
|
+
|
126
|
+
|
127
|
+
-k, --filter_token (number) : Filter the output to only include lines
|
128
|
+
whose token count are greater than the passed number.
|
129
|
+
|
130
|
+
|
131
|
+
-s, --warn_token (number) : Highlight with a warning lines whose
|
132
|
+
token count are greater than or equal to the passed number.
|
133
|
+
|
134
|
+
|
135
|
+
-d, --error_token (number) : Highlight with an error lines whose
|
136
|
+
token count are greater than or equal to the passed number.
|
137
|
+
|
138
|
+
|
139
|
+
-p, --parse_file (file) : A file to use as input.
|
140
|
+
|
141
|
+
-i, --input_directory (directory) : All ruby files found recursively
|
142
|
+
inside the directory are passed as input.
|
143
|
+
USAGE
|
144
|
+
STDOUT.puts usage
|
145
|
+
STDOUT.puts
|
146
|
+
STDOUT.puts message
|
147
|
+
end
|
148
|
+
|
149
|
+
def no_complexity_token_or_state?
|
150
|
+
!comp_state && !comp_token
|
151
|
+
end
|
152
|
+
|
153
|
+
def set_formatters(state_filter, token_filter)
|
154
|
+
if formater =~ /html/i
|
155
|
+
self.state_formater = StateHTMLComplexityFormater.new(STDOUT,state_filter)
|
156
|
+
self.token_count_formater = HTMLTokenCounterFormater.new(STDOUT,token_filter)
|
157
|
+
else
|
158
|
+
self.state_formater = ParseStateFormater.new(STDOUT,state_filter)
|
159
|
+
self.token_count_formater = TokenCounterFormater.new(STDOUT,token_filter)
|
160
|
+
end
|
161
|
+
|
162
|
+
self.state_formater = nil if !comp_state
|
163
|
+
self.token_count_formater = nil if !comp_token
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class StateHTMLComplexityFormater < ParseStateFormater
|
2
|
+
include HTMLStyleSheet
|
3
|
+
|
4
|
+
def start(new_out=nil)
|
5
|
+
reset_data
|
6
|
+
@out = new_out if new_out
|
7
|
+
@out.puts "<html><head><title>Cyclometric Complexity</title></head>"
|
8
|
+
@out.puts style_sheet
|
9
|
+
@out.puts "<body>"
|
10
|
+
end
|
11
|
+
|
12
|
+
def end
|
13
|
+
@out.puts "</body>"
|
14
|
+
@out.puts "</html>"
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_class_compute_state(type_name,name,complexity,lines)
|
18
|
+
@current = name
|
19
|
+
@out.puts "<div class=\"class_complexity\">"
|
20
|
+
@out.puts "<h2 class=\"class_name\">#{type_name} : #{name}</h2>"
|
21
|
+
@out.puts "<div class=\"class_total_complexity\">Total Complexity: #{complexity}</div>"
|
22
|
+
@out.puts "<div class=\"class_total_lines\">Total Lines: #{lines}</div>"
|
23
|
+
@out.puts "<table width=\"100%\" border=\"1\">"
|
24
|
+
@out.puts "<tr><th>Method</th><th>Complexity</th><th># Lines</th></tr>"
|
25
|
+
end
|
26
|
+
|
27
|
+
def end_class_compute_state(name)
|
28
|
+
@out.puts "</table>"
|
29
|
+
@out.puts "</div>"
|
30
|
+
end
|
31
|
+
|
32
|
+
def def_compute_state(name, complexity, lines)
|
33
|
+
return if @filter.ignore?(complexity)
|
34
|
+
klass = warn_error?(complexity, name)
|
35
|
+
@out.puts "<tr><td>#{name}</td><td#{klass}>#{complexity}</td><td>#{lines}</td></tr>"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Counts the number of tokens in each line.
|
2
|
+
class TokenCounter
|
3
|
+
include RubyToken
|
4
|
+
|
5
|
+
attr_reader :current_file
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@files = Hash.new
|
9
|
+
@tokens_per_line = Hash.new(0)
|
10
|
+
@current_file = ""
|
11
|
+
end
|
12
|
+
|
13
|
+
# Mark file to associate with the token count.
|
14
|
+
def set_current_file(file)
|
15
|
+
@current_file = file
|
16
|
+
@tokens_per_line = Hash.new(0)
|
17
|
+
@files[@current_file] = @tokens_per_line
|
18
|
+
end
|
19
|
+
|
20
|
+
# Iterate through all tracked files, passing the
|
21
|
+
# the provided formater the token counts.
|
22
|
+
def list_tokens_per_line(formater)
|
23
|
+
formater.start_count(@files.size)
|
24
|
+
@files.each do |fname, tok_per_line|
|
25
|
+
formater.start_file(fname)
|
26
|
+
tok_per_line.sort.each do |line,num|
|
27
|
+
formater.line_token_count(line,num)
|
28
|
+
end
|
29
|
+
formater.end_file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Count the token for the passed line.
|
34
|
+
def count_token(line_no,token)
|
35
|
+
case token
|
36
|
+
when TkSPACE, TkNL, TkRD_COMMENT
|
37
|
+
# Do not count these as tokens
|
38
|
+
when TkCOMMENT
|
39
|
+
# Ignore this only for comments in a statement?
|
40
|
+
# Ignore TkCOLON,TkCOLON2 and operators? like "." etc..
|
41
|
+
when TkRBRACK, TkRPAREN, TkRBRACE
|
42
|
+
# Ignore the closing of an array/index/hash/paren
|
43
|
+
# The opening is counted, but no more.
|
44
|
+
# Thus [], () {} is counted as 1 token not 2.
|
45
|
+
else
|
46
|
+
# may want to filter out comments...
|
47
|
+
@tokens_per_line[line_no] += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class TokenCounterFormater < BaseFormater
|
2
|
+
|
3
|
+
def start(new_out=nil)
|
4
|
+
reset_data
|
5
|
+
@out = new_out if new_out
|
6
|
+
@out.puts "Token Count"
|
7
|
+
end
|
8
|
+
|
9
|
+
def start_count(number_of_files)
|
10
|
+
@out.puts "Counting tokens for #{number_of_files} files."
|
11
|
+
end
|
12
|
+
|
13
|
+
def start_file(file_name)
|
14
|
+
@current = file_name
|
15
|
+
@out.puts "File:#{file_name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def line_token_count(line_number,number_of_tokens)
|
19
|
+
return if @filter.ignore?(number_of_tokens)
|
20
|
+
warn_error?(number_of_tokens, line_number)
|
21
|
+
@out.puts "Line:#{line_number} ; Tokens : #{number_of_tokens}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def end_file
|
25
|
+
@out.puts ""
|
26
|
+
end
|
27
|
+
|
28
|
+
def end_count
|
29
|
+
end
|
30
|
+
|
31
|
+
def end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
data/lib/saikuro/usage.rb
CHANGED
@@ -1,37 +0,0 @@
|
|
1
|
-
# This is a patch to RDoc so that when saikuro is installed as a
|
2
|
-
# RubyGem usage will read the proper file.
|
3
|
-
|
4
|
-
module RDoc
|
5
|
-
|
6
|
-
def RDoc.main_program_file=(file)
|
7
|
-
@@main_program_file = file
|
8
|
-
end
|
9
|
-
|
10
|
-
# Display usage
|
11
|
-
def RDoc.usage_no_exit(*args)
|
12
|
-
@@main_program_file ||= caller[-1].sub(/:\d+$/, '')
|
13
|
-
comment = File.open(@@main_program_file) do |file|
|
14
|
-
find_comment(file)
|
15
|
-
end
|
16
|
-
|
17
|
-
comment = comment.gsub(/^\s*#/, '')
|
18
|
-
|
19
|
-
markup = SM::SimpleMarkup.new
|
20
|
-
flow_convertor = SM::ToFlow.new
|
21
|
-
|
22
|
-
flow = markup.convert(comment, flow_convertor)
|
23
|
-
|
24
|
-
format = "plain"
|
25
|
-
|
26
|
-
unless args.empty?
|
27
|
-
flow = extract_sections(flow, args)
|
28
|
-
end
|
29
|
-
|
30
|
-
options = RI::Options.instance
|
31
|
-
if args = ENV["RI"]
|
32
|
-
options.parse(args.split)
|
33
|
-
end
|
34
|
-
formatter = options.formatter.new(options, "")
|
35
|
-
formatter.display_flow(flow)
|
36
|
-
end
|
37
|
-
end
|