flog 1.1.0 → 1.2.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/History.txt CHANGED
@@ -1,10 +1,32 @@
1
- == 1.1.0 / 2007-08-21
1
+ === 1.2.0 / 2008-10-22
2
+
3
+ * 14 minor enhancements:
4
+
5
+ * Added -c flag to continue dispite errors.
6
+ * Added -m to only report code in methods (skips #none).
7
+ * Added -n flag to give NO method details (summary only)
8
+ * Added -n to skip method details... pussies should learn grep.
9
+ * Added -q to quiet method details (total per method only)
10
+ * Added avg & stddev to total.
11
+ * Added avg score per method to report.
12
+ * Added lots of doco from contributors (hugh sasse?).
13
+ * Fixed class names when const2/3.
14
+ * Fixed unified ruby changes
15
+ * Refactored flog with help from flay.
16
+ * Refactored get_source_index
17
+ * Refactored into gem_updater.rb and cleaned up.
18
+ * Works with new incremental rubygems, albiet slower than before.
19
+
20
+ === 1.1.0 / 2007-08-21
2
21
 
3
22
  * 3 major enhancements:
23
+
4
24
  * Added assignments and branches and a lot of other stuff. rad.
5
25
  * Added process_iter section for DSL style blocks (rake tasks etc).
6
26
  * Made Flog usable as a library.
27
+
7
28
  * 12 minor enhancements:
29
+
8
30
  * Added -a flag to turn off threshold culling for other tools.
9
31
  * Added -s for summarizing the score.
10
32
  * Added -v feedback to know what file you're flogging.
@@ -17,23 +39,27 @@
17
39
  * Added unpack.rb and update_scores.rb at base level (not installed)
18
40
  * Added scoring for block_pass.
19
41
  * Converted totals to use distance formula on ABC's.
42
+
20
43
  * 3 bug fixes:
44
+
21
45
  * Ran flog on every latest gem available. Found a bunch of problems.
22
46
  * Use a stack for both class/module and method accounting.
23
47
  * block_args weren't processing the arg
24
48
 
25
- == 1.0.2 / 2007-08-01
49
+ === 1.0.2 / 2007-08-01
26
50
 
27
51
  * 1 bug fix:
52
+
28
53
  * stupid rubygems bin wrapper... *sigh*
29
54
 
30
- == 1.0.1 / 2007-08-01
55
+ === 1.0.1 / 2007-08-01
31
56
 
32
57
  * 1 bug fix:
58
+
33
59
  * New Rule: NEVER release new software when exhausted: Fixed dependency list.
34
60
 
35
- == 1.0.0 / 2007-08-01
61
+ === 1.0.0 / 2007-08-01
36
62
 
37
63
  * 1 major enhancement:
38
- * Birthday!
39
64
 
65
+ * Birthday!
data/Manifest.txt CHANGED
@@ -3,6 +3,7 @@ Manifest.txt
3
3
  README.txt
4
4
  Rakefile
5
5
  bin/flog
6
+ gem_updater.rb
6
7
  lib/flog.rb
7
8
  unpack.rb
8
9
  update_scores.rb
data/README.txt CHANGED
@@ -1,7 +1,7 @@
1
- flog
2
- by Ryan Davis, Seattle.rb
3
- http://ruby.sadi.st/
4
- http://rubyforge.org/projects/seattlerb
1
+ = flog
2
+
3
+ * http://ruby.sadi.st/
4
+ * http://rubyforge.org/projects/seattlerb
5
5
 
6
6
  == DESCRIPTION:
7
7
 
@@ -25,7 +25,7 @@ report. The higher the score, the more pain the code is in.
25
25
  == REQUIREMENTS:
26
26
 
27
27
  * ruby2ruby
28
- * parse_tree
28
+ * ParseTree
29
29
 
30
30
  == INSTALL:
31
31
 
data/Rakefile CHANGED
@@ -2,16 +2,22 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
+
6
+ Hoe.add_include_dirs("../../ParseTree/dev/lib",
7
+ "../../RubyInline/dev/lib",
8
+ "../../sexp_processor/dev/lib",
9
+ "../../ZenTest/dev/lib",
10
+ "lib")
11
+
5
12
  require './lib/flog'
6
13
 
7
- Hoe.new('flog', Flog::VERSION) do |p|
8
- p.rubyforge_name = 'seattlerb'
9
- p.summary = p.paragraphs_of('README.txt', 2).first
10
- p.description = p.paragraphs_of('README.txt', 2, 6).join("\n\n")
11
- p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2..-1].map {|u| u.strip }
12
- p.changes = p.paragraphs_of('History.txt', 1).join("\n\n")
14
+ Hoe.new('flog', Flog::VERSION) do |flog|
15
+ flog.rubyforge_name = 'seattlerb'
16
+
17
+ flog.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
13
18
 
14
- p.extra_deps << ["ParseTree", '>= 2.0.0']
19
+ flog.extra_deps << ['sexp_processor', '~> 3.0']
20
+ flog.extra_deps << ["ParseTree", '~> 3.0']
15
21
  end
16
22
 
17
23
  # vim: syntax=Ruby
data/bin/flog CHANGED
@@ -2,15 +2,20 @@
2
2
 
3
3
  require 'flog'
4
4
 
