flog 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,27 @@
1
+ == 1.1.0 / 2007-08-21
2
+
3
+ * 3 major enhancements:
4
+ * Added assignments and branches and a lot of other stuff. rad.
5
+ * Added process_iter section for DSL style blocks (rake tasks etc).
6
+ * Made Flog usable as a library.
7
+ * 12 minor enhancements:
8
+ * Added -a flag to turn off threshold culling for other tools.
9
+ * Added -s for summarizing the score.
10
+ * Added -v feedback to know what file you're flogging.
11
+ * Added branching penalty so tons of nested conditionals get beat down.
12
+ * Added send (3).
13
+ * Capture and ignore SyntaxErrors from template/generator code. Stupid DHH.
14
+ * Report can now take an IO object.
15
+ * block_args now penalizes all non-benign forms of block_pass.
16
+ * Added usage to bin/flog. Moved -I processing to bin/flog.
17
+ * Added unpack.rb and update_scores.rb at base level (not installed)
18
+ * Added scoring for block_pass.
19
+ * Converted totals to use distance formula on ABC's.
20
+ * 3 bug fixes:
21
+ * Ran flog on every latest gem available. Found a bunch of problems.
22
+ * Use a stack for both class/module and method accounting.
23
+ * block_args weren't processing the arg
24
+
1
25
  == 1.0.2 / 2007-08-01
2
26
 
3
27
  * 1 bug fix:
data/Manifest.txt CHANGED
@@ -4,3 +4,5 @@ README.txt
4
4
  Rakefile
5
5
  bin/flog
6
6
  lib/flog.rb
7
+ unpack.rb
8
+ update_scores.rb
data/README.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  flog
2
2
  by Ryan Davis, Seattle.rb
3
- http://seattlerb.rubyforge.org/
3
+ http://ruby.sadi.st/
4
4
  http://rubyforge.org/projects/seattlerb
5
5
 
6
6
  == DESCRIPTION:
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ Hoe.new('flog', Flog::VERSION) do |p|
8
8
  p.rubyforge_name = 'seattlerb'
9
9
  p.summary = p.paragraphs_of('README.txt', 2).first
10
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/).last.strip
11
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2..-1].map {|u| u.strip }
12
12
  p.changes = p.paragraphs_of('History.txt', 1).join("\n\n")
13
13
 
14
14
  p.extra_deps << ["ParseTree", '>= 2.0.0']
data/bin/flog CHANGED
@@ -1,7 +1,25 @@
1
- #!/usr/local/bin/ruby -w
1
+ #!/usr/local/bin/ruby -ws
2
2
 
3
3
  require 'flog'
4
4
 
5
+ ARGV.push "-" if ARGV.empty?
6
+
7
+ if defined? $h then
8
+ puts "#{File.basename $0} options dirs_or_files"
9
+ puts " -a display all flog results, not top 60%"
10
+ puts " -h display help"
11
+ puts " -I=path extend $LOAD_PATH with path"
12
+ puts " -s display total score only"
13
+ puts " -v verbosely display progress and errors"
14
+ exit 0
15
+ end
16
+
17
+ if defined? $I and String === $I then
18
+ $I.split(/:/).each do |dir|
19
+ $: << dir
20
+ end
21
+ end
22
+
5
23
  flogger = Flog.new
6
- flogger.process_files ARGV
24
+ flogger.flog_files ARGV
7
25
  flogger.report
data/lib/flog.rb CHANGED
@@ -3,24 +3,44 @@ require 'parse_tree'
3
3
  require 'sexp_processor'
4
4
  require 'unified_ruby'
5
5
 
6
+ $a ||= false
7
+ $s ||= false
8
+ $v ||= false
9
+
6
10
  class Flog < SexpProcessor
7
- VERSION = '1.0.2'
11
+ VERSION = '1.1.0'
8
12
 
9
13
  include UnifiedRuby
10
14
 
