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 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