flog 1.0.2 → 1.1.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,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: