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