11
- THRESHOLD = 0.60
12
-
15
+ THRESHOLD = $a ? 1.0 : 0.60
13
16
  SCORES = Hash.new(1)
17
+ BRANCHING = [ :and, :case, :else, :if, :or, :rescue, :until, :when, :while ]
14
18
 
19
+ # various non-call constructs
20
+ 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,
29
+ :to_proc_normal => 5,
30
+ :yield => 1,
31
+ }
32
+
33
+ # eval forms
15
34
  SCORES.merge!(:define_method => 5,
16
35
  :eval => 5,
17
36
  :module_eval => 5,
18
37
  :class_eval => 5,
19
38
  :instance_eval => 5)
20
39
 
40
+ # various "magic" usually used for "clever code"
21
41
  SCORES.merge!(:alias_method => 2,
22
- :include => 2,
23
42
  :extend => 2,
43
+ :include => 2,
24
44
  :instance_method => 2,
25
45
  :instance_methods => 2,
26
46
  :method_added => 2,
@@ -36,63 +56,142 @@ class Flog < SexpProcessor
36
56
  :public_instance_methods => 2,
37
57
  :public_method_defined? => 2,
38
58
  :remove_method => 2,
59
+ :send => 3,
39
60
  :undef_method => 2)
40
61
 
41
- @@no_class = :none
62
+ # calls I don't like and usually see being abused
63
+ SCORES.merge!(:inject => 2)
64
+
65
+ @@no_class = :main
42
66
  @@no_method = :none
43
67
 
68
+ attr_reader :calls
69
+
44
70
  def initialize
45
71
  super
46
72
  @pt = ParseTree.new(false)
47
- @klass_name, @method_name = @@no_class, @@no_method
73
+ @klasses = []
74
+ @methods = []
48
75
  self.auto_shift_type = true
49
76
  self.require_empty = false # HACK
50
- @totals = Hash.new 0
51
- @multiplier = 1.0
77
+ self.reset
78
+ end
52
79
 
53
- @calls = Hash.new { |h,k| h[k] = Hash.new 0 }
80
+ def add_to_score(name, score)
81
+ @calls["#{self.klass_name}##{self.method_name}"][name] += score * @multiplier
82
+ end
83
+
84
+ def bad_dog! bonus
85
+ @multiplier += bonus
86
+ yield 42
87
+ @multiplier -= bonus
54
88
  end
55
89
 
56
- def process_files *files
90
+ def bleed exp
91
+ process exp.shift until exp.empty?
92
+ end
93
+
94
+ def flog_files *files
57
95
  files.flatten.each do |file|
58
- next unless File.file? file or file == "-"
59
- ruby = file == "-" ? $stdin.read : File.read(file)
60
- sexp = @pt.parse_tree_for_string(ruby, file)
61
- process Sexp.from_array(sexp)
96
+ if File.directory? file then
97
+ flog_files Dir["#{file}/**/*.rb"]
98
+ else
99
+ warn "** flogging #{file}" if $v
100
+ ruby = file == "-" ? $stdin.read : File.read(file)
101
+ begin
102
+ sexp = @pt.parse_tree_for_string(ruby, file)
103
+ process Sexp.from_array(sexp).first
104
+ rescue SyntaxError => e
105
+ if e.inspect =~ /<%|%>/ then
106
+ warn e.inspect + " at " + e.backtrace.first(5).join(', ')
107
+ warn "...stupid lemmings and their bad erb templates... skipping"
108
+ else
109
+ raise e
110
+ end
111
+ end
112
+ end
62
113
  end
63
114
  end
64
115
 
65
- def report
66
- total_score = @totals.values.inject(0) { |sum,n| sum + n }
67
- max = total_score * THRESHOLD
116
+ def klass name
117
+ @klasses.unshift name
118
+ yield
119
+ @klasses.shift
120
+ end
121
+
122
+ def klass_name
123
+ @klasses.first || @@no_class
124
+ end
125
+
126
+ def method name
127
+ @methods.unshift name
128
+ yield
129
+ @methods.shift
130
+ end
131
+
132
+ def method_name
133
+ @methods.first || @@no_method
134
+ end
135
+
136
+ def report io = $stdout
68
137
  current = 0
