self-flagellation 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2008-05-30
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
@@ -0,0 +1,19 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/flagellate
6
+ lib/self-flagellation.rb
7
+ lib/self-flagellation/flagellate.rb
8
+ tasks/ann.rake
9
+ tasks/bones.rake
10
+ tasks/gem.rake
11
+ tasks/manifest.rake
12
+ tasks/notes.rake
13
+ tasks/post_load.rake
14
+ tasks/rdoc.rake
15
+ tasks/rubyforge.rake
16
+ tasks/setup.rb
17
+ tasks/spec.rake
18
+ tasks/svn.rake
19
+ tasks/test.rake
@@ -0,0 +1,53 @@
1
+ self-flagellation
2
+ by Ben Scofield
3
+ http://github.com/bscofield/self-flagellation
4
+ original flog code
5
+ by Ryan Davis, Seattle.rb
6
+ http://ruby.sadi.st/
7
+ http://rubyforge.org/projects/seattlerb
8
+
9
+ == DESCRIPTION:
10
+
11
+ Self-flagellation is an addition/extension of Ryan Davis's flog, which
12
+ allows you to specify a customized set of complexity metrics (instead
13
+ of just using the defaults built into the gem)
14
+
15
+ == FEATURES/PROBLEMS:
16
+
17
+ All the features of flog are here, with the addition of a -f option
18
+ available on the commandline.
19
+
20
+ == SYNOPSIS:
21
+
22
+ > ./bin/flagellate -f=data/scores.yml bin/flagellate
23
+ Total Score: xxx
24
+
25
+ == INSTALL:
26
+
27
+ sudo gem install self-flagellation
28
+
29
+ == LICENSE:
30
+
31
+ (The MIT License)
32
+
33
+ Copyright (c) 2008 Ben Scofield
34
+ (original flog code Copyright (c) 2007 Ryan Davis)
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining
37
+ a copy of this software and associated documentation files (the
38
+ 'Software'), to deal in the Software without restriction, including
39
+ without limitation the rights to use, copy, modify, merge, publish,
40
+ distribute, sublicense, and/or sell copies of the Software, and to
41
+ permit persons to whom the Software is furnished to do so, subject to
42
+ the following conditions:
43
+
44
+ The above copyright notice and this permission notice shall be
45
+ included in all copies or substantial portions of the Software.
46
+
47
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
48
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
49
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
50
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
51
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
52
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
53
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,18 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+ load 'tasks/setup.rb'
5
+
6
+ ensure_in_path 'lib'
7
+ require 'self-flagellation'
8
+
9
+ PROJ.version = SelfFlagellation::VERSION
10
+ PROJ.name = 'self-flagellation'
11
+ PROJ.authors = 'Ben Scofield'
12
+ PROJ.email = 'bscofield@gmail.com'
13
+ PROJ.url = 'http://github.com/bscofield/self-flagellation'
14
+ PROJ.rubyforge.name = 'viget'
15
+
16
+ PROJ.spec.opts << '--color'
17
+
18
+ # EOF
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), '..', 'lib', 'self-flagellation'))
5
+
6
+ ARGV.push "-" if ARGV.empty?
7
+
8
+ if defined? $h then
9
+ puts "#{File.basename $0} options dirs_or_files"
10
+ puts " -a display all flog results, not top 60%"
11
+ puts " -f=path use complexity scores from YAML file in PATH"
12
+ puts " -h display help"
13
+ puts " -I=path extend $LOAD_PATH with path"
14
+ puts " -s display total score only"
15
+ puts " -v verbosely display progress and errors"
16
+ exit 0
17
+ end
18
+
19
+ if defined? $I and String === $I then
20
+ $I.split(/:/).each do |dir|
21
+ $: << dir
22
+ end
23
+ end
24
+
25
+ flogger = SelfFlagellation::Flagellate.new
26
+
27
+ if defined? $f and String === $f then
28
+ flogger.reset_scores $f
29
+ end
30
+
31
+ flogger.flog_files ARGV
32
+ flogger.report
@@ -0,0 +1,56 @@
1
+ # $Id$
2
+
3
+ # Equivalent to a header guard in C/C++
4
+ # Used to prevent the class/module from being loaded more than once
5
+ unless defined? SelfFlagellation
6
+
7
+ module SelfFlagellation
8
+
9
+ # :stopdoc:
10
+ VERSION = '1.0.0'
11
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
12
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
13
+ # :startdoc:
14
+
15
+ # Returns the version string for the library.
16
+ #
17
+ def self.version
18
+ VERSION
19
+ end
20
+
21
+ # Returns the library path for the module. If any arguments are given,
22
+ # they will be joined to the end of the libray path using
23
+ # <tt>File.join</tt>.
24
+ #
25
+ def self.libpath( *args )
26
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
27
+ end
28
+
29
+ # Returns the lpath for the module. If any arguments are given,
30
+ # they will be joined to the end of the path using
31
+ # <tt>File.join</tt>.
32
+ #
33
+ def self.path( *args )
34
+ args.empty? ? PATH : ::File.join(PATH, *args)
35
+ end
36
+
37
+ # Utility method used to rquire all files ending in .rb that lie in the
38
+ # directory below this file that has the same name as the filename passed
39
+ # in. Optionally, a specific _directory_ name can be passed in such that
40
+ # the _filename_ does not have to be equivalent to the directory.
41
+ #
42
+ def self.require_all_libs_relative_to( fname, dir = nil )
43
+ dir ||= ::File.basename(fname, '.*')
44
+ search_me = ::File.expand_path(
45
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
46
+
47
+ Dir.glob(search_me).sort.each {|rb| require rb}
48
+ end
49
+
50
+ end # module SelfFlagellation
51
+
52
+ SelfFlagellation.require_all_libs_relative_to __FILE__
53
+
54
+ end # unless defined?
55
+
56
+ # EOF
@@ -0,0 +1,491 @@
1
+ require 'yaml'
2
+ require 'rubygems'
3
+ require 'parse_tree'
4
+ require 'sexp_processor'
5
+ require 'unified_ruby'
6
+
7
+ $a ||= false
8
+ $s ||= false
9
+ $v ||= false
10
+
11
+ module SelfFlagellation
12
+ class Flagellate < SexpProcessor
13
+ include UnifiedRuby
14
+
15
+ THRESHOLD = $a ? 1.0 : 0.60
16
+ SCORES = Hash.new(1)
17
+ BRANCHING = [ :and, :case, :else, :if, :or, :rescue, :until, :when, :while ]
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
34
+ SCORES.merge!(:define_method => 5,
35
+ :eval => 5,
36
+ :module_eval => 5,
37
+ :class_eval => 5,
38
+ :instance_eval => 5)
39
+
40
+ # various "magic" usually used for "clever code"
41
+ SCORES.merge!(:alias_method => 2,
42
+ :extend => 2,
43
+ :include => 2,
44
+ :instance_method => 2,
45
+ :instance_methods => 2,
46
+ :method_added => 2,
47
+ :method_defined? => 2,
48
+ :method_removed => 2,
49
+ :method_undefined => 2,
50
+ :private_class_method => 2,
51
+ :private_instance_methods => 2,
52
+ :private_method_defined? => 2,
53
+ :protected_instance_methods => 2,
54
+ :protected_method_defined? => 2,
55
+ :public_class_method => 2,
56
+ :public_instance_methods => 2,
57
+ :public_method_defined? => 2,
58
+ :remove_method => 2,
59
+ :send => 3,
60
+ :undef_method => 2)
61
+
62
+ # calls I don't like and usually see being abused
63
+ SCORES.merge!(:inject => 2)
64
+
65
+ @@no_class = :main
66
+ @@no_method = :none
67
+
68
+ attr_reader :calls
69
+
70
+ def initialize
71
+ super
72
+ @pt = ParseTree.new(false)
73
+ @klasses = []
74
+ @methods = []
75
+ self.auto_shift_type = true
76
+ self.require_empty = false # HACK
77
+ self.reset
78
+ end
79
+
80
+ def reset_scores(path = nil)
81
+ if path
82
+ yaml = YAML.load_file(path)
83
+ @other_scores = yaml['OTHER_SCORES']
84
+ @scores = Hash.new(1)
85
+ @scores.merge!(yaml['SCORES'])
86
+ end
87
+ rescue
88
+ nil
89
+ end
90
+
91
+ def add_to_score(name, score)
92
+ @calls["#{self.klass_name}##{self.method_name}"][name] += score * @multiplier
93
+ end
94
+
95
+ def bad_dog! bonus
96
+ @multiplier += bonus
97
+ yield 42
98
+ @multiplier -= bonus
99
+ end
100
+
101
+ def bleed exp
102
+ process exp.shift until exp.empty?
103
+ end
104
+
105
+ def flog_files *files
106
+ files.flatten.each do |file|
107
+ if File.directory? file then
108
+ flog_files Dir["#{file}/**/*.rb"]
109
+ else
110
+ warn "** flogging #{file}" if $v
111
+ ruby = file == "-" ? $stdin.read : File.read(file)
112
+ begin
113
+ sexp = @pt.parse_tree_for_string(ruby, file)
114
+ process Sexp.from_array(sexp).first
115
+ rescue SyntaxError => e
116
+ if e.inspect =~ /<%|%>/ then
117
+ warn e.inspect + " at " + e.backtrace.first(5).join(', ')
118
+ warn "...stupid lemmings and their bad erb templates... skipping"
119
+ else
120
+ raise e
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def klass name
128
+ @klasses.unshift name
129
+ yield
130
+ @klasses.shift
131
+ end
132
+
133
+ def klass_name
134
+ @klasses.first || @@no_class
135
+ end
136
+
137
+ def method name
138
+ @methods.unshift name
139
+ yield
140
+ @methods.shift
141
+ end
142
+
143
+ def method_name
144
+ @methods.first || @@no_method
145
+ end
146
+
147
+ def report io = $stdout
148
+ current = 0
149
+ total_score = self.total
150
+ max = total_score * THRESHOLD
151
+ totals = self.totals
152
+
153
+ if $s then
154
+ io.puts total_score
155
+ exit 0
156
+ end
157
+
158
+ io.puts "Total score = #{total_score}"
159
+ io.puts
160
+
161
+ @calls.sort_by { |k,v| -totals[k] }.each do |klass_method, calls|
162
+ total = totals[klass_method]
163
+ io.puts "%s: (%.1f)" % [klass_method, total]
164
+ calls.sort_by { |k,v| -v }.each do |call, count|
165
+ io.puts " %6.1f: %s" % [count, call]
166
+ end
167
+
168
+ current += total
169
+ break if current >= max
170
+ end
171
+ ensure
172
+ self.reset
173
+ end
174
+
175
+ def reset
176
+ @totals = @total_score = nil
177
+ @multiplier = 1.0
178
+ @calls = Hash.new { |h,k| h[k] = Hash.new 0 }
179
+ end
180
+
181
+ def total
182
+ self.totals unless @total_score # calculates total_score as well
183
+
184
+ @total_score
185
+ end
186
+
187
+ def totals
188
+ unless @totals then
189
+ @total_score = 0
190
+ @totals = Hash.new(0)
191
+ self.calls.each do |meth, tally|
192
+ a, b, c = 0, 0, 0
193
+ tally.each do |cat, score|
194
+ case cat
195
+ when :assignment then a += score
196
+ when :branch then b += score
197
+ else c += score
198
+ end
199
+ end
200
+ score = Math.sqrt(a*a + b*b + c*c)
201
+ @totals[meth] = score
202
+ @total_score += score
203
+ end
204
+ end
205
+ @totals
206
+ end
207
+
208
+ def other_scores
209
+ @other_scores || OTHER_SCORES
210
+ end
211
+
212
+ def scores
213
+ @scores || SCORES
214
+ end
215
+
216
+ ############################################################
217
+ # Process Methods:
218
+
219
+ def process_alias(exp)
220
+ process exp.shift
221
+ process exp.shift
222
+ add_to_score :alias, other_scores[:alias]
223
+ s()
224
+ end
225
+
226
+ def process_and(exp)
227
+ add_to_score :branch, other_scores[:branch]
228
+ bad_dog! 0.1 do
229
+ process exp.shift # lhs
230
+ process exp.shift # rhs
231
+ end
232
+ s()
233
+ end
234
+
235
+ def process_attrasgn(exp)
236
+ add_to_score :assignment, other_scores[:assignment]
237
+ process exp.shift # lhs
238
+ exp.shift # name
239
+ process exp.shift # rhs
240
+ s()
241
+ end
242
+
243
+ def process_attrset(exp)
244
+ add_to_score :assignment, other_scores[:assignment]
245
+ raise exp.inspect
246
+ s()
247
+ end
248
+
249
+ def process_block(exp)
250
+ bad_dog! 0.1 do
251
+ bleed exp
252
+ end
253
+ s()
254
+ end
255
+
256
+ # [:block_pass, [:lit, :blah], [:fcall, :foo]]
257
+ def process_block_pass(exp)
258
+ arg = exp.shift
259
+ call = exp.shift
260
+
261
+ add_to_score :block_pass, other_scores[:block]
262
+
263
+ case arg.first
264
+ when :lvar, :dvar, :ivar, :cvar, :self, :const, :nil then
265
+ # do nothing
266
+ when :lit, :call then
267
+ add_to_score :to_proc_normal, other_scores[:to_proc_normal]
268
+ when :iter, *BRANCHING then
269
+ add_to_score :to_proc_icky!, other_scores[:to_proc_icky!]
270
+ else
271
+ raise({:block_pass => [arg, call]}.inspect)
272
+ end
273
+
274
+ process arg
275
+ process call
276
+
277
+ s()
278
+ end
279
+
280
+ def process_call(exp)
281
+ bad_dog! 0.2 do
282
+ recv = process exp.shift
283
+ end
284
+ name = exp.shift
285
+ bad_dog! 0.2 do
286
+ args = process exp.shift
287
+ end
288
+
289
+ score = scores[name]
290
+ add_to_score name, score
291
+
292
+ s()
293
+ end
294
+
295
+ def process_case(exp)
296
+ add_to_score :branch, other_scores[:branch]
297
+ process exp.shift # recv
298
+ bad_dog! 0.1 do
299
+ bleed exp
300
+ end
301
+ s()
302
+ end
303
+
304
+ def process_class(exp)
305
+ self.klass exp.shift do
306
+ bad_dog! 1.0 do
307
+ supr = process exp.shift
308
+ end
309
+ bleed exp
310
+ end
311
+ s()
312
+ end
313
+
314
+ def process_dasgn_curr(exp)
315
+ add_to_score :assignment, other_scores[:assignment]
316
+ exp.shift # name
317
+ process exp.shift # assigment, if any
318
+ s()
319
+ end
320
+
321
+ def process_defn(exp)
322
+ self.method exp.shift do
323
+ bleed exp
324
+ end
325
+ s()
326
+ end
327
+
328
+ def process_defs(exp)
329
+ process exp.shift
330
+ self.method exp.shift do
331
+ bleed exp
332
+ end
333
+ s()
334
+ end
335
+
336
+ def process_else(exp)
337
+ add_to_score :branch, other_scores[:branch]
338
+ bad_dog! 0.1 do
339
+ bleed exp
340
+ end
341
+ s()
342
+ end
343
+
344
+ def process_iasgn(exp)
345
+ add_to_score :assignment, other_scores[:assignment]
346
+ exp.shift # name
347
+ process exp.shift # rhs
348
+ s()
349
+ end
350
+
351
+ def process_if(exp)
352
+ add_to_score :branch, other_scores[:branch]
353
+ process exp.shift # cond
354
+ bad_dog! 0.1 do
355
+ process exp.shift # true
356
+ process exp.shift # false
357
+ end
358
+ s()
359
+ end
360
+
361
+ def process_iter(exp)
362
+ context = (self.context - [:class, :module, :scope])
363
+ if context.uniq.sort_by {|s|s.to_s} == [:block, :iter] then
364
+ recv = exp.first
365
+ if recv[0] == :call and recv[1] == nil and recv.arglist[1] and [:lit, :str].include? recv.arglist[1][0] then
366
+ msg = recv[2]
367
+ submsg = recv.arglist[1][1]
368
+ self.method submsg do
369
+ self.klass msg do
370
+ bleed exp
371
+ end
372
+ end
373
+ return s()
374
+ end
375
+ end
376
+
377
+ add_to_score :branch, other_scores[:branch]
378
+
379
+ process exp.shift # no penalty for LHS
380
+
381
+ bad_dog! 0.1 do
382
+ bleed exp
383
+ end
384
+
385
+ s()
386
+ end
387
+
388
+ def process_lasgn(exp)
389
+ add_to_score :assignment, other_scores[:assignment]
390
+ exp.shift # name
391
+ process exp.shift # rhs
392
+ s()
393
+ end
394
+
395
+ def process_lit(exp)
396
+ value = exp.shift
397
+ case value
398
+ when 0, -1 then
399
+ # ignore those because they're used as array indicies instead of first/last
400
+ when Integer then
401
+ add_to_score :lit_fixnum, other_scores[:lit_fixnum]
402
+ when Float, Symbol, Regexp, Range then
403
+ # do nothing
404
+ else
405
+ raise value.inspect
406
+ end
407
+ s()
408
+ end
409
+
410
+ def process_masgn(exp)
411
+ add_to_score :assignment, other_scores[:assignment]
412
+ process exp.shift # lhs
413
+ process exp.shift # rhs
414
+ s()
415
+ end
416
+
417
+ def process_module(exp)
418
+ self.klass exp.shift do
419
+ bleed exp
420
+ end
421
+ s()
422
+ end
423
+
424
+ def process_or(exp)
425
+ add_to_score :branch, other_scores[:branch]
426
+ bad_dog! 0.1 do
427
+ process exp.shift # lhs
428
+ process exp.shift # rhs
429
+ end
430
+ s()
431
+ end
432
+
433
+ def process_rescue(exp)
434
+ add_to_score :branch, other_scores[:branch]
435
+ bad_dog! 0.1 do
436
+ bleed exp
437
+ end
438
+ s()
439
+ end
440
+
441
+ def process_sclass(exp)
442
+ bad_dog! 0.5 do
443
+ recv = process exp.shift
444
+ bleed exp
445
+ end
446
+
447
+ add_to_score :sclass, other_scores[:sclass]
448
+ s()
449
+ end
450
+
451
+ def process_super(exp)
452
+ add_to_score :super, other_scores[:super]
453
+ bleed exp
454
+ s()
455
+ end
456
+
457
+ def process_until(exp)
458
+ add_to_score :branch, other_scores[:branch]
459
+ bad_dog! 0.1 do
460
+ process exp.shift # cond
461
+ process exp.shift # body
462
+ end
463
+ exp.shift # pre/post
464
+ s()
465
+ end
466
+
467
+ def process_when(exp)
468
+ add_to_score :branch, other_scores[:branch]
469
+ bad_dog! 0.1 do
470
+ bleed exp
471
+ end
472
+ s()
473
+ end
474
+
475
+ def process_while(exp)
476
+ add_to_score :branch, other_scores[:branch]
477
+ bad_dog! 0.1 do
478
+ process exp.shift # cond
479
+ process exp.shift # body
480
+ end
481
+ exp.shift # pre/post
482
+ s()
483
+ end
484
+
485
+ def process_yield(exp)
486
+ add_to_score :yield, other_scores[:yield]
487
+ bleed exp
488
+ s()
489
+ end
490
+ end
491
+ end