self-flagellation 1.0.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.
@@ -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