138
+ total_score = self.total
139
+ max = total_score * THRESHOLD
140
+ totals = self.totals
69
141
 
70
- puts "Total score = #{total_score}"
71
- puts
142
+ if $s then
143
+ io.puts total_score
144
+ exit 0
145
+ end
146
+
147
+ io.puts "Total score = #{total_score}"
148
+ io.puts
72
149
 
73
- @calls.sort_by { |k,v| -@totals[k] }.each do |klass_method, calls|
74
- total = @totals[klass_method]
75
- puts "%s: (%d)" % [klass_method, total]
150
+ @calls.sort_by { |k,v| -totals[k] }.each do |klass_method, calls|
151
+ total = totals[klass_method]
152
+ io.puts "%s: (%.1f)" % [klass_method, total]
76
153
  calls.sort_by { |k,v| -v }.each do |call, count|
77
- puts " %4d: %s" % [count, call]
154
+ io.puts " %6.1f: %s" % [count, call]
78
155
  end
79
156
 
80
157
  current += total
81
158
  break if current >= max
82
159
  end
83
- rescue
84
- # do nothing
160
+ ensure
161
+ self.reset
85
162
  end
86
163
 
87
- def add_to_score(name, score)
88
- @totals["#{@klass_name}##{@method_name}"] += score * @multiplier
89
- @calls["#{@klass_name}##{@method_name}"][name] += score * @multiplier
164
+ def reset
165
+ @totals = @total_score = nil
166
+ @multiplier = 1.0
167
+ @calls = Hash.new { |h,k| h[k] = Hash.new 0 }
90
168
  end
91
169
 
92
- def bad_dog! bonus
93
- @multiplier += bonus
94
- yield
95
- @multiplier -= bonus
170
+ def total
171
+ self.totals unless @total_score # calculates total_score as well
172
+
173
+ @total_score
174
+ end
175
+
176
+ def totals
177
+ unless @totals then
178
+ @total_score = 0
179
+ @totals = Hash.new(0)
180
+ self.calls.each do |meth, tally|
181
+ a, b, c = 0, 0, 0
182
+ tally.each do |cat, score|
183
+ case cat
184
+ when :assignment then a += score
185
+ when :branch then b += score
186
+ else c += score
187
+ end
188
+ end
189
+ score = Math.sqrt(a*a + b*b + c*c)
190
+ @totals[meth] = score
191
+ @total_score += score
192
+ end
193
+ end
194
+ @totals
96
195
  end
97
196
 
98
197
  ############################################################
@@ -101,7 +200,37 @@ class Flog < SexpProcessor
101
200
  def process_alias(exp)
102
201
  process exp.shift
103
202
  process exp.shift
104
- add_to_score :alias, 2
203
+ add_to_score :alias, OTHER_SCORES[:alias]
204
+ s()
205
+ end
206
+
207
+ def process_and(exp)
208
+ add_to_score :branch, OTHER_SCORES[:branch]
209
+ bad_dog! 0.1 do
210
+ process exp.shift # lhs
211
+ process exp.shift # rhs
212
+ end
213
+ s()
214
+ end
215
+
216
+ def process_attrasgn(exp)
217
+ add_to_score :assignment, OTHER_SCORES[:assignment]
218
+ process exp.shift # lhs
219
+ exp.shift # name
220
+ process exp.shift # rhs
221
+ s()
222
+ end
223
+
224
+ def process_attrset(exp)
225
+ add_to_score :assignment, OTHER_SCORES[:assignment]
226
+ raise exp.inspect
227
+ s()
228
+ end
229
+
230
+ def process_block(exp)
231
+ bad_dog! 0.1 do
232
+ bleed exp
233
+ end
105
234
  s()
