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 +24 -0
- data/Manifest.txt +2 -0
- data/README.txt +1 -1
- data/Rakefile +1 -1
- data/bin/flog +20 -2
- data/lib/flog.rb +328 -60
- data/unpack.rb +22 -0
- data/update_scores.rb +238 -0
- metadata +6 -4
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
data/README.txt
CHANGED
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/).
|
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 -
|
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.
|
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
|
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
|
-
|
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
|
-
@
|
73
|
+
@klasses = []
|
74
|
+
@methods = []
|
48
75
|
self.auto_shift_type = true
|
49
76
|
self.require_empty = false # HACK
|
50
|
-
|
51
|
-
|
77
|
+
self.reset
|
78
|
+
end
|
52
79
|
|
53
|
-
|
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
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
66
|
-
|
67
|
-
|
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
|
-
|
71
|
-
|
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|
|
74
|
-
total =
|
75
|
-
puts "%s: (
|
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 " %
|
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
|
-
|
84
|
-
|
160
|
+
ensure
|
161
|
+
self.reset
|
85
162
|
end
|
86
163
|
|
87
|
-
def
|
88
|
-
@totals
|
89
|
-
@
|
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
|
93
|
-
@
|
94
|
-
|
95
|
-
@
|
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,
|
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 :
|
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 => [
|
252
|
+
raise({:block_pass => [arg, call]}.inspect)
|
122
253
|
end
|
123
254
|
|
124
|
-
|
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
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
149
|
-
|
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
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
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,
|
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
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
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
|
-
|
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
|
7
|
-
date: 2007-08-
|
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://
|
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.
|
72
|
+
version: 1.3.0
|
71
73
|
version:
|