flog 3.2.3 → 4.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.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
|