106
235
  end
107
236
 
@@ -110,18 +239,22 @@ class Flog < SexpProcessor
110
239
  arg = exp.shift
111
240
  call = exp.shift
112
241
 
242
+ add_to_score :block_pass, OTHER_SCORES[:block]
243
+
113
244
  case arg.first
114
- when :iter then
115
- add_to_score :to_proc_iter_wtf?, 6
116
- when :lit, :call, :iter then
117
- add_to_score :to_proc, 3
118
- when :lvar, :dvar, :ivar, :nil then
245
+ when :lvar, :dvar, :ivar, :cvar, :self, :const, :nil then
119
246
  # do nothing
247
+ when :lit, :call then
248
+ add_to_score :to_proc_normal, OTHER_SCORES[:to_proc_normal]
249
+ when :iter, *BRANCHING then
250
+ add_to_score :to_proc_icky!, OTHER_SCORES[:to_proc_icky!]
120
251
  else
121
- raise({:block_pass => [call, arg]}.inspect)
252
+ raise({:block_pass => [arg, call]}.inspect)
122
253
  end
123
254
 
124
- call = process call
255
+ process arg
256
+ process call
257
+
125
258
  s()
126
259
  end
127
260
 
@@ -140,30 +273,103 @@ class Flog < SexpProcessor
140
273
  s()
141
274
  end
142
275
 
143
- def process_class(exp)
144
- @klass_name = exp.shift
145
- bad_dog! 1.0 do
146
- supr = process exp.shift
276
+ def process_case(exp)
277
+ add_to_score :branch, OTHER_SCORES[:branch]
278
+ process exp.shift # recv
279
+ bad_dog! 0.1 do
280
+ bleed exp
147
281
  end
148
- until exp.empty?
149
- process exp.shift
282
+ s()
283
+ end
284
+
285
+ def process_class(exp)
286
+ self.klass exp.shift do
287
+ bad_dog! 1.0 do
288
+ supr = process exp.shift
289
+ end
290
+ bleed exp
150
291
  end
151
- @klass_name = @@no_class
292
+ s()
293
+ end
294
+
295
+ def process_dasgn_curr(exp)
296
+ add_to_score :assignment, OTHER_SCORES[:assignment]
297
+ exp.shift # name
298
+ process exp.shift # assigment, if any
152
299
  s()
153
300
  end
154
301
 
155
302
  def process_defn(exp)
156
- @method_name = exp.shift
157
- process exp.shift until exp.empty?
158
- @method_name = @@no_method
303
+ self.method exp.shift do
304
+ bleed exp
305
+ end
159
306
  s()
160
307
  end
161
308
 
162
309
  def process_defs(exp)
163
310
  process exp.shift
164
- @method_name = exp.shift
165
- process exp.shift until exp.empty?
166
- @method_name = @@no_method
311
+ self.method exp.shift do
312
+ bleed exp
313
+ end
314
+ s()
315
+ end
316
+
317
+ def process_else(exp)
318
+ add_to_score :branch, OTHER_SCORES[:branch]
319
+ bad_dog! 0.1 do
320
+ bleed exp
321
+ end
322
+ s()
323
+ 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
331
+
332
+ def process_if(exp)
333
+ add_to_score :branch, OTHER_SCORES[:branch]
334
+ process exp.shift # cond
335
+ bad_dog! 0.1 do
336
+ process exp.shift # true
337
+ process exp.shift # false
338
+ end
339
+ s()
340
+ end
341
+
342
+ def process_iter(exp)
343
+ context = (self.context - [:class, :module, :scope])
344
+ if context.uniq.sort_by {|s|s.to_s} == [:block, :iter] then
345
+ 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
347
+ msg = recv[2]
348
+ submsg = recv.arglist[1][1]
349
+ self.method submsg do
350
+ self.klass msg do
351
+ bleed exp
352
+ end
353
+ end
354
+ return s()
355
+ end
356
+ end
357
+
358
+ add_to_score :branch, OTHER_SCORES[:branch]
359
+
360
+ process exp.shift # no penalty for LHS
361
+
362
+ bad_dog! 0.1 do
363
+ bleed exp
364
+ end
365
+
366
+ s()
367
+ end
368
+
369
+ def process_lasgn(exp)
370
+ add_to_score :assignment, OTHER_SCORES[:assignment]
371
+ exp.shift # name
372
+ process exp.shift # rhs
167
373
  s()
