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 +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:
|