metric_fu-Saikuro 1.1.1.0 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|