flog 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+