5
+ # In case this is being called on the end of a pipe:
5
6
  ARGV.push "-" if ARGV.empty?
6
7
 
7
8
  if defined? $h then
8
9
  puts "#{File.basename $0} options dirs_or_files"
9
10
  puts " -a display all flog results, not top 60%"
11
+ puts " -c continue despite syntax errors"
10
12
  puts " -h display help"
11
13
  puts " -I=path extend $LOAD_PATH with path"
14
+ puts " -m skip code outside of methods"
15
+ puts " -n no method details in report"
12
16
  puts " -s display total score only"
13
17
  puts " -v verbosely display progress and errors"
18
+ puts " -q quiet, don't show method breakdowns"
14
19
  exit 0
15
20
  end
16
21
 
data/gem_updater.rb ADDED
@@ -0,0 +1,161 @@
1
+ require 'rubygems/remote_fetcher'
2
+
3
+ $u ||= false
4
+
5
+ module GemUpdater
6
+ GEMURL = URI.parse 'http://gems.rubyforge.org'
7
+
8
+ @@index = nil
9
+
10
+ def self.stupid_gems
11
+ ["ruby-aes-table1-1.0.gem", # stupid dups usually because of "dash" renames
12
+ "ruby-aes-unroll1-1.0.gem",
13
+ "hpricot-scrub-0.2.0.gem",
14
+ "extract_curves-0.0.1.gem",
15
+ "extract_curves-0.0.1-i586-linux.gem",
16
+ "extract_curves-0.0.1-mswin32.gem",
17
+ "rfeedparser-ictv-0.9.931.gem",
18
+ "spec_unit-0.0.1.gem"]
19
+ end
20
+
21
+ def self.initialize_dir
22
+ Dir.mkdir "../gems" unless File.directory? "../gems"
23
+ self.in_gem_dir do
24
+ File.symlink ".", "cache" unless File.exist? "cache"
25
+ end
26
+ end
27
+
28
+ def self.get_source_index
29
+ return @@index if @@index
30
+
31
+ dump = if $u or not File.exist? '.source_index' then
32
+ url = GEMURL + "Marshal.#{Gem.marshal_version}.Z"
33
+ dump = Gem::RemoteFetcher.fetcher.fetch_path url
34
+ require 'zlib'
35
+ dump = Gem.inflate dump
36
+ open '.source_index', 'wb' do |io| io.write dump end
37
+ dump
38
+ else
39
+ open '.source_index', 'rb' do |io| io.read end
40
+ end
41
+
42
+ @@index = Marshal.load dump
43
+ end
44
+
45
+ def self.get_latest_gems
46
+ @@cache ||= get_source_index.latest_specs
47
+ end
48
+
49
+ def self.get_gems_by_name
50
+ @@by_name ||= Hash[*get_latest_gems.map { |gem|
51
+ [gem.name, gem, gem.full_name, gem]
52
+ }.flatten]
53
+ end
54
+
55
+ def self.dependencies_of name
56
+ index = self.get_source_index
57
+ get_gems_by_name[name].dependencies.map { |dep| index.search(dep).last }
58
+ end
59
+
60
+ def self.dependent_upon name
61
+ get_latest_gems.find_all { |gem|
62
+ gem.dependencies.any? { |dep| dep.name == name }
63
+ }
64
+ end
65
+
66
+ def self.update_gem_tarballs
67
+ GemUpdater.initialize_dir
68
+
69
+ latest = GemUpdater.get_latest_gems
70
+
71
+ puts "updating mirror"
72
+
73
+ self.in_gem_dir do
74
+ gems = Dir["*.gem"]
75
+ tgzs = Dir["*.tgz"]
76
+
77
+ old = tgzs - latest.map { |spec| "#{spec.full_name}.tgz" }
78
+ unless old.empty? then
79
+ puts "deleting #{old.size} tgzs"
80
+ old.each do |tgz|
81
+ File.unlink tgz
82
+ end
83
+ end
84
+
85
+ new = latest.map { |spec|
86
+ "#{spec.full_name}.tgz"
87
+ } - tgzs
88
+
89
+ puts "fetching #{new.size} tgzs"
90
+
91
+ latest.sort.each do |spec|
92
+ full_name = spec.full_name
93
+ tgz_name = "#{full_name}.tgz"
94
+ gem_name = "#{full_name}.gem"
95
+
96
+ next if tgzs.include? tgz_name
97
+
98
+ unless gems.include? gem_name then
99
+ begin
100
+ warn "downloading #{full_name}"
101
+ Gem::RemoteFetcher.fetcher.download(spec, GEMURL, Dir.pwd)
102
+ rescue Gem::RemoteFetcher::FetchError
103
+ warn " failed"
104
+ next
105
+ end
106
+ end
107
+
108
+ warn "converting #{gem_name} to tarball"
109
+
110
+ unless File.directory? full_name then
111
+ system "gem unpack cache/#{gem_name}"
112
+ system "gem spec -l cache/#{gem_name} > #{full_name}/gemspec.rb"
113
+ end
114
+
115
+ system "tar zmcf #{tgz_name} #{full_name}"
116
+ system "rm -rf #{full_name} #{gem_name}"
117
+ end
118
+ end
119
+ end
120
+
121
+ def self.each_gem filter = /^[\w-]+-\d+(\.\d+)*\.tgz$/
122
+ self.in_gem_dir do
123
+ Dir["*.tgz"].each do |tgz|
124
+ next unless tgz =~ filter
125
+
126
+ yield File.basename(tgz, ".tgz")
127
+ end
128
+ end
129
+ end
130
+
131
+ def self.with_gem name
132
+ self.in_gem_dir do
133
+ begin
134
+ system "tar zxmf #{name}.tgz 2> /dev/null"
135
+ Dir.chdir name do
136
+ yield name
137
+ end
138
+ ensure
139
+ system "rm -r #{name}"
140
+ end
141
+ end
142
+ end
143
+
144
+ def self.load_yaml path, default = {}
145
+ YAML.load(File.read(path)) rescue default
146
+ end
147
+
148
+ def self.save_yaml path, data
149
+ File.open("#{path}.new", 'w') do |f|
150
+ warn "*** saving #{path}"
151
+ YAML.dump data, f
152
+ end
153
+ File.rename "#{path}.new", path
154
+ end
155
+
156
+ def self.in_gem_dir
157
+ Dir.chdir "../gems" do
158
+ yield
159
+ end
160
+ end
161
+ end
data/lib/flog.rb CHANGED
@@ -3,12 +3,16 @@ require 'parse_tree'
3
3
  require 'sexp_processor'