168
374
  end
169
375
 
@@ -173,7 +379,7 @@ class Flog < SexpProcessor
173
379
  when 0, -1 then
174
380
  # ignore those because they're used as array indicies instead of first/last
175
381
  when Integer then
176
- add_to_score :lit_fixnum, 0.25
382
+ add_to_score :lit_fixnum, OTHER_SCORES[:lit_fixnum]
177
383
  when Float, Symbol, Regexp, Range then
178
384
  # do nothing
179
385
  else
@@ -182,22 +388,84 @@ class Flog < SexpProcessor
182
388
  s()
183
389
  end
184
390
 
391
+ def process_masgn(exp)
392
+ add_to_score :assignment, OTHER_SCORES[:assignment]
393
+ process exp.shift # lhs
394
+ process exp.shift # rhs
395
+ s()
396
+ end
397
+
185
398
  def process_module(exp)
186
- @klass_name = exp.shift
187
- until exp.empty?
188
- process exp.shift
399
+ self.klass exp.shift do
400
+ bleed exp
401
+ end
402
+ s()
403
+ end
404
+
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
189
418
  end
190
- @klass_name = @@no_class
191
419
  s()
192
420
  end
193
421
 
194
422
  def process_sclass(exp)
195
423
  bad_dog! 0.5 do
196
424
  recv = process exp.shift
197
- process exp.shift until exp.empty?
425
+ bleed exp
426
+ end
427
+
428
+ add_to_score :sclass, OTHER_SCORES[:sclass]
429
+ s()
430
+ end
431
+
432
+ def process_super(exp)
433
+ add_to_score :super, OTHER_SCORES[:super]
434
+ bleed exp
435
+ s()
436
+ end
437
+
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
+ def process_while(exp)
457
+ add_to_score :branch, OTHER_SCORES[:branch]
458
+ bad_dog! 0.1 do
459
+ process exp.shift # cond
460
+ process exp.shift # body
198
461
  end
462
+ exp.shift # pre/post
463
+ s()
464
+ end
199
465
 
200
- add_to_score :sclass, 5
466
+ def process_yield(exp)
467
+ add_to_score :yield, OTHER_SCORES[:yield]
468
+ bleed exp
201
469
  s()
202
470
  end
203
471
  end
