flog 3.2.3 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +13 -0
- data/Manifest.txt +2 -0
- data/Rakefile +17 -6
- data/bin/flog +4 -5
- data/lib/flog.rb +105 -259
- data/lib/flog_cli.rb +237 -0
- data/lib/flog_task.rb +26 -0
- data/lib/gauntlet_flog.rb +5 -3
- data/test/test_flog.rb +77 -214
- data/test/test_flog_cli.rb +202 -0
- metadata +16 -13
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/History.txt
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
=== 4.0.0 / 2013-04-18
|
2
|
+
|
3
|
+
* 1 major enhancement:
|
4
|
+
|
5
|
+
* Renamed Flog#methods to #method_scores. (makaroni4)
|
6
|
+
|
7
|
+
* 4 minor enhancements:
|
8
|
+
|
9
|
+
* Added accessors for methods, scores. Now available for CIs! (makaroni4)
|
10
|
+
* Refactored calculations down to 2 methods: total_score and totals. (makaroni4)
|
11
|
+
* Refactored to #calculate, #threshold. (makaroni4)
|
12
|
+
* Track stack of nested sclass scopes for parser (pithyless)
|
13
|
+
|
1
14
|
=== 3.2.3 / 2013-03-21
|
2
15
|
|
3
16
|
* 1 bug fix:
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -25,14 +25,25 @@ Hoe.spec 'flog' do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
task :debug do
|
28
|
-
require "
|
28
|
+
require "flog_cli"
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
class FlogCLI
|
31
|
+
def_delegators :@flog, :flog_ruby
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
file = ENV["F"]
|
35
|
+
ruby = ENV["R"]
|
36
|
+
|
37
|
+
flog = FlogCLI.new :parser => RubyParser
|
38
|
+
|
39
|
+
if file then
|
40
|
+
flog.flog file
|
41
|
+
else
|
42
|
+
flog.flog_ruby ruby, "-"
|
43
|
+
flog.calculate_total_scores
|
44
|
+
end
|
45
|
+
|
46
|
+
flog.report
|
36
47
|
end
|
37
48
|
|
38
49
|
# vim: syntax=ruby
|
data/bin/flog
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
#!/usr/local/bin/ruby -w
|
2
2
|
|
3
|
-
require
|
4
|
-
require 'flog'
|
3
|
+
require "flog_cli"
|
5
4
|
|
6
|
-
|
5
|
+
FlogCLI.load_plugins
|
7
6
|
|
8
|
-
options =
|
7
|
+
options = FlogCLI.parse_options ARGV
|
9
8
|
|
10
9
|
ARGV << "-" if ARGV.empty?
|
11
10
|
|
12
|
-
flogger =
|
11
|
+
flogger = FlogCLI.new options
|
13
12
|
flogger.flog ARGV
|
14
13
|
flogger.report
|
15
14
|
|
data/lib/flog.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require 'optparse'
|
5
|
-
require 'timeout'
|
1
|
+
require "sexp_processor"
|
2
|
+
require "ruby_parser"
|
3
|
+
require "timeout"
|
6
4
|
|
7
5
|
class File
|
8
|
-
RUBY19 = "<3".respond_to? :encoding unless defined? RUBY19
|
6
|
+
RUBY19 = "<3".respond_to? :encoding unless defined? RUBY19 # :nodoc:
|
9
7
|
|
10
8
|
class << self
|
11
9
|
alias :binread :read unless RUBY19
|
@@ -13,10 +11,21 @@ class File
|
|
13
11
|
end
|
14
12
|
|
15
13
|
class Flog < SexpProcessor
|
16
|
-
VERSION = "
|
14
|
+
VERSION = "4.0.0" # :nodoc:
|
15
|
+
|
16
|
+
##
|
17
|
+
# Cut off point where the report should stop unless --all given.
|
17
18
|
|
18
19
|
THRESHOLD = 0.60
|
20
|
+
|
21
|
+
##
|
22
|
+
# The scoring system hash. Maps node type to score.
|
23
|
+
|
19
24
|
SCORES = Hash.new 1
|
25
|
+
|
26
|
+
##
|
27
|
+
# Names of nodes that branch.
|
28
|
+
|
20
29
|
BRANCHING = [ :and, :case, :else, :if, :or, :rescue, :until, :when, :while ]
|
21
30
|
|
22
31
|
##
|
@@ -78,136 +87,13 @@ class Flog < SexpProcessor
|
|
78
87
|
@@no_class = :main
|
79
88
|
@@no_method = :none
|
80
89
|
|
90
|
+
# :stopdoc:
|
81
91
|
attr_accessor :multiplier
|
82
92
|
attr_reader :calls, :option, :class_stack, :method_stack, :mass, :sclass
|
83
|
-
attr_reader :method_locations
|
84
|
-
|
85
|
-
def self.plugins
|
86
|
-
@plugins ||= {}
|
87
|
-
end
|
88
|
-
|
89
|
-
# TODO: I think I want to do this more like hoe's plugin system. Generalize?
|
90
|
-
def self.load_plugins
|
91
|
-
loaded, found = {}, {}
|
92
|
-
|
93
|
-
Gem.find_files("flog/*.rb").reverse.each do |path|
|
94
|
-
found[File.basename(path, ".rb").intern] = path
|
95
|
-
end
|
96
|
-
|
97
|
-
found.each do |name, plugin|
|
98
|
-
next if loaded[name]
|
99
|
-
begin
|
100
|
-
warn "loading #{plugin}" # if $DEBUG
|
101
|
-
loaded[name] = load plugin
|
102
|
-
rescue LoadError => e
|
103
|
-
warn "error loading #{plugin.inspect}: #{e.message}. skipping..."
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
self.plugins.merge loaded
|
108
|
-
|
109
|
-
names = Flog.constants.map {|s| s.to_s}.reject {|n| n =~ /^[A-Z_]+$/}
|
110
|
-
|
111
|
-
names.each do |name|
|
112
|
-
# next unless Hoe.plugins.include? name.downcase.intern
|
113
|
-
mod = Flog.const_get(name)
|
114
|
-
next if Class === mod
|
115
|
-
warn "extend #{mod}" if $DEBUG
|
116
|
-
# self.extend mod
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# REFACTOR: from flay
|
121
|
-
def self.expand_dirs_to_files *dirs
|
122
|
-
extensions = %w[rb rake]
|
123
|
-
|
124
|
-
dirs.flatten.map { |p|
|
125
|
-
if File.directory? p then
|
126
|
-
Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")]
|
127
|
-
else
|
128
|
-
p
|
129
|
-
end
|
130
|
-
}.flatten.sort
|
131
|
-
end
|
132
|
-
|
133
|
-
def self.parse_options args = ARGV
|
134
|
-
option = {
|
135
|
-
:quiet => false,
|
136
|
-
:continue => false,
|
137
|
-
:parser => RubyParser,
|
138
|
-
}
|
139
|
-
|
140
|
-
OptionParser.new do |opts|
|
141
|
-
opts.separator "Standard options:"
|
142
|
-
|
143
|
-
opts.on("-a", "--all", "Display all flog results, not top 60%.") do
|
144
|
-
option[:all] = true
|
145
|
-
end
|
146
|
-
|
147
|
-
opts.on("-b", "--blame", "Include blame information for methods.") do
|
148
|
-
option[:blame] = true
|
149
|
-
end
|
150
|
-
|
151
|
-
opts.on("-c", "--continue", "Continue despite syntax errors.") do
|
152
|
-
option[:continue] = true
|
153
|
-
end
|
154
|
-
|
155
|
-
opts.on("-d", "--details", "Show method details.") do
|
156
|
-
option[:details] = true
|
157
|
-
end
|
158
|
-
|
159
|
-
opts.on("-g", "--group", "Group and sort by class.") do
|
160
|
-
option[:group] = true
|
161
|
-
end
|
162
|
-
|
163
|
-
opts.on("-h", "--help", "Show this message.") do
|
164
|
-
puts opts
|
165
|
-
exit
|
166
|
-
end
|
167
|
-
|
168
|
-
opts.on("-I dir1,dir2,dir3", Array, "Add to LOAD_PATH.") do |dirs|
|
169
|
-
dirs.each do |dir|
|
170
|
-
$: << dir
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
opts.on("-m", "--methods-only", "Skip code outside of methods.") do
|
175
|
-
option[:methods] = true
|
176
|
-
end
|
177
|
-
|
178
|
-
opts.on("-q", "--quiet", "Don't show parse errors.") do
|
179
|
-
option[:quiet] = true
|
180
|
-
end
|
181
|
-
|
182
|
-
opts.on("-s", "--score", "Display total score only.") do
|
183
|
-
option[:score] = true
|
184
|
-
end
|
185
|
-
|
186
|
-
opts.on("-v", "--verbose", "Display progress during processing.") do
|
187
|
-
option[:verbose] = true
|
188
|
-
end
|
189
|
-
|
190
|
-
opts.on("--18", "Use a ruby 1.8 parser.") do
|
191
|
-
option[:parser] = Ruby18Parser
|
192
|
-
end
|
193
|
-
|
194
|
-
opts.on("--19", "Use a ruby 1.9 parser.") do
|
195
|
-
option[:parser] = Ruby19Parser
|
196
|
-
end
|
197
|
-
|
198
|
-
next if self.plugins.empty?
|
199
|
-
opts.separator "Plugin options:"
|
200
|
-
|
201
|
-
extra = self.methods.grep(/parse_options/) - %w(parse_options)
|
202
|
-
|
203
|
-
extra.sort.each do |msg|
|
204
|
-
self.send msg, opts, option
|
205
|
-
end
|
206
|
-
|
207
|
-
end.parse! Array(args)
|
93
|
+
attr_reader :method_locations, :method_scores, :scores
|
94
|
+
attr_reader :total_score, :totals
|
208
95
|
|
209
|
-
|
210
|
-
end
|
96
|
+
# :startdoc:
|
211
97
|
|
212
98
|
##
|
213
99
|
# Add a score to the tally. Score can be predetermined or looked up
|
@@ -223,18 +109,44 @@ class Flog < SexpProcessor
|
|
223
109
|
|
224
110
|
def average
|
225
111
|
return 0 if calls.size == 0
|
226
|
-
|
112
|
+
total_score / calls.size
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Calculates classes and methods scores.
|
117
|
+
|
118
|
+
def calculate
|
119
|
+
each_by_score threshold do |class_method, score, call_list|
|
120
|
+
klass = class_method.split(/#|::/).first
|
121
|
+
|
122
|
+
method_scores[klass] << [class_method, score]
|
123
|
+
scores[klass] += score
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Returns true if the form looks like a "DSL" construct.
|
129
|
+
#
|
130
|
+
# task :blah do ... end
|
131
|
+
# => s(:iter, s(:call, nil, :task, s(:lit, :blah)), ...)
|
132
|
+
|
133
|
+
def dsl_name? args
|
134
|
+
return false unless args and not args.empty?
|
135
|
+
|
136
|
+
first_arg = args.first
|
137
|
+
first_arg = first_arg[1] if first_arg[0] == :hash
|
138
|
+
|
139
|
+
[:lit, :str].include? first_arg[0] and first_arg[1]
|
227
140
|
end
|
228
141
|
|
229
142
|
##
|
230
143
|
# Iterate over the calls sorted (descending) by score.
|
231
144
|
|
232
145
|
def each_by_score max = nil
|
233
|
-
|
234
|
-
current = 0
|
146
|
+
current = 0
|
235
147
|
|
236
|
-
calls.sort_by { |k,v| -
|
237
|
-
score =
|
148
|
+
calls.sort_by { |k,v| -totals[k] }.each do |class_method, call_list|
|
149
|
+
score = totals[class_method]
|
238
150
|
|
239
151
|
yield class_method, score, call_list
|
240
152
|
|
@@ -244,12 +156,12 @@ class Flog < SexpProcessor
|
|
244
156
|
end
|
245
157
|
|
246
158
|
##
|
247
|
-
# Flog the given files
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
files = Flog.expand_dirs_to_files(*files_or_dirs)
|
159
|
+
# Flog the given files. Deals with "-", and syntax errors.
|
160
|
+
#
|
161
|
+
# Not as smart as FlogCLI's #flog method as it doesn't traverse
|
162
|
+
# dirs. Use FlogCLI.expand_dirs_to_files or see FlogCLI#flog.
|
252
163
|
|
164
|
+
def flog(*files)
|
253
165
|
files.each do |file|
|
254
166
|
next unless file == '-' or File.readable? file
|
255
167
|
|
@@ -257,6 +169,8 @@ class Flog < SexpProcessor
|
|
257
169
|
|
258
170
|
flog_ruby ruby, file
|
259
171
|
end
|
172
|
+
|
173
|
+
calculate_total_scores
|
260
174
|
end
|
261
175
|
|
262
176
|
##
|
@@ -337,10 +251,13 @@ class Flog < SexpProcessor
|
|
337
251
|
@method_stack.shift
|
338
252
|
end
|
339
253
|
|
254
|
+
##
|
255
|
+
# Creates a new Flog instance with +options+.
|
256
|
+
|
340
257
|
def initialize option = {}
|
341
258
|
super()
|
342
259
|
@option = option
|
343
|
-
@sclass =
|
260
|
+
@sclass = []
|
344
261
|
@class_stack = []
|
345
262
|
@method_stack = []
|
346
263
|
@method_locations = {}
|
@@ -367,60 +284,27 @@ class Flog < SexpProcessor
|
|
367
284
|
end
|
368
285
|
|
369
286
|
##
|
370
|
-
# Returns the
|
371
|
-
# none.
|
287
|
+
# Returns the method/score pair of the maximum score.
|
372
288
|
|
373
|
-
def
|
374
|
-
|
375
|
-
m = "##{m}" unless m =~ /::/
|
376
|
-
m
|
289
|
+
def max_method
|
290
|
+
totals.max_by { |_, score| score }
|
377
291
|
end
|
378
292
|
|
379
293
|
##
|
380
|
-
#
|
381
|
-
|
382
|
-
def output_details io, max = nil
|
383
|
-
io.puts
|
294
|
+
# Returns the maximum score for a single method. Used for FlogTask.
|
384
295
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
self.print_score io, class_method, score
|
389
|
-
|
390
|
-
if option[:details] then
|
391
|
-
call_list.sort_by { |k,v| -v }.each do |call, count|
|
392
|
-
io.puts " %6.1f: %s" % [count, call]
|
393
|
-
end
|
394
|
-
io.puts
|
395
|
-
end
|
396
|
-
end
|
397
|
-
# io.puts
|
296
|
+
def max_score
|
297
|
+
max_method.last
|
398
298
|
end
|
399
299
|
|
400
300
|
##
|
401
|
-
#
|
402
|
-
#
|
403
|
-
|
404
|
-
def output_details_grouped io, max = nil
|
405
|
-
scores = Hash.new 0
|
406
|
-
methods = Hash.new { |h,k| h[k] = [] }
|
407
|
-
|
408
|
-
each_by_score max do |class_method, score, call_list|
|
409
|
-
klass = class_method.split(/#|::/).first
|
410
|
-
|
411
|
-
methods[klass] << [class_method, score]
|
412
|
-
scores[klass] += score
|
413
|
-
end
|
414
|
-
|
415
|
-
scores.sort_by { |_, n| -n }.each do |klass, total|
|
416
|
-
io.puts
|
417
|
-
|
418
|
-
io.puts "%8.1f: %s" % [total, "#{klass} total"]
|
301
|
+
# Returns the first method in the list, or "#none" if there are
|
302
|
+
# none.
|
419
303
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
304
|
+
def method_name
|
305
|
+
m = @method_stack.first || @@no_method
|
306
|
+
m = "##{m}" unless m =~ /::/
|
307
|
+
m
|
424
308
|
end
|
425
309
|
|
426
310
|
##
|
@@ -435,18 +319,6 @@ class Flog < SexpProcessor
|
|
435
319
|
@multiplier -= bonus
|
436
320
|
end
|
437
321
|
|
438
|
-
##
|
439
|
-
# Print out one formatted score.
|
440
|
-
|
441
|
-
def print_score io, name, score
|
442
|
-
location = @method_locations[name]
|
443
|
-
if location then
|
444
|
-
io.puts "%8.1f: %-32s %s" % [score, name, location]
|
445
|
-
else
|
446
|
-
io.puts "%8.1f: %s" % [score, name]
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
322
|
##
|
451
323
|
# Process each element of #exp in turn.
|
452
324
|
|
@@ -454,32 +326,15 @@ class Flog < SexpProcessor
|
|
454
326
|
process exp.shift until exp.empty?
|
455
327
|
end
|
456
328
|
|
457
|
-
##
|
458
|
-
# Report results to #io, STDOUT by default.
|
459
|
-
|
460
|
-
def report(io = $stdout)
|
461
|
-
io.puts "%8.1f: %s" % [total, "flog total"]
|
462
|
-
io.puts "%8.1f: %s" % [average, "flog/method average"]
|
463
|
-
|
464
|
-
return if option[:score]
|
465
|
-
|
466
|
-
max = option[:all] ? nil : total * THRESHOLD
|
467
|
-
if option[:group] then
|
468
|
-
output_details_grouped io, max
|
469
|
-
else
|
470
|
-
output_details io, max
|
471
|
-
end
|
472
|
-
ensure
|
473
|
-
self.reset
|
474
|
-
end
|
475
|
-
|
476
329
|
##
|
477
330
|
# Reset score data
|
478
331
|
|
479
332
|
def reset
|
480
|
-
@totals
|
481
|
-
@multiplier
|
482
|
-
@calls
|
333
|
+
@totals = @total_score = nil
|
334
|
+
@multiplier = 1.0
|
335
|
+
@calls = Hash.new { |h,k| h[k] = Hash.new 0 }
|
336
|
+
@method_scores = Hash.new { |h,k| h[k] = [] }
|
337
|
+
@scores = Hash.new 0
|
483
338
|
end
|
484
339
|
|
485
340
|
##
|
@@ -497,47 +352,46 @@ class Flog < SexpProcessor
|
|
497
352
|
Math.sqrt(a*a + b*b + c*c)
|
498
353
|
end
|
499
354
|
|
355
|
+
##
|
356
|
+
# Returns the method signature for the current method.
|
357
|
+
|
500
358
|
def signature
|
501
359
|
"#{klass_name}#{method_name}"
|
502
360
|
end
|
503
361
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
@total_score
|
508
|
-
end
|
509
|
-
|
510
|
-
def max_score
|
511
|
-
max_method.last
|
512
|
-
end
|
362
|
+
##
|
363
|
+
# Final threshold that is used for report
|
513
364
|
|
514
|
-
def
|
515
|
-
|
365
|
+
def threshold
|
366
|
+
option[:all] ? nil : total_score * THRESHOLD
|
516
367
|
end
|
517
368
|
|
518
369
|
##
|
519
|
-
#
|
370
|
+
# Calculates the total score and populates @totals.
|
520
371
|
|
521
|
-
def
|
522
|
-
|
523
|
-
@total_score = 0
|
524
|
-
@totals = Hash.new(0)
|
372
|
+
def calculate_total_scores
|
373
|
+
return if @totals
|
525
374
|
|
526
|
-
|
527
|
-
|
528
|
-
score = score_method(tally)
|
375
|
+
@total_score = 0
|
376
|
+
@totals = Hash.new(0)
|
529
377
|
|
530
|
-
|
531
|
-
|
532
|
-
|
378
|
+
calls.each do |meth, tally|
|
379
|
+
next if option[:methods] and meth =~ /##{@@no_method}$/
|
380
|
+
score = score_method(tally)
|
381
|
+
|
382
|
+
@totals[meth] = score
|
383
|
+
@total_score += score
|
533
384
|
end
|
385
|
+
end
|
534
386
|
|
535
|
-
|
387
|
+
def no_method # :nodoc:
|
388
|
+
@@no_method
|
536
389
|
end
|
537
390
|
|
538
391
|
############################################################
|
539
392
|
# Process Methods:
|
540
393
|
|
394
|
+
# :stopdoc:
|
541
395
|
def process_alias(exp)
|
542
396
|
process exp.shift
|
543
397
|
process exp.shift
|
@@ -642,7 +496,7 @@ class Flog < SexpProcessor
|
|
642
496
|
alias :process_lasgn :process_dasgn_curr
|
643
497
|
|
644
498
|
def process_defn(exp)
|
645
|
-
name = @sclass ?
|
499
|
+
name = @sclass.empty? ? exp.shift : "::#{exp.shift}"
|
646
500
|
in_method name, exp.file, exp.line do
|
647
501
|
process_until_empty exp
|
648
502
|
end
|
@@ -678,15 +532,6 @@ class Flog < SexpProcessor
|
|
678
532
|
s()
|
679
533
|
end
|
680
534
|
|
681
|
-
def dsl_name? args
|
682
|
-
return false unless args and not args.empty?
|
683
|
-
|
684
|
-
first_arg = args.first
|
685
|
-
first_arg = first_arg[1] if first_arg[0] == :hash
|
686
|
-
|
687
|
-
[:lit, :str].include? first_arg[0] and first_arg[1]
|
688
|
-
end
|
689
|
-
|
690
535
|
def process_iter(exp)
|
691
536
|
context = (self.context - [:class, :module, :scope])
|
692
537
|
context = context.uniq.sort_by { |s| s.to_s }
|
@@ -758,12 +603,12 @@ class Flog < SexpProcessor
|
|
758
603
|
end
|
759
604
|
|
760
605
|
def process_sclass(exp)
|
761
|
-
@sclass
|
606
|
+
@sclass.push(true)
|
762
607
|
penalize_by 0.5 do
|
763
608
|
process exp.shift # recv
|
764
609
|
process_until_empty exp
|
765
610
|
end
|
766
|
-
@sclass
|
611
|
+
@sclass.pop
|
767
612
|
|
768
613
|
add_to_score :sclass
|
769
614
|
s()
|
@@ -791,4 +636,5 @@ class Flog < SexpProcessor
|
|
791
636
|
process_until_empty exp
|
792
637
|
s()
|
793
638
|
end
|
639
|
+
# :startdoc:
|
794
640
|
end
|