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 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
@@ -5,6 +5,8 @@ README.txt
5
5
  Rakefile
6
6
  bin/flog
7
7
  lib/flog.rb
8
+ lib/flog_cli.rb
8
9
  lib/flog_task.rb
9
10
  lib/gauntlet_flog.rb
10
11
  test/test_flog.rb
12
+ test/test_flog_cli.rb
data/Rakefile CHANGED
@@ -25,14 +25,25 @@ Hoe.spec 'flog' do
25
25
  end
26
26
 
27
27
  task :debug do
28
- require "flog"
28
+ require "flog_cli"
29
29
 
30
- file = ENV["F"] || "-"
31
- ruby = file == "-" ? ENV["R"] : File.read(file)
30
+ class FlogCLI
31
+ def_delegators :@flog, :flog_ruby
32
+ end
32
33
 
33
- @flog = Flog.new :parser => RubyParser
34
- @flog.flog_ruby ruby, file
35
- @flog.report
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 'optparse'
4
- require 'flog'
3
+ require "flog_cli"
5
4
 
6
- Flog.load_plugins
5
+ FlogCLI.load_plugins
7
6
 
8
- options = Flog.parse_options ARGV
7
+ options = FlogCLI.parse_options ARGV
9
8
 
10
9
  ARGV << "-" if ARGV.empty?
11
10
 
12
- flogger = Flog.new options
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 'rubygems'
2
- require 'sexp_processor'
3
- require 'ruby_parser'
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 = "3.2.3"
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
- option
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
- total / calls.size
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
- my_totals = totals
234
- current = 0
146
+ current = 0
235
147
 
236
- calls.sort_by { |k,v| -my_totals[k] }.each do |class_method, call_list|
237
- score = my_totals[class_method]
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 or directories. Smart. Deals with "-", syntax
248
- # errors, and traversing subdirectories intelligently.
249
-
250
- def flog(*files_or_dirs)
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 = nil
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 first method in the list, or "#none" if there are
371
- # none.
287
+ # Returns the method/score pair of the maximum score.
372
288
 
373
- def method_name
374
- m = @method_stack.first || @@no_method
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
- # Output the report up to a given max or report everything, if nil.
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
- each_by_score max do |class_method, score, call_list|
386
- return 0 if option[:methods] and class_method =~ /##{@@no_method}/
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
- # Output the report, grouped by class/module, up to a given max or
402
- # report everything, if nil.
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
- methods[klass].each do |name, score|
421
- self.print_score io, name, score
422
- end
423
- end
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 = @total_score = nil
481
- @multiplier = 1.0
482
- @calls = Hash.new { |h,k| h[k] = Hash.new 0 }
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
- def total # FIX: I hate this indirectness
505
- totals unless @total_score # calculates total_score as well
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 max_method
515
- totals.max_by { |_, score| score }
365
+ def threshold
366
+ option[:all] ? nil : total_score * THRESHOLD
516
367
  end
517
368
 
518
369
  ##
519
- # Return the total score and populates @totals.
370
+ # Calculates the total score and populates @totals.
520
371
 
521
- def totals
522
- unless @totals then
523
- @total_score = 0
524
- @totals = Hash.new(0)
372
+ def calculate_total_scores
373
+ return if @totals
525
374
 
526
- calls.each do |meth, tally|
527
- next if option[:methods] and meth =~ /##{@@no_method}$/
528
- score = score_method(tally)
375
+ @total_score = 0
376
+ @totals = Hash.new(0)
529
377
 
530
- @totals[meth] = score
531
- @total_score += score
532
- end
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
- @totals
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 ? "::#{exp.shift}" : exp.shift
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 = true
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 = nil
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