4
4
  require 'unified_ruby'
5
5
 
6
- $a ||= false
7
- $s ||= false
8
- $v ||= false
6
+ $a ||= false # report all methods, not just 60%
7
+ $c ||= false # continue despite syntax errors
8
+ $m ||= false # real methods only (no global scope)
9
+ $n ||= false # no method details
10
+ $s ||= false # summary only
11
+ $v ||= false # verbose, print methods as processed
12
+ $q ||= false # quiet, don't show method details
9
13
 
10
14
  class Flog < SexpProcessor
11
- VERSION = '1.1.0'
15
+ VERSION = '1.2.0'
12
16
 
13
17
  include UnifiedRuby
14
18
 
@@ -16,50 +20,58 @@ class Flog < SexpProcessor
16
20
  SCORES = Hash.new(1)
17
21
  BRANCHING = [ :and, :case, :else, :if, :or, :rescue, :until, :when, :while ]
18
22
 
23
+ ##
19
24
  # various non-call constructs
25
+
20
26
  OTHER_SCORES = {
21
- :alias => 2,
22
- :assignment => 1,
23
- :block => 1,
24
- :branch => 1,
25
- :lit_fixnum => 0.25,
26
- :sclass => 5,
27
- :super => 1,
28
- :to_proc_icky! => 10,
27
+ :alias => 2,
28
+ :assignment => 1,
29
+ :block => 1,
30
+ :branch => 1,
31
+ :lit_fixnum => 0.25,
32
+ :sclass => 5,
33
+ :super => 1,
34
+ :to_proc_icky! => 10,
29
35
  :to_proc_normal => 5,
30
- :yield => 1,
36
+ :yield => 1,
31
37
  }
32
38
 
39
+ ##
33
40
  # eval forms
41
+
34
42
  SCORES.merge!(:define_method => 5,
35
- :eval => 5,
36
- :module_eval => 5,
37
- :class_eval => 5,
43
+ :eval => 5,
44
+ :module_eval => 5,
45
+ :class_eval => 5,
38
46
  :instance_eval => 5)
39
47
 
48
+ ##
40
49
  # various "magic" usually used for "clever code"
41
- SCORES.merge!(:alias_method => 2,
42
- :extend => 2,
43
- :include => 2,
44
- :instance_method => 2,
45
- :instance_methods => 2,
46
- :method_added => 2,
47
- :method_defined? => 2,
48
- :method_removed => 2,
49
- :method_undefined => 2,
50
- :private_class_method => 2,
51
- :private_instance_methods => 2,
52
- :private_method_defined? => 2,
53
- :protected_instance_methods => 2,
54
- :protected_method_defined? => 2,
55
- :public_class_method => 2,
56
- :public_instance_methods => 2,
57
- :public_method_defined? => 2,
58
- :remove_method => 2,
59
- :send => 3,
60
- :undef_method => 2)
61
50
 
51
+ SCORES.merge!(:alias_method => 2,
52
+ :extend => 2,
53
+ :include => 2,
54
+ :instance_method => 2,
55
+ :instance_methods => 2,
56
+ :method_added => 2,
57
+ :method_defined? => 2,
58
+ :method_removed => 2,
59
+ :method_undefined => 2,
60
+ :private_class_method => 2,
61
+ :private_instance_methods => 2,
62
+ :private_method_defined? => 2,
63
+ :protected_instance_methods => 2,
64
+ :protected_method_defined? => 2,
65
+ :public_class_method => 2,
66
+ :public_instance_methods => 2,
67
+ :public_method_defined? => 2,
68
+ :remove_method => 2,
69
+ :send => 3,
70
+ :undef_method => 2)
71
+
72
+ ##
62
73
  # calls I don't like and usually see being abused
74
+
63
75
  SCORES.merge!(:inject => 2)
64
76
 
65
77
  @@no_class = :main
@@ -81,16 +93,31 @@ class Flog < SexpProcessor
81
93
  @calls["#{self.klass_name}##{self.method_name}"][name] += score * @multiplier
82
94
  end
83
95
 
96
+ ##
97
+ # For the duration of the block the complexity factor is increased
98
+ # by #bonus This allows the complexity of sub-expressions to be
99
+ # influenced by the expressions in which they are found. Yields 42
100
+ # to the supplied block.
101
+
84
102
  def bad_dog! bonus
85
103
  @multiplier += bonus
86
104
  yield 42
87
105
  @multiplier -= bonus
88
106
  end
89
107
 
108
+ ##
109
+ # process each element of #exp in turn.
110
+
90
111
  def bleed exp
91
112
  process exp.shift until exp.empty?
92
113
  end
93
114
 
115
+ ##
116
+ # Process #files with flog, recursively descending directories.
117
+ #--
118
+ # There is no way to exclude directories at present (RCS, SCCS, .svn)
119
+ #++
120
+
94
121
  def flog_files *files
95
122
  files.flatten.each do |file|
96
123
  if File.directory? file then
@@ -103,10 +130,12 @@ class Flog < SexpProcessor
103
130
  process Sexp.from_array(sexp).first
104
131
  rescue SyntaxError => e
105
132
  if e.inspect =~ /<%|%>/ then
106
- warn e.inspect + " at " + e.backtrace.first(5).join(', ')
133
+ warn "#{e.inspect} at #{e.backtrace.first(5).join(', ')}"
107
134
  warn "...stupid lemmings and their bad erb templates... skipping"
108
135
  else
109
- raise e
136
+ raise e unless $c
137
+ warn file
138
+ warn "#{e.inspect} at #{e.backtrace.first(5).join(', ')}"
110
139
  end
111
140
  end
112
141
  end
@@ -119,40 +148,65 @@ class Flog < SexpProcessor
119
148
  @klasses.shift
120
149
  end
121
150
 
151
+ ##
152
+ # returns the first class in the list, or @@no_class if there are
153
+ # none.
154
+
122
155
  def klass_name
123
- @klasses.first || @@no_class
156
+ name = @klasses.first || @@no_class
157
+ if Sexp === name then
158
+ case name.first
159
+ when :colon2 then
160
+ name = name.flatten
161
+ name.delete :const
162
+ name.delete :colon2
163
+ name = name.join("::")
164
+ when :colon3 then
165
+ name = name.last
166
+ end
167
+ end
168
+ name
124
169
  end
125
170
 
171
+ ##
172
+ # Adds name to the list of methods, for the duration of the block
173
+
126
174
  def method name
127
175
  @methods.unshift name
128
176
  yield
129
177
  @methods.shift
130
178
  end
131
179
 
180
+ ##
181
+ # returns the first method in the list, or @@no_method if there are
182
+ # none.
183
+
132
184
  def method_name
133
185
  @methods.first || @@no_method
134
186
  end
135
187
 
188
+ ##
189
+ # Report results to #io, STDOUT by default.
190
+
136
191
  def report io = $stdout
137
192
  current = 0
193
+ totals = self.totals
138
194
  total_score = self.total
139
195
  max = total_score * THRESHOLD
140
- totals = self.totals
141
-
142
- if $s then
143
- io.puts total_score
144
- exit 0
145
- end
146
196
 
147
- io.puts "Total score = #{total_score}"
197
+ io.puts "Total Flog = %.1f (%.1f +/- %.1f flog / method)" % [total_score, self.average, self.stddev]
148
198
  io.puts
149
199
 
200
+ exit 0 if $s
201
+
150
202
  @calls.sort_by { |k,v| -totals[k] }.each do |klass_method, calls|
203
+ next if $m and klass_method =~ /##{@@no_method}/
151
204
  total = totals[klass_method]
152
205
  io.puts "%s: (%.1f)" % [klass_method, total]
206
+ next if $q
153
207
  calls.sort_by { |k,v| -v }.each do |call, count|
154
208
  io.puts " %6.1f: %s" % [count, call]
155
- end
209
+ end unless $n
156
210
 
157
211
  current += total
158
212
  break if current >= max
@@ -162,22 +216,23 @@ class Flog < SexpProcessor
162
216
  end
163
217
 
164
218
  def reset
165
- @totals = @total_score = nil
219
+ # TODO: rename @totals
220
+ @totals = @total = nil
166
221
  @multiplier = 1.0
167
222
  @calls = Hash.new { |h,k| h[k] = Hash.new 0 }
168
223
  end
169
224
 
170
- def total
171
- self.totals unless @total_score # calculates total_score as well
225
+ attr_reader :total, :average, :stddev
172
226
 
173
- @total_score
174
- end
227
+ ##
228
+ # Return the total score and populates @totals.
175
229
 
176
230
  def totals
177
231
  unless @totals then
178
- @total_score = 0
232
+ @total = 0
179
233
  @totals = Hash.new(0)
180
234
  self.calls.each do |meth, tally|
235
+ next if $m and meth =~ /##{@@no_method}$/
181
236
  a, b, c = 0, 0, 0
182
237
  tally.each do |cat, score|
183
238
  case cat
@@ -188,9 +243,17 @@ class Flog < SexpProcessor
188
243
  end