data/unpack.rb ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ code = '../code'
4
+
5
+ pattern = ARGV.empty? ? nil : Regexp.union(*ARGV)
6
+
7
+ Dir.mkdir code unless File.directory? code
8
+
9
+ Dir.chdir code do
10
+ Dir["../gems/*.gem"].each do |gem|
11
+ project = File.basename gem
12
+ next unless project =~ pattern if pattern
13
+ dir = project.sub(/\.gem$/, '')
14
+ warn dir
15
+ unless File.directory? dir then
16
+ Dir.mkdir dir
17
+ Dir.chdir dir do
18
+ system "(tar -Oxf ../#{gem} data.tar.gz | tar zxf -) 2> /dev/null"
19
+ end
20
+ end
21
+ end
22
+ end
data/update_scores.rb ADDED
@@ -0,0 +1,238 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ $: << 'lib'
4
+ $: << '../../ParseTree/dev/lib'
5
+ require 'flog'
6
+ require 'rubygems/source_info_cache'
7
+
8
+ $u ||= false
9
+ $f ||= false
10
+
11
+ $score_file = '../dev/scores.yml'
12
+ $misc_error = [-1]
13
+ $syntax_error = [-2]
14
+ $no_methods = ["", -3]
15
+ $no_gem = [-4]
16
+
17
+ max = (ARGV.shift || 10).to_i
18
+
19
+ scores = YAML.load(File.read($score_file)) rescue {}
20
+
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|
27
+ scores[p] = $no_gem.dup
28
+ end
29
+
30
+ Dir.mkdir "../gems" unless File.directory? "../gems"
31
+
32
+ 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
61
+ end
62
+
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")
64
+
65
+ $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|
68
+ owner = spec.authors.compact
69
+ owner = Array(spec.email) if owner.empty?
70
+ owner.map! { |o| o.sub(/\s*[^ \w@.].*$/, '') }
71
+ owner = ["NOT Ryan Davis"] if owner.include? "Ryan Davis" and name !~ my_projects
72
+
73
+ # because we screwed these up back before hoe
74
+ owner << "Eric Hodel" if name =~ /bfts|RubyToC|ParseTree|heckle/
75
+
76
+ $owners["#{spec.full_name}.gem"] = owner.uniq
77
+ }
78
+
79
+ def score_for dir
80
+ files = `find #{dir} -name \\*.rb | grep -v gen.*templ`.split(/\n/)
81
+
82
+ flogger = Flog.new
83
+ 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
87
+ rescue SyntaxError => e
88
+ warn e.inspect + " at " + e.backtrace.first(5).join(', ') if $v
89
+ $syntax_error.dup
90
+ rescue => e
91
+ warn e.inspect + " at " + e.backtrace.first(5).join(', ') if $v
92
+ $misc_error.dup
93
+ end
94
+
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
+
103
+ begin
104
+ dirty = false
105
+ Dir.chdir "../gems" do
106
+ Dir["*.gem"].each_with_index do |gem, i|
107
+ project = File.basename gem
108
+ next if scores.has_key? project unless $f and scores[project][0] < 0
109
+ dirty = true
110
+ begin
111
+ warn gem
112
+ dir = gem.sub(/\.gem$/, '')
113
+ Dir.mkdir dir
114
+ Dir.chdir dir do
115
+ system "(tar -Oxf ../#{gem} data.tar.gz | tar zxf -) 2> /dev/null"
116
+ system "chmod -R a+r ."
117
+ scores[project] = score_for(File.directory?('lib') ? 'lib' : '.')
118
+ end
119
+ ensure
120
+ system "rm -rf #{dir}"
121
+ end
122
+
123
+ if i % 500 == 0 then
124
+ save_scores scores
125
+ end
126
+ end
127
+ end
128
+ ensure
129
+ save_scores scores if dirty
130
+ end
131
+
132
+ scores.reject! { |k,v| v.first.nil? or Fixnum === v.last or v.last.last < 0 }
133
+
134
+ class Array
135
+ def sum
136
+ sum = 0
137
+ self.each { |i| sum += i }
138
+ sum
139
+ end
140
+
141
+ def average
142
+ return self.sum / self.length.to_f
143
+ end
144
+
145
+ def sample_variance
146
+ avg = self.average
147
+ sum = 0
148
+ self.each { |i| sum += (i - avg) ** 2 }
149
+ return (1 / self.length.to_f * sum)
150
+ end
151
+
152
+ def stddev
153
+ return Math.sqrt(self.sample_variance)
154
+ end
155
+ end
156
+
157
+ def title heading
158
+ puts
159
+ puts "#{heading}:"
160
+ puts
161
+ yield if block_given?
162
+ end
163
+
164
+ def report title, data
165
+ max = data.map { |d| d.first.size }.max
166
+
167
+ title "Top #{data.size} #{title}" if title
168
+ data.each_with_index do |(n, c, a, s), i|
169
+ puts "%4d: %-#{max}s: %4d methods, %8.2f +/- %8.2f flog" % [i + 1, n, c, a, s]
170
+ end
171
+ end
172
+
173
+ project_numbers = scores.map { |k,v| [k, v[1..-1].map {|_,n| n}.flatten] }
174
+ project_stats = project_numbers.map { |k,v| [k, v.size, v.average, v.stddev] }
175
+
176
+ 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 }
180
+
181
+ puts "total # gems : %8d" % scores.size
182
+ puts "total # methods : %8d" % all_scores.size
183
+ puts "avg methods / gem : %8.2f +/- %8.2f" % [method_counts.average, method_counts.stddev]
184
+ puts "avg flog / project: %8.2f +/- %8.2f" % [flog_numbers.average, flog_numbers.stddev]
185
+ puts "avg flog / method : %8.2f +/- %8.2f" % [all_scores.average, all_scores.stddev]
186
+ end
187
+
188
+ def report_worst section, data
189
+ title section do
190
+ max_size = data.map { |k| k.first.size }.max
191
+ data.each_with_index do |(k,v), i|
192
+ puts "%3d: %9.2f: %-#{max_size}s %s" % [i + 1, *yield(k, v)]
193
+ end
194
+ end
195
+ end
196
+
197
+ worst = scores.sort_by { |k,v| -v.first }.first(max)
198
+ report_worst "Worst Projects EVAR", worst do |project, score|
199
+ [score.first, project, $owners[project].join(', ')]
200
+ end
201
+
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]
205
+ end
206
+
207
+ report "Methods per Gem", project_stats.sort_by { |n, c, a, sd| -c }.first(max)
208
+ report "Avg Flog / Method", project_stats.sort_by { |n, c, a, sd| -a }.first(max)
209
+
210
+ $score_per_owner = Hash.new(0.0)
211
+ $projects_per_owner = Hash.new { |h,k| h[k] = {} }
212
+ $owners.each do |project, owners|
213
+ next unless scores.has_key? project # bad project
214
+ owners.each do |owner|
215
+ score = scores[project].first || 10000
216
+ $projects_per_owner[owner][project] = score
217
+ $score_per_owner[owner] += score
218
+ end
219
+ end
220
+
221
+ def report_bad_people section
222
+ title section
223
+ bad_people = yield
224
+ max_size = bad_people.map { |a| a.first.size }.max
225
+ fmt = "%4d: %#{max_size}s: %2d projects %8.1f tot %8.1f avg"
226
+ bad_people.each_with_index do |(name, projects), i|
227
+ avg = projects.values.average
228
+ puts fmt % [i + 1, name, projects.size, $score_per_owner[name], avg]
229
+ end
230
+ end
231
+
232
+ report_bad_people "Top Flog Scores per Developer" do
233
+ $projects_per_owner.sort_by { |k,v| -$score_per_owner[k] }.first(max)
234
+ end
235
+
236
+ report_bad_people "Most Prolific Developers" do |k,v|
237
+ $projects_per_owner.sort_by { |k,v| -v.size }.first(max)
238
+ end
metadata CHANGED
@@ -3,13 +3,13 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: flog
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.2
7
- date: 2007-08-01 00:00:00 -07:00
6
+ version: 1.1.0
7
+ date: 2007-08-21 00:00:00 -07:00
8
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
9
  require_paths:
10
10
  - lib
11
11
  email: ryand-ruby@zenspider.com
12
- homepage: http://rubyforge.org/projects/seattlerb
12
+ homepage: http://ruby.sadi.st/
13
13
  rubyforge_project: seattlerb
14
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
15
  autorequire:
@@ -35,6 +35,8 @@ files:
35
35
  - Rakefile
36
36
  - bin/flog
37
37
  - lib/flog.rb
38
+ - unpack.rb
39
+ - update_scores.rb
38
40
  test_files: []
39
41
 
40
42
  rdoc_options:
@@ -67,5 +69,5 @@ dependencies:
67
69
  requirements:
68
70
  - - ">="
69
71
  - !ruby/object:Gem::Version
70
- version: 1.2.2
72
+ version: 1.3.0
71
73
  version: