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/lib/flog_cli.rb ADDED
@@ -0,0 +1,237 @@
1
+ require "rubygems"
2
+ require "optparse"
3
+ require "forwardable"
4
+
5
+ require "flog"
6
+
7
+ class FlogCLI
8
+ extend Forwardable
9
+
10
+ def_delegators :@flog, :average, :calculate, :each_by_score, :option
11
+ def_delegators :@flog, :method_locations, :method_scores, :reset, :scores
12
+ def_delegators :@flog, :threshold, :total_score, :no_method, :calculate_total_scores
13
+
14
+ ##
15
+ # Expands +*dirs+ to all files within that match ruby and rake extensions.
16
+ # --
17
+ # REFACTOR: from flay
18
+
19
+ def self.expand_dirs_to_files *dirs
20
+ extensions = %w[rb rake]
21
+
22
+ dirs.flatten.map { |p|
23
+ if File.directory? p then
24
+ Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")]
25
+ else
26
+ p
27
+ end
28
+ }.flatten.sort
29
+ end
30
+
31
+ ##
32
+ # Loads all flog plugins. Files must be named "flog/*.rb".
33
+
34
+ def self.load_plugins
35
+ # TODO: I think I want to do this more like hoe's plugin system. Generalize?
36
+ loaded, found = {}, {}
37
+
38
+ Gem.find_files("flog/*.rb").reverse.each do |path|
39
+ found[File.basename(path, ".rb").intern] = path
40
+ end
41
+
42
+ found.each do |name, plugin|
43
+ next if loaded[name]
44
+ begin
45
+ warn "loading #{plugin}" # if $DEBUG
46
+ loaded[name] = load plugin
47
+ rescue LoadError => e
48
+ warn "error loading #{plugin.inspect}: #{e.message}. skipping..."
49
+ end
50
+ end
51
+
52
+ self.plugins.merge loaded
53
+
54
+ names = Flog.constants.map {|s| s.to_s}.reject {|n| n =~ /^[A-Z_]+$/}
55
+
56
+ names.each do |name|
57
+ # next unless Hoe.plugins.include? name.downcase.intern
58
+ mod = Flog.const_get(name)
59
+ next if Class === mod
60
+ warn "extend #{mod}" if $DEBUG
61
+ # self.extend mod
62
+ end
63
+ end
64
+
65
+ ##
66
+ # Parse options in +args+ (defaults to ARGV).
67
+
68
+ def self.parse_options args = ARGV
69
+ option = {
70
+ :quiet => false,
71
+ :continue => false,
72
+ :parser => RubyParser,
73
+ }
74
+
75
+ OptionParser.new do |opts|
76
+ opts.separator "Standard options:"
77
+
78
+ opts.on("-a", "--all", "Display all flog results, not top 60%.") do
79
+ option[:all] = true
80
+ end
81
+
82
+ opts.on("-b", "--blame", "Include blame information for methods.") do
83
+ option[:blame] = true
84
+ end
85
+
86
+ opts.on("-c", "--continue", "Continue despite syntax errors.") do
87
+ option[:continue] = true
88
+ end
89
+
90
+ opts.on("-d", "--details", "Show method details.") do
91
+ option[:details] = true
92
+ end
93
+
94
+ opts.on("-g", "--group", "Group and sort by class.") do
95
+ option[:group] = true
96
+ end
97
+
98
+ opts.on("-h", "--help", "Show this message.") do
99
+ puts opts
100
+ exit
101
+ end
102
+
103
+ opts.on("-I dir1,dir2,dir3", Array, "Add to LOAD_PATH.") do |dirs|
104
+ dirs.each do |dir|
105
+ $: << dir
106
+ end
107
+ end
108
+
109
+ opts.on("-m", "--methods-only", "Skip code outside of methods.") do
110
+ option[:methods] = true
111
+ end
112
+
113
+ opts.on("-q", "--quiet", "Don't show parse errors.") do
114
+ option[:quiet] = true
115
+ end
116
+
117
+ opts.on("-s", "--score", "Display total score only.") do
118
+ option[:score] = true
119
+ end
120
+
121
+ opts.on("-v", "--verbose", "Display progress during processing.") do
122
+ option[:verbose] = true
123
+ end
124
+
125
+ opts.on("--18", "Use a ruby 1.8 parser.") do
126
+ option[:parser] = Ruby18Parser
127
+ end
128
+
129
+ opts.on("--19", "Use a ruby 1.9 parser.") do
130
+ option[:parser] = Ruby19Parser
131
+ end
132
+
133
+ next if self.plugins.empty?
134
+ opts.separator "Plugin options:"
135
+
136
+ extra = self.method_scores.grep(/parse_options/) - %w(parse_options)
137
+
138
+ extra.sort.each do |msg|
139
+ self.send msg, opts, option
140
+ end
141
+
142
+ end.parse! Array(args)
143
+
144
+ option
145
+ end
146
+
147
+ ##
148
+ # The known plugins for Flog. See Flog.load_plugins.
149
+
150
+ def self.plugins
151
+ @plugins ||= {}
152
+ end
153
+
154
+ ##
155
+ # Flog the given files or directories. Smart. Deals with "-", syntax
156
+ # errors, and traversing subdirectories intelligently.
157
+
158
+ def flog(*files_or_dirs)
159
+ files = FlogCLI.expand_dirs_to_files(*files_or_dirs)
160
+ @flog.flog(*files)
161
+ end
162
+
163
+ ##
164
+ # Creates a new Flog instance with +options+.
165
+
166
+ def initialize options = {}
167
+ @flog = Flog.new options
168
+ end
169
+
170
+ ##
171
+ # Output the report up to a given max or report everything, if nil.
172
+
173
+ def output_details io, max = nil
174
+ io.puts
175
+
176
+ each_by_score max do |class_method, score, call_list|
177
+ return 0 if option[:methods] and class_method =~ /##{no_method}/
178
+
179
+ self.print_score io, class_method, score
180
+
181
+ if option[:details] then
182
+ call_list.sort_by { |k,v| -v }.each do |call, count|
183
+ io.puts " %6.1f: %s" % [count, call]
184
+ end
185
+ io.puts
186
+ end
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Output the report, grouped by class/module, up to a given max or
192
+ # report everything, if nil.
193
+
194
+ def output_details_grouped io, threshold = nil
195
+ calculate
196
+
197
+ scores.sort_by { |_, n| -n }.each do |klass, total|
198
+ io.puts
199
+
200
+ io.puts "%8.1f: %s" % [total, "#{klass} total"]
201
+
202
+ method_scores[klass].each do |name, score|
203
+ self.print_score io, name, score
204
+ end
205
+ end
206
+ end
207
+
208
+ ##
209
+ # Print out one formatted score.
210
+
211
+ def print_score io, name, score
212
+ location = method_locations[name]
213
+ if location then
214
+ io.puts "%8.1f: %-32s %s" % [score, name, location]
215
+ else
216
+ io.puts "%8.1f: %s" % [score, name]
217
+ end
218
+ end
219
+
220
+ ##
221
+ # Report results to #io, STDOUT by default.
222
+
223
+ def report(io = $stdout)
224
+ io.puts "%8.1f: %s" % [total_score, "flog total"]
225
+ io.puts "%8.1f: %s" % [average, "flog/method average"]
226
+
227
+ return if option[:score]
228
+
229
+ if option[:group] then
230
+ output_details_grouped io, threshold
231
+ else
232
+ output_details io, threshold
233
+ end
234
+ ensure
235
+ self.reset
236
+ end
237
+ end
data/lib/flog_task.rb CHANGED
@@ -1,12 +1,35 @@
1
1
  require 'rake/tasklib'
2
2
 
3
3
  class FlogTask < Rake::TaskLib
4
+ ##
5
+ # The name of the task. Defaults to :flog
6
+
4
7
  attr_accessor :name
8
+
9
+ ##
10
+ # What directories to operate on. Sensible defaults.
11
+
5
12
  attr_accessor :dirs
13
+
14
+ ##
15
+ # Threshold to fail the task at. Default 200.
16
+
6
17
  attr_accessor :threshold
18
+
19
+ ##
20
+ # Verbosity of output. Defaults to rake's trace (-t) option.
21
+
7
22
  attr_accessor :verbose
23
+
24
+ ##
25
+ # Method to use to score. Defaults to :total
26
+
8
27
  attr_accessor :method
9
28
 
29
+ ##
30
+ # Creates a new FlogTask instance with given +name+, +threshold+,
31
+ # +dirs+, and +method+.
32
+
10
33
  def initialize name = :flog, threshold = 200, dirs = nil, method = nil
11
34
  @name = name
12
35
  @dirs = dirs || %w(app bin lib spec test)
@@ -21,6 +44,9 @@ class FlogTask < Rake::TaskLib
21
44
  define
22
45
  end
23
46
 
47
+ ##
48
+ # Defines the flog task.
49
+
24
50
  def define
25
51
  desc "Analyze for code complexity in: #{dirs.join(', ')}"
26
52
  task name do
data/lib/gauntlet_flog.rb CHANGED
@@ -10,6 +10,7 @@ require 'flog'
10
10
  require 'gauntlet'
11
11
  require 'pp'
12
12
 
13
+ # :stopdoc:
13
14
  class FlogGauntlet < Gauntlet
14
15
  $owners = {}
15
16
  $score_file = 'flog-scores.yml'
@@ -17,7 +18,7 @@ class FlogGauntlet < Gauntlet
17
18
  $syntax_error = {:total => -2, :average => -2, :methods => {}}
18
19
  $no_gem = {:total => -4, :average => -4, :methods => {}}
19
20
 
20
- # copied straight from hoedown.rb
21
+ # copied straight from hoedown.rb
21
22
  my_projects = %w[InlineFortran ParseTree RubyInline RubyToC
22
23
  ZenHacks ZenTest bfts box_layout
23
24
  change_class flay flog gauntlet heckle
@@ -55,7 +56,7 @@ class FlogGauntlet < Gauntlet
55
56
  puts "avg methods / gem : %8.2f (%8.2f stddev)" % [method_counts.average, method_counts.stddev]
56
57
  puts "avg flog / gem : %8.2f (%8.2f stddev)" % [flog_numbers.average, flog_numbers.stddev]
57
58
  end
58
-
59
+
59
60
  worst = scores.sort_by { |k,v| -v[:total] }.first(max)
60
61
  report_worst "Worst Projects EVAR", worst do |project, score|
61
62
  owner = $owners[project].join(', ') rescue nil
@@ -119,7 +120,7 @@ class FlogGauntlet < Gauntlet
119
120
  topN = Hash[*methods.sort_by { |k,v| -v }.first(n).flatten]
120
121
  {
121
122
  :max => methods.values.max,
122
- :total => flogger.total,
123
+ :total => flogger.total_score,
123
124
  :size => methods.size,
124
125
  :average => flogger.average,
125
126
  :stddev => flogger.stddev,
@@ -191,3 +192,4 @@ filter = Regexp.new filter if filter
191
192
  flogger = FlogGauntlet.new
192
193
  flogger.run_the_gauntlet filter
193
194
  flogger.display_report max
195
+ # :startdoc:
data/test/test_flog.rb CHANGED
@@ -1,20 +1,30 @@
1
- require 'minitest/autorun'
2
- require 'flog'
1
+ require "minitest/autorun"
2
+ require "flog"
3
3
 
4
4
  class Flog
5
5
  attr_writer :calls
6
6
  end
7
7
 
8
- class TestFlog < MiniTest::Unit::TestCase
8
+ class FlogTest < MiniTest::Unit::TestCase
9
+ def setup_flog
10
+ old_stdin = $stdin
11
+ $stdin = StringIO.new "2 + 3"
12
+ $stdin.rewind
13
+
14
+ @flog.flog "-" # @flog can be Flog or FlogCLI
15
+ ensure
16
+ $stdin = old_stdin
17
+ end
18
+ end
19
+
20
+ class TestFlog < FlogTest
9
21
  def setup
10
22
  @flog = Flog.new :parser => RubyParser
11
23
  end
12
24
 
13
25
  def test_add_to_score
14
26
  assert_empty @flog.calls
15
- @flog.class_stack << "Base" << "MyKlass"
16
- @flog.method_stack << "mymethod"
17
- @flog.add_to_score "blah", 42
27
+ setup_my_klass
18
28
 
19
29
  expected = {"MyKlass::Base#mymethod" => {"blah" => 42.0}}
20
30
  assert_equal expected, @flog.calls
@@ -31,100 +41,27 @@ class TestFlog < MiniTest::Unit::TestCase
31
41
  assert_equal 1.0, @flog.average
32
42
  end
33
43
 
34
- def test_cls_expand_dirs_to_files
35
- expected = %w(lib/flog.rb lib/flog_task.rb lib/gauntlet_flog.rb)
36
- assert_equal expected, Flog.expand_dirs_to_files('lib')
37
- expected = %w(Rakefile)
38
- assert_equal expected, Flog.expand_dirs_to_files('Rakefile')
39
- end
40
-
41
- def test_cls_parse_options
42
- # defaults
43
- opts = Flog.parse_options
44
- assert_equal false, opts[:quiet]
45
- assert_equal false, opts[:continue]
46
-
47
- {
48
- "-a" => :all,
49
- "--all" => :all,
50
- "-b" => :blame,
51
- "--blame" => :blame,
52
- "-c" => :continue,
53
- "--continue" => :continue,
54
- "-d" => :details,
55
- "--details" => :details,
56
- "-g" => :group,
57
- "--group" => :group,
58
- "-m" => :methods,
59
- "--methods-only" => :methods,
60
- "-q" => :quiet,
61
- "--quiet" => :quiet,
62
- "-s" => :score,
63
- "--score" => :score,
64
- "-v" => :verbose,
65
- "--verbose" => :verbose,
66
- }.each do |key, val|
67
- assert_equal true, Flog.parse_options(key)[val]
68
- end
69
- end
70
-
71
- def test_cls_parse_options_path
72
- old_path = $:.dup
73
- Flog.parse_options("-Ia,b,c")
74
- assert_equal old_path + %w(a b c), $:
75
-
76
- Flog.parse_options(["-I", "d,e,f"])
77
- assert_equal old_path + %w(a b c d e f), $:
78
-
79
- Flog.parse_options(["-I", "g", "-Ih"])
80
- assert_equal old_path + %w(a b c d e f g h), $:
81
- ensure
82
- $:.replace old_path
83
- end
84
-
85
- def test_cls_parse_options_help
86
- def Flog.exit
87
- raise "happy"
88
- end
89
-
90
- ex = nil
91
- o, e = capture_io do
92
- ex = assert_raises RuntimeError do
93
- Flog.parse_options "-h"
94
- end
95
- end
96
-
97
- assert_equal "happy", ex.message
98
- assert_match(/methods-only/, o)
99
- assert_equal "", e
100
- end
101
-
102
44
  def test_flog
103
- old_stdin = $stdin
104
- $stdin = StringIO.new "2 + 3"
105
- $stdin.rewind
106
-
107
- @flog.flog "-"
45
+ setup_flog
108
46
 
109
47
  exp = { "main#none" => { :+ => 1.0, :lit_fixnum => 0.6 } }
110
48
  assert_equal exp, @flog.calls
111
49
 
112
- assert_equal 1.6, @flog.total unless @flog.option[:methods]
50
+ assert_equal 1.6, @flog.total_score unless @flog.option[:methods]
113
51
  assert_equal 3, @flog.mass["-"]
114
- ensure
115
- $stdin = old_stdin
116
52
  end
117
-
53
+
118
54
  def test_flog_ruby
119
55
  ruby = "2 + 3"
120
56
  file = "sample.rb"
121
57
 
122
58
  @flog.flog_ruby ruby, file
59
+ @flog.calculate_total_scores
123
60
 
124
61
  exp = { "main#none" => { :+ => 1.0, :lit_fixnum => 0.6 } }
125
62
  assert_equal exp, @flog.calls
126
63
 
127
- assert_equal 1.6, @flog.total unless @flog.option[:methods]
64
+ assert_equal 1.6, @flog.total_score unless @flog.option[:methods]
128
65
  assert_equal 3, @flog.mass[file]
129
66
  end
130
67
 
@@ -197,66 +134,6 @@ class TestFlog < MiniTest::Unit::TestCase
197
134
  assert_equal "::whatevs", @flog.method_name
198
135
  end
199
136
 
200
- def test_output_details
201
- @flog.option[:all] = true
202
- test_flog
203
-
204
- @flog.totals["main#something"] = 42.0
205
-
206
- o = StringIO.new
207
- @flog.output_details o
208
-
209
- expected = "\n 1.6: main#none\n"
210
-
211
- assert_equal expected, o.string
212
- assert_equal 1.6, @flog.totals["main#none"]
213
- end
214
-
215
- def test_output_details_grouped
216
- test_flog
217
-
218
- o = StringIO.new
219
- @flog.output_details_grouped o
220
-
221
- expected = "\n 1.6: main total\n 1.6: main#none\n"
222
-
223
- assert_equal expected, o.string
224
- end
225
-
226
- def test_output_details_methods
227
- @flog.option[:methods] = true
228
-
229
- test_flog
230
-
231
- @flog.totals["main#something"] = 42.0 # TODO: no sense... why no output?
232
-
233
- o = StringIO.new
234
- @flog.output_details o
235
-
236
- # HACK assert_equal "", o.string
237
- assert_equal 0, @flog.totals["main#none"]
238
- end
239
-
240
- def test_output_details_detailed
241
- @flog.option[:details] = true
242
-
243
- test_flog
244
-
245
- @flog.totals["main#something"] = 42.0
246
-
247
- o = StringIO.new
248
- @flog.output_details o, nil
249
-
250
- expected = "\n 1.6: main#none
251
- 1.0: +
252
- 0.6: lit_fixnum
253
-
254
- "
255
-
256
- assert_equal expected, o.string
257
- assert_equal 1.6, @flog.totals["main#none"]
258
- end
259
-
260
137
  # def test_process_until_empty
261
138
  # flunk "no"
262
139
  # end
@@ -420,11 +297,29 @@ class TestFlog < MiniTest::Unit::TestCase
420
297
 
421
298
  setup
422
299
  @flog.process sexp
300
+ @flog.calculate_total_scores
423
301
 
424
302
  exp = {'main::x' => {:lit_fixnum => 0.375}, 'main#none' => {:sclass => 5.0}}
425
303
  assert_equal exp, @flog.calls
426
304
 
427
- assert_in_delta 5.375, @flog.total
305
+ assert_in_delta 5.375, @flog.total_score
306
+ end
307
+
308
+ def test_process_defn_in_self_after_self
309
+ sexp = s(:sclass, s(:self),
310
+ s(:sclass, s(:self), s(:self)),
311
+ s(:defn, :x,
312
+ s(:args, :y),
313
+ s(:lit, 42)))
314
+
315
+ setup
316
+ @flog.process sexp
317
+ @flog.calculate_total_scores
318
+
319
+ exp = {'main::x' => {:lit_fixnum => 0.375}, 'main#none' => {:sclass => 12.5}}
320
+ assert_equal exp, @flog.calls
321
+
322
+ assert_in_delta 12.875, @flog.total_score
428
323
  end
429
324
 
430
325
  def test_process_defs
@@ -536,9 +431,10 @@ class TestFlog < MiniTest::Unit::TestCase
536
431
 
537
432
  setup
538
433
  @flog.process sexp
434
+ @flog.calculate_total_scores
539
435
 
540
436
  assert_equal hash, @flog.calls
541
- assert_in_delta score, @flog.total
437
+ assert_in_delta score, @flog.total_score
542
438
  end
543
439
 
544
440
  def test_process_lit
@@ -613,71 +509,6 @@ class TestFlog < MiniTest::Unit::TestCase
613
509
  util_process sexp, 1.50, :yield => 1.0, :lit_fixnum => 0.50
614
510
  end
615
511
 
616
- def test_report
617
- test_flog
618
-
619
- o = StringIO.new
620
- @flog.report o
621
-
622
- expected = " 1.6: flog total
623
- 1.6: flog/method average
624
-
625
- 1.6: main#none
626
- "
627
-
628
- assert_equal expected, o.string
629
- end
630
-
631
- def test_report_all
632
- old_stdin = $stdin
633
- $stdin = StringIO.new "2 + 3"
634
- $stdin.rewind
635
-
636
- @flog.flog "-"
637
- @flog.totals["main#something"] = 42.0
638
-
639
- exp = { "main#none" => { :+ => 1.0, :lit_fixnum => 0.6 } }
640
- assert_equal exp, @flog.calls
641
-
642
- @flog.option[:all] = true
643
-
644
- assert_equal 1.6, @flog.total unless @flog.option[:methods]
645
- assert_equal 3, @flog.mass["-"]
646
-
647
- o = StringIO.new
648
- @flog.report o
649
-
650
- expected = " 1.6: flog total
651
- 1.6: flog/method average
652
-
653
- 1.6: main#none
654
- "
655
-
656
- assert_equal expected, o.string
657
- # FIX: add thresholded output
658
- ensure
659
- $stdin = old_stdin
660
- end
661
-
662
- def test_report_group
663
- # TODO: add second group to ensure proper output
664
- @flog.option[:group] = true
665
-
666
- test_flog
667
-
668
- o = StringIO.new
669
- @flog.report o
670
-
671
- expected = " 1.6: flog total
672
- 1.6: flog/method average
673
-
674
- 1.6: main total
675
- 1.6: main#none
676
- "
677
-
678
- assert_equal expected, o.string
679
- end
680
-
681
512
  def test_score_method
682
513
  assert_equal 3.0, @flog.score_method(:blah => 3.0)
683
514
  assert_equal 4.0, @flog.score_method(:assignment => 4.0)
@@ -699,9 +530,11 @@ class TestFlog < MiniTest::Unit::TestCase
699
530
  assert_equal "main#y", @flog.signature
700
531
  end
701
532
 
702
- def test_total
533
+ def test_total_score
703
534
  @flog.add_to_score "blah", 2
704
- assert_equal 2.0, @flog.total
535
+ @flog.calculate_total_scores
536
+
537
+ assert_equal 2.0, @flog.total_score
705
538
  end
706
539
 
707
540
  def test_max_method
@@ -711,6 +544,7 @@ class TestFlog < MiniTest::Unit::TestCase
711
544
  "main#meth_two" => {"foo" => 2.0, "bar" => 14.0},
712
545
  }
713
546
 
547
+ @flog.calculate_total_scores
714
548
  assert_equal ["main#meth_two", 16.0], @flog.max_method
715
549
  end
716
550
 
@@ -720,6 +554,7 @@ class TestFlog < MiniTest::Unit::TestCase
720
554
  "main#meth_one" => {"foo" => 1.0, "bar" => 1.0},
721
555
  "main#meth_two" => {"foo" => 2.0, "bar" => 14.0},
722
556
  }