189
244
  score = Math.sqrt(a*a + b*b + c*c)
190
245
  @totals[meth] = score
191
- @total_score += score
246
+ @total += score
192
247
  end
193
248
  end
249
+
250
+ size = self.calls.size.to_f
251
+ @average = @total / size
252
+
253
+ sum = 0
254
+ @totals.values.each { |i| sum += (i - @average) ** 2 }
255
+ @stddev = (1 / size * sum)
256
+
194
257
  @totals
195
258
  end
196
259
 
@@ -212,6 +275,7 @@ class Flog < SexpProcessor
212
275
  end
213
276
  s()
214
277
  end
278
+ alias :process_or :process_and
215
279
 
216
280
  def process_attrasgn(exp)
217
281
  add_to_score :assignment, OTHER_SCORES[:assignment]
@@ -234,7 +298,6 @@ class Flog < SexpProcessor
234
298
  s()
235
299
  end
236
300
 
237
- # [:block_pass, [:lit, :blah], [:fcall, :foo]]
238
301
  def process_block_pass(exp)
239
302
  arg = exp.shift
240
303
  call = exp.shift
@@ -298,6 +361,8 @@ class Flog < SexpProcessor
298
361
  process exp.shift # assigment, if any
299
362
  s()
300
363
  end
364
+ alias :process_iasgn :process_dasgn_curr
365
+ alias :process_lasgn :process_dasgn_curr
301
366
 
302
367
  def process_defn(exp)
303
368
  self.method exp.shift do
@@ -321,13 +386,8 @@ class Flog < SexpProcessor
321
386
  end
322
387
  s()
323
388
  end
324
-
325
- def process_iasgn(exp)
326
- add_to_score :assignment, OTHER_SCORES[:assignment]
327
- exp.shift # name
328
- process exp.shift # rhs
329
- s()
330
- end
389
+ alias :process_rescue :process_else
390
+ alias :process_when :process_else
331
391
 
332
392
  def process_if(exp)
333
393
  add_to_score :branch, OTHER_SCORES[:branch]
@@ -343,7 +403,8 @@ class Flog < SexpProcessor
343
403
  context = (self.context - [:class, :module, :scope])
344
404
  if context.uniq.sort_by {|s|s.to_s} == [:block, :iter] then
345
405
  recv = exp.first
346
- if recv[0] == :call and recv[1] == nil and recv.arglist[1] and [:lit, :str].include? recv.arglist[1][0] then
406
+ if (recv[0] == :call and recv[1] == nil and recv.arglist[1] and
407
+ [:lit, :str].include? recv.arglist[1][0]) then
347
408
  msg = recv[2]
348
409
  submsg = recv.arglist[1][1]
349
410
  self.method submsg do
@@ -366,13 +427,6 @@ class Flog < SexpProcessor
366
427
  s()
367
428
  end
368
429
 
369
- def process_lasgn(exp)
370
- add_to_score :assignment, OTHER_SCORES[:assignment]
371
- exp.shift # name
372
- process exp.shift # rhs
373
- s()
374
- end
375
-
376
430
  def process_lit(exp)
377
431
  value = exp.shift
378
432
  case value
@@ -390,8 +444,7 @@ class Flog < SexpProcessor
390
444
 
391
445
  def process_masgn(exp)
392
446
  add_to_score :assignment, OTHER_SCORES[:assignment]
393
- process exp.shift # lhs
394
- process exp.shift # rhs
447
+ bleed exp
395
448
  s()
396
449
  end
397
450
 
@@ -402,23 +455,6 @@ class Flog < SexpProcessor
402
455
  s()
403
456
  end
404
457
 
405
- def process_or(exp)
406
- add_to_score :branch, OTHER_SCORES[:branch]
407
- bad_dog! 0.1 do
408
- process exp.shift # lhs
409
- process exp.shift # rhs
410
- end
411
- s()
412
- end
413
-
414
- def process_rescue(exp)
415
- add_to_score :branch, OTHER_SCORES[:branch]
416
- bad_dog! 0.1 do
417
- bleed exp
418
- end
419
- s()
420
- end
421
-
422
458
  def process_sclass(exp)
423
459
  bad_dog! 0.5 do
424
460
  recv = process exp.shift
@@ -435,24 +471,6 @@ class Flog < SexpProcessor
435
471
  s()
436
472
  end
437
473
 
438
- def process_until(exp)
439
- add_to_score :branch, OTHER_SCORES[:branch]
440
- bad_dog! 0.1 do
441
- process exp.shift # cond
442
- process exp.shift # body
443
- end
444
- exp.shift # pre/post
445
- s()
446
- end
447
-
448
- def process_when(exp)
449
- add_to_score :branch, OTHER_SCORES[:branch]
450
- bad_dog! 0.1 do
451
- bleed exp
452
- end
453
- s()
454
- end
455
-
456
474
  def process_while(exp)
457
475
  add_to_score :branch, OTHER_SCORES[:branch]
458
476
  bad_dog! 0.1 do
@@ -462,6 +480,7 @@ class Flog < SexpProcessor
462
480
  exp.shift # pre/post
463
481
  s()
464
482
  end
483
+ alias :process_until :process_while
465
484
 
466
485
  def process_yield(exp)
467
486
  add_to_score :yield, OTHER_SCORES[:yield]
data/unpack.rb CHANGED
File without changes
data/update_scores.rb CHANGED
@@ -1,70 +1,63 @@
1
- #!/usr/local/bin/ruby -ws
1
+ #!/usr/bin/env ruby -ws
2
2
 
3
- $: << 'lib'
4
- $: << '../../ParseTree/dev/lib'
3
+ # Update the flog scores for a specific set of gems.
4
+
5
+ $: << 'lib' << '../../ParseTree/dev/lib'
6
+ $:.unshift File.expand_path("~/Work/svn/rubygems/lib")
7
+
8
+ require 'yaml'
5
9
  require 'flog'
6
- require 'rubygems/source_info_cache'
10
+ require 'gem_updater'
7
11
 
8
12
  $u ||= false
9
13
  $f ||= false
10
14
 
11
- $score_file = '../dev/scores.yml'
12
- $misc_error = [-1]
13
- $syntax_error = [-2]
14
- $no_methods = ["", -3]
15
- $no_gem = [-4]
15
+ $score_file = '../dev/scores.yml'
16
+ $misc_error = {:total => -1, :average => -1, :methods => {}}
17
+ $syntax_error = {:total => -2, :average => -2, :methods => {}}
18
+ $no_gem = {:total => -4, :average => -4, :methods => {}}
16
19
 
17
- max = (ARGV.shift || 10).to_i
20
+ max = (ARGV.shift || 10).to_i
18
21
 
19
22
  scores = YAML.load(File.read($score_file)) rescue {}
20
23
 
21
- ["ruby-aes-table1-1.0.gem",
22
- "ruby-aes-unroll1-1.0.gem",
23
- "hpricot-scrub-0.2.0.gem",
24
- "extract_curves-0.0.1.gem",
25
- "rfeedparser-ictv-0.9.931.gem",
26
- "spec_unit-0.0.1.gem"].each do|p|
24
+ ##
25
+ # Save the scores in $score_file.
26
+ #--
27
+ # Creates a new file, then renames to overwrite the old one.
28
+ # Wouldn't it be better to copy the old one, then create a new
29
+ # one so you can do a diff?
30
+ #++
31
+
32
+ def save_scores scores
33
+ File.open("#{$score_file}.new", 'w') do |f|
34
+ warn "*** saving scores"
35
+ YAML.dump scores, f
36
+ end
37
+ File.rename "#{$score_file}.new", $score_file
38
+ end
39
+
40
+ GemUpdater::stupid_gems.each do|p|
27
41
  scores[p] = $no_gem.dup
28
42
  end
29
43
 
30
- Dir.mkdir "../gems" unless File.directory? "../gems"
44
+ GemUpdater::initialize_dir
31
45
 
32
46
  if $u then
33
- puts "updating mirror"
34
-
35
- Dir.chdir "../gems" do
36
- cache = Gem::SourceInfoCache.cache_data['http://gems.rubyforge.org']
37
-
38
- gems = Dir["*.gem"]
39
- old = gems - cache.source_index.latest_specs.values.map { |spec|
40
- "#{spec.full_name}.gem"
41
- }
42
-
43
- puts "deleting #{old.size} gems"
44
- old.each do |gem|
45
- scores.delete gem
46
- File.unlink gem
47
- end
48
-
49
- new = cache.source_index.latest_specs.map { |name, spec|
50
- "#{spec.full_name}.gem"
51
- } - gems
52
-
53
- puts "fetching #{new.size} gems"
54
- new.each do |gem|
55
- next if scores[gem] == $no_gem unless $f # FIX
56
- unless system "wget http://gems.rubyforge.org/gems/#{gem}" then
57
- scores[gem] = $no_gem
58
- end
59
- end
60
- end
47
+ GemUpdater.update_gem_tarballs
48
+ exit 1
61
49
  end
62
50
 
63
- my_projects = Regexp.union("InlineFortran", "ParseTree", "RubyInline", "ZenTest", "bfts", "box_layout", "flog", "heckle", "image_science", "miniunit", "png", "ruby2ruby", "vlad", "zentest", "ZenHacks", "rubyforge", "RubyToC", "hoe")
51
+ my_projects = Regexp.union("InlineFortran", "ParseTree", "RubyInline",
52
+ "RubyToC", "ZenHacks", "ZenTest", "bfts",
53
+ "box_layout", "flog", "heckle", "hoe",
54
+ "image_science", "miniunit", "png", "ruby2ruby",
55
+ "rubyforge", "vlad", "zentest")
64
56
 
65
57
  $owners = {}
66
- cache = Marshal.load(File.read(Gem::SourceInfoCache.new.cache_file))
67
- cache['http://gems.rubyforge.org'].source_index.latest_specs.map { |name, spec|
58
+
59
+ GemUpdater.get_latest_gems.each do |spec|
60
+ name = spec.name
68
61
  owner = spec.authors.compact
69
62
  owner = Array(spec.email) if owner.empty?
70
63
  owner.map! { |o| o.sub(/\s*[^ \w@.].*$/, '') }
@@ -73,17 +66,22 @@ cache['http://gems.rubyforge.org'].source_index.latest_specs.map { |name, spec|
73
66
  # because we screwed these up back before hoe
74
67
  owner << "Eric Hodel" if name =~ /bfts|RubyToC|ParseTree|heckle/
75
68
 
76
- $owners["#{spec.full_name}.gem"] = owner.uniq
77
- }
69
+ $owners["#{spec.full_name}.tgz"] = owner.uniq || 'omg I have no idea'
70
+ end
78
71
 
79
72
  def score_for dir
80
73
  files = `find #{dir} -name \\*.rb | grep -v gen.*templ`.split(/\n/)
81
74
 
82
75
  flogger = Flog.new
83
76
  flogger.flog_files files
84
- methods = flogger.totals.reject { |k,v| k =~ /\#none$/ }.sort_by { |k,v| v }
85
- methods = [$no_methods.dup] if methods.empty?
86
- [flogger.total] + methods
77
+ methods = flogger.totals.reject { |k,v| k =~ /\#none$/ }
78
+ {
79
+ :total => flogger.total,
80
+ :size => methods.size,
81
+ :average => flogger.average,
82
+ :stddev => flogger.stddev,
83
+ :methods => methods
84
+ }
87
85
  rescue SyntaxError => e
88
86
  warn e.inspect + " at " + e.backtrace.first(5).join(', ') if $v
89
87
  $syntax_error.dup
@@ -92,44 +90,40 @@ rescue => e
92
90
  $misc_error.dup
93
91
  end
94
92
 
95
- def save_scores scores
96
- File.open("#{$score_file}.new", 'w') do |f|
97
- warn "*** saving scores"
98
- YAML.dump scores, f
99
- end
100
- File.rename "#{$score_file}.new", $score_file
101
- end
102
-
93
+ # extract all the gems and process the data for them.
103
94
  begin
104
95
  dirty = false
105
96
  Dir.chdir "../gems" do
106
- Dir["*.gem"].each_with_index do |gem, i|
97
+ Dir["*.tgz"].each_with_index do |gem, i|
107
98
  project = File.basename gem
108
- next if scores.has_key? project unless $f and scores[project][0] < 0
99
+ next if scores.has_key? project unless $f and scores[project][:total] < 0
109
100
  dirty = true
110
101
  begin
111
102
  warn gem
112
- dir = gem.sub(/\.gem$/, '')
113
- Dir.mkdir dir
103
+ dir = gem.sub(/\.tgz$/, '')
104
+
105
+ system "tar -zmxf #{gem} 2> /dev/null"
106
+
114
107
  Dir.chdir dir do
115
- system "(tar -Oxf ../#{gem} data.tar.gz | tar zxf -) 2> /dev/null"
116
108
  system "chmod -R a+r ."
117
109
  scores[project] = score_for(File.directory?('lib') ? 'lib' : '.')
118
110
  end
119
111
  ensure
120
112
  system "rm -rf #{dir}"
121
113
  end
122
-
123
- if i % 500 == 0 then
124
- save_scores scores
125
- end
126
114
  end
127
115
  end
128
116
  ensure
129
117
  save_scores scores if dirty
130
118
  end
131
119
 
132
- scores.reject! { |k,v| v.first.nil? or Fixnum === v.last or v.last.last < 0 }
120
+ scores.reject! { |k,v| v[:total].nil? or v[:methods].empty? }
121
+
122
+ class Hash
123
+ def sorted_methods
124
+ self[:methods].sort_by { |k,v| -v }
125
+ end
126
+ end
133
127
 
134
128
  class Array
135
129
  def sum
@@ -170,13 +164,13 @@ def report title, data
170
164
  end
171
165
  end
172
166
 
173
- project_numbers = scores.map { |k,v| [k, v[1..-1].map {|_,n| n}.flatten] }
167
+ project_numbers = scores.map { |k,v| [k, v[:methods].map {|_,n| n}.flatten] }
174
168
  project_stats = project_numbers.map { |k,v| [k, v.size, v.average, v.stddev] }
175
169
 
176
170
  title "Statistics" do
177
- flog_numbers = scores.map { |k,v| v.first }
178
- all_scores = scores.map { |k,v| v[1..-1].map { |_,n| n } }.flatten
179
- method_counts = project_stats.map { |n,c,a,s| c }
171
+ flog_numbers = scores.map { |k,v| v[:total] }
172
+ all_scores = scores.map { |k,v| v[:methods].values }.flatten
173
+ method_counts = scores.map { |k,v| v[:size] }
180
174
 
181
175
  puts "total # gems : %8d" % scores.size
182
176
  puts "total # methods : %8d" % all_scores.size
@@ -194,14 +188,27 @@ def report_worst section, data
194
188
  end
195
189
  end
196
190
 
197
- worst = scores.sort_by { |k,v| -v.first }.first(max)
191
+ worst = scores.sort_by { |k,v| -v[:total] }.first(max)
198
192
  report_worst "Worst Projects EVAR", worst do |project, score|
199
- [score.first, project, $owners[project].join(', ')]
193
+ owner = $owners[project].join(', ') rescue nil
194
+ raise "#{project} seems not to have an owner" if owner.nil?
195
+ [score[:total], project, owner]
200
196
  end
201
197
 
202
- worst = scores.sort_by { |k,v| -v.last.last }.first(max)
203
- report_worst "Worst Methods EVAR", worst do |project, methods|
204
- [methods.last.last, project, methods.last.first]
198
+ worst = {}
199
+ scores.each do |long_name, spec|
200
+ name = long_name.sub(/-(\d+\.)*\d+\.gem$/, '')
201
+ spec[:methods].each do |method_name, score|
202
+ worst[[name, method_name]] = score
203
+ end
204
+ end
205
+
206
+ worst = worst.sort_by { |_,v| -v }.first(max)
207
+
208
+ max_size = worst.map { |(name, meth), score| name.size }.max
209
+ title "Worth Methods EVAR"
210
+ worst.each_with_index do |((name, meth), score), i|
211
+ puts "%3d: %9.2f: %-#{max_size}s %s" % [i + 1, score, name, meth]
205
212
  end
206
213
 
207
214
  report "Methods per Gem", project_stats.sort_by { |n, c, a, sd| -c }.first(max)
@@ -212,7 +219,7 @@ $projects_per_owner = Hash.new { |h,k| h[k] = {} }
212
219
  $owners.each do |project, owners|
213
220
  next unless scores.has_key? project # bad project
214
221
  owners.each do |owner|
215
- score = scores[project].first || 10000
222
+ score = scores[project][:total] || 1000000
216
223
  $projects_per_owner[owner][project] = score
217
224
  $score_per_owner[owner] += score
218
225
  end
@@ -234,5 +241,5 @@ report_bad_people "Top Flog Scores per Developer" do
234
241
  end
235
242
 
236
243
  report_bad_people "Most Prolific Developers" do |k,v|
237
- $projects_per_owner.sort_by { |k,v| -v.size }.first(max)
244
+ $projects_per_owner.sort_by { |k,v| [-v.size, -$score_per_owner[k]] }.first(max)
238
245
  end
metadata CHANGED
@@ -1,73 +1,94 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: flog
5
3
  version: !ruby/object:Gem::Version
6
- version: 1.1.0
7
- date: 2007-08-21 00:00:00 -07:00
8
- summary: Flog reports the most tortured code in an easy to read pain report. The higher the score, the more pain the code is in.
9
- require_paths:
10
- - lib
11
- email: ryand-ruby@zenspider.com
12
- homepage: http://ruby.sadi.st/
13
- rubyforge_project: seattlerb
14
- description: "Flog reports the most tortured code in an easy to read pain report. The higher the score, the more pain the code is in. % ./bin/flog bin/flog Total score = 128.7 Flog#report: (21) 4: puts 2: sort_by ..."
15
- autorequire:
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
4
+ version: 1.2.0
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
7
  - Ryan Davis
31
- files:
32
- - History.txt
33
- - Manifest.txt
34
- - README.txt
35
- - Rakefile
36
- - bin/flog
37
- - lib/flog.rb
38
- - unpack.rb
39
- - update_scores.rb
40
- test_files: []
41
-
42
- rdoc_options:
43
- - --main
44
- - README.txt
45
- extra_rdoc_files:
46
- - History.txt
47
- - Manifest.txt
48
- - README.txt
49
- executables:
50
- - flog
51
- extensions: []
52
-
53
- requirements: []
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
54
11
 
12
+ date: 2008-10-22 00:00:00 -05:00
13
+ default_executable:
55
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sexp_processor
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "3.0"
24
+ version:
56
25
  - !ruby/object:Gem::Dependency
57
26
  name: ParseTree
27
+ type: :runtime
58
28
  version_requirement:
59
- version_requirements: !ruby/object:Gem::Version::Requirement
29
+ version_requirements: !ruby/object:Gem::Requirement
60
30
  requirements:
61
- - - ">="
31
+ - - ~>
62
32
  - !ruby/object:Gem::Version
63
- version: 2.0.0
33
+ version: "3.0"
64
34
  version:
65
35
  - !ruby/object:Gem::Dependency
66
36
  name: hoe
37
+ type: :development
67
38
  version_requirement:
68
- version_requirements: !ruby/object:Gem::Version::Requirement
39
+ version_requirements: !ruby/object:Gem::Requirement
69
40
  requirements:
70
41
  - - ">="
71
42
  - !ruby/object:Gem::Version
72
- version: 1.3.0
43
+ version: 1.8.0
73
44
  version:
45
+ description: Flog reports the most tortured code in an easy to read pain report. The higher the score, the more pain the code is in.
46
+ email:
47
+ - ryand-ruby@zenspider.com
48
+ executables:
49
+ - flog
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - History.txt
54
+ - Manifest.txt
55
+ - README.txt
56
+ files:
57
+ - History.txt
58
+ - Manifest.txt
59
+ - README.txt
60
+ - Rakefile
61
+ - bin/flog
62
+ - gem_updater.rb
63
+ - lib/flog.rb
64
+ - unpack.rb
65
+ - update_scores.rb
66
+ has_rdoc: true
67
+ homepage: http://ruby.sadi.st/
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --main
71
+ - README.txt
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: "0"
85
+ version:
86
+ requirements: []
87
+
88
+ rubyforge_project: seattlerb
89
+ rubygems_version: 1.3.0
90
+ signing_key:
91
+ specification_version: 2
92
+ summary: Flog reports the most tortured code in an easy to read pain report
93
+ test_files: []
94
+