557
+ @flog.calculate_total_scores
723
558
 
724
559
  assert_equal 16.0, @flog.max_score
725
560
  end
@@ -736,6 +571,34 @@ class TestFlog < MiniTest::Unit::TestCase
736
571
  assert_equal exp, @flog.calls
737
572
  end
738
573
 
739
- assert_in_delta score, @flog.total
574
+ @flog.calculate_total_scores
575
+
576
+ assert_in_delta score, @flog.total_score
577
+ end
578
+
579
+ def test_threshold
580
+ test_flog
581
+ assert_equal Flog::THRESHOLD * 1.6, @flog.threshold
582
+ end
583
+
584
+ def test_no_threshold
585
+ @flog.option[:all] = true
586
+ assert_equal nil, @flog.threshold
587
+ end
588
+
589
+ def test_calculate
590
+ setup_my_klass
591
+
592
+ @flog.calculate_total_scores
593
+ @flog.calculate
594
+
595
+ assert_equal({ 'MyKlass' => 42.0 }, @flog.scores)
596
+ assert_equal({ 'MyKlass' => [["MyKlass::Base#mymethod", 42.0]] }, @flog.method_scores)
597
+ end
598
+
599
+ def setup_my_klass
600
+ @flog.class_stack << "Base" << "MyKlass"
601
+ @flog.method_stack << "mymethod"
602
+ @flog.add_to_score "blah", 42
740
603
  end
741
604
  end