flog 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +31 -5
- data/Manifest.txt +1 -0
- data/README.txt +5 -5
- data/Rakefile +13 -7
- data/bin/flog +5 -0
- data/gem_updater.rb +161 -0
- data/lib/flog.rb +126 -107
- data/unpack.rb +0 -0
- data/update_scores.rb +89 -82
- metadata +73 -52
data/History.txt
CHANGED
@@ -1,10 +1,32 @@
|
|
1
|
-
|
1
|
+
=== 1.2.0 / 2008-10-22
|
2
|
+
|
3
|
+
* 14 minor enhancements:
|
4
|
+
|
5
|
+
* Added -c flag to continue dispite errors.
|
6
|
+
* Added -m to only report code in methods (skips #none).
|
7
|
+
* Added -n flag to give NO method details (summary only)
|
8
|
+
* Added -n to skip method details... pussies should learn grep.
|
9
|
+
* Added -q to quiet method details (total per method only)
|
10
|
+
* Added avg & stddev to total.
|
11
|
+
* Added avg score per method to report.
|
12
|
+
* Added lots of doco from contributors (hugh sasse?).
|
13
|
+
* Fixed class names when const2/3.
|
14
|
+
* Fixed unified ruby changes
|
15
|
+
* Refactored flog with help from flay.
|
16
|
+
* Refactored get_source_index
|
17
|
+
* Refactored into gem_updater.rb and cleaned up.
|
18
|
+
* Works with new incremental rubygems, albiet slower than before.
|
19
|
+
|
20
|
+
=== 1.1.0 / 2007-08-21
|
2
21
|
|
3
22
|
* 3 major enhancements:
|
23
|
+
|
4
24
|
* Added assignments and branches and a lot of other stuff. rad.
|
5
25
|
* Added process_iter section for DSL style blocks (rake tasks etc).
|
6
26
|
* Made Flog usable as a library.
|
27
|
+
|
7
28
|
* 12 minor enhancements:
|
29
|
+
|
8
30
|
* Added -a flag to turn off threshold culling for other tools.
|
9
31
|
* Added -s for summarizing the score.
|
10
32
|
* Added -v feedback to know what file you're flogging.
|
@@ -17,23 +39,27 @@
|
|
17
39
|
* Added unpack.rb and update_scores.rb at base level (not installed)
|
18
40
|
* Added scoring for block_pass.
|
19
41
|
* Converted totals to use distance formula on ABC's.
|
42
|
+
|
20
43
|
* 3 bug fixes:
|
44
|
+
|
21
45
|
* Ran flog on every latest gem available. Found a bunch of problems.
|
22
46
|
* Use a stack for both class/module and method accounting.
|
23
47
|
* block_args weren't processing the arg
|
24
48
|
|
25
|
-
|
49
|
+
=== 1.0.2 / 2007-08-01
|
26
50
|
|
27
51
|
* 1 bug fix:
|
52
|
+
|
28
53
|
* stupid rubygems bin wrapper... *sigh*
|
29
54
|
|
30
|
-
|
55
|
+
=== 1.0.1 / 2007-08-01
|
31
56
|
|
32
57
|
* 1 bug fix:
|
58
|
+
|
33
59
|
* New Rule: NEVER release new software when exhausted: Fixed dependency list.
|
34
60
|
|
35
|
-
|
61
|
+
=== 1.0.0 / 2007-08-01
|
36
62
|
|
37
63
|
* 1 major enhancement:
|
38
|
-
* Birthday!
|
39
64
|
|
65
|
+
* Birthday!
|
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
flog
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
= flog
|
2
|
+
|
3
|
+
* http://ruby.sadi.st/
|
4
|
+
* http://rubyforge.org/projects/seattlerb
|
5
5
|
|
6
6
|
== DESCRIPTION:
|
7
7
|
|
@@ -25,7 +25,7 @@ report. The higher the score, the more pain the code is in.
|
|
25
25
|
== REQUIREMENTS:
|
26
26
|
|
27
27
|
* ruby2ruby
|
28
|
-
*
|
28
|
+
* ParseTree
|
29
29
|
|
30
30
|
== INSTALL:
|
31
31
|
|
data/Rakefile
CHANGED
@@ -2,16 +2,22 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'hoe'
|
5
|
+
|
6
|
+
Hoe.add_include_dirs("../../ParseTree/dev/lib",
|
7
|
+
"../../RubyInline/dev/lib",
|
8
|
+
"../../sexp_processor/dev/lib",
|
9
|
+
"../../ZenTest/dev/lib",
|
10
|
+
"lib")
|
11
|
+
|
5
12
|
require './lib/flog'
|
6
13
|
|
7
|
-
Hoe.new('flog', Flog::VERSION) do |
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2..-1].map {|u| u.strip }
|
12
|
-
p.changes = p.paragraphs_of('History.txt', 1).join("\n\n")
|
14
|
+
Hoe.new('flog', Flog::VERSION) do |flog|
|
15
|
+
flog.rubyforge_name = 'seattlerb'
|
16
|
+
|
17
|
+
flog.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
|
13
18
|
|
14
|
-
|
19
|
+
flog.extra_deps << ['sexp_processor', '~> 3.0']
|
20
|
+
flog.extra_deps << ["ParseTree", '~> 3.0']
|
15
21
|
end
|
16
22
|
|
17
23
|
# vim: syntax=Ruby
|
data/bin/flog
CHANGED
@@ -2,15 +2,20 @@
|
|
2
2
|
|
3
3
|
require 'flog'
|
4
4
|
|
5
|
+
# In case this is being called on the end of a pipe:
|
5
6
|
ARGV.push "-" if ARGV.empty?
|
6
7
|
|
7
8
|
if defined? $h then
|
8
9
|
puts "#{File.basename $0} options dirs_or_files"
|
9
10
|
puts " -a display all flog results, not top 60%"
|
11
|
+
puts " -c continue despite syntax errors"
|
10
12
|
puts " -h display help"
|
11
13
|
puts " -I=path extend $LOAD_PATH with path"
|
14
|
+
puts " -m skip code outside of methods"
|
15
|
+
puts " -n no method details in report"
|
12
16
|
puts " -s display total score only"
|
13
17
|
puts " -v verbosely display progress and errors"
|
18
|
+
puts " -q quiet, don't show method breakdowns"
|
14
19
|
exit 0
|
15
20
|
end
|
16
21
|
|
data/gem_updater.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'rubygems/remote_fetcher'
|
2
|
+
|
3
|
+
$u ||= false
|
4
|
+
|
5
|
+
module GemUpdater
|
6
|
+
GEMURL = URI.parse 'http://gems.rubyforge.org'
|
7
|
+
|
8
|
+
@@index = nil
|
9
|
+
|
10
|
+
def self.stupid_gems
|
11
|
+
["ruby-aes-table1-1.0.gem", # stupid dups usually because of "dash" renames
|
12
|
+
"ruby-aes-unroll1-1.0.gem",
|
13
|
+
"hpricot-scrub-0.2.0.gem",
|
14
|
+
"extract_curves-0.0.1.gem",
|
15
|
+
"extract_curves-0.0.1-i586-linux.gem",
|
16
|
+
"extract_curves-0.0.1-mswin32.gem",
|
17
|
+
"rfeedparser-ictv-0.9.931.gem",
|
18
|
+
"spec_unit-0.0.1.gem"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.initialize_dir
|
22
|
+
Dir.mkdir "../gems" unless File.directory? "../gems"
|
23
|
+
self.in_gem_dir do
|
24
|
+
File.symlink ".", "cache" unless File.exist? "cache"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.get_source_index
|
29
|
+
return @@index if @@index
|
30
|
+
|
31
|
+
dump = if $u or not File.exist? '.source_index' then
|
32
|
+
url = GEMURL + "Marshal.#{Gem.marshal_version}.Z"
|
33
|
+
dump = Gem::RemoteFetcher.fetcher.fetch_path url
|
34
|
+
require 'zlib'
|
35
|
+
dump = Gem.inflate dump
|
36
|
+
open '.source_index', 'wb' do |io| io.write dump end
|
37
|
+
dump
|
38
|
+
else
|
39
|
+
open '.source_index', 'rb' do |io| io.read end
|
40
|
+
end
|
41
|
+
|
42
|
+
@@index = Marshal.load dump
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.get_latest_gems
|
46
|
+
@@cache ||= get_source_index.latest_specs
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.get_gems_by_name
|
50
|
+
@@by_name ||= Hash[*get_latest_gems.map { |gem|
|
51
|
+
[gem.name, gem, gem.full_name, gem]
|
52
|
+
}.flatten]
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.dependencies_of name
|
56
|
+
index = self.get_source_index
|
57
|
+
get_gems_by_name[name].dependencies.map { |dep| index.search(dep).last }
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.dependent_upon name
|
61
|
+
get_latest_gems.find_all { |gem|
|
62
|
+
gem.dependencies.any? { |dep| dep.name == name }
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.update_gem_tarballs
|
67
|
+
GemUpdater.initialize_dir
|
68
|
+
|
69
|
+
latest = GemUpdater.get_latest_gems
|
70
|
+
|
71
|
+
puts "updating mirror"
|
72
|
+
|
73
|
+
self.in_gem_dir do
|
74
|
+
gems = Dir["*.gem"]
|
75
|
+
tgzs = Dir["*.tgz"]
|
76
|
+
|
77
|
+
old = tgzs - latest.map { |spec| "#{spec.full_name}.tgz" }
|
78
|
+
unless old.empty? then
|
79
|
+
puts "deleting #{old.size} tgzs"
|
80
|
+
old.each do |tgz|
|
81
|
+
File.unlink tgz
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
new = latest.map { |spec|
|
86
|
+
"#{spec.full_name}.tgz"
|
87
|
+
} - tgzs
|
88
|
+
|
89
|
+
puts "fetching #{new.size} tgzs"
|
90
|
+
|
91
|
+
latest.sort.each do |spec|
|
92
|
+
full_name = spec.full_name
|
93
|
+
tgz_name = "#{full_name}.tgz"
|
94
|
+
gem_name = "#{full_name}.gem"
|
95
|
+
|
96
|
+
next if tgzs.include? tgz_name
|
97
|
+
|
98
|
+
unless gems.include? gem_name then
|
99
|
+
begin
|
100
|
+
warn "downloading #{full_name}"
|
101
|
+
Gem::RemoteFetcher.fetcher.download(spec, GEMURL, Dir.pwd)
|
102
|
+
rescue Gem::RemoteFetcher::FetchError
|
103
|
+
warn " failed"
|
104
|
+
next
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
warn "converting #{gem_name} to tarball"
|
109
|
+
|
110
|
+
unless File.directory? full_name then
|
111
|
+
system "gem unpack cache/#{gem_name}"
|
112
|
+
system "gem spec -l cache/#{gem_name} > #{full_name}/gemspec.rb"
|
113
|
+
end
|
114
|
+
|
115
|
+
system "tar zmcf #{tgz_name} #{full_name}"
|
116
|
+
system "rm -rf #{full_name} #{gem_name}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.each_gem filter = /^[\w-]+-\d+(\.\d+)*\.tgz$/
|
122
|
+
self.in_gem_dir do
|
123
|
+
Dir["*.tgz"].each do |tgz|
|
124
|
+
next unless tgz =~ filter
|
125
|
+
|
126
|
+
yield File.basename(tgz, ".tgz")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.with_gem name
|
132
|
+
self.in_gem_dir do
|
133
|
+
begin
|
134
|
+
system "tar zxmf #{name}.tgz 2> /dev/null"
|
135
|
+
Dir.chdir name do
|
136
|
+
yield name
|
137
|
+
end
|
138
|
+
ensure
|
139
|
+
system "rm -r #{name}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.load_yaml path, default = {}
|
145
|
+
YAML.load(File.read(path)) rescue default
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.save_yaml path, data
|
149
|
+
File.open("#{path}.new", 'w') do |f|
|
150
|
+
warn "*** saving #{path}"
|
151
|
+
YAML.dump data, f
|
152
|
+
end
|
153
|
+
File.rename "#{path}.new", path
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.in_gem_dir
|
157
|
+
Dir.chdir "../gems" do
|
158
|
+
yield
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/flog.rb
CHANGED
@@ -3,12 +3,16 @@ require 'parse_tree'
|
|
3
3
|
require 'sexp_processor'
|
4
4
|
require 'unified_ruby'
|
5
5
|
|
6
|
-
$a ||= false
|
7
|
-
$
|
8
|
-
$
|
6
|
+
$a ||= false # report all methods, not just 60%
|
7
|
+
$c ||= false # continue despite syntax errors
|
8
|
+
$m ||= false # real methods only (no global scope)
|
9
|
+
$n ||= false # no method details
|
10
|
+
$s ||= false # summary only
|
11
|
+
$v ||= false # verbose, print methods as processed
|
12
|
+
$q ||= false # quiet, don't show method details
|
9
13
|
|
10
14
|
class Flog < SexpProcessor
|
11
|
-
VERSION = '1.
|
15
|
+
VERSION = '1.2.0'
|
12
16
|
|
13
17
|
include UnifiedRuby
|
14
18
|
|
@@ -16,50 +20,58 @@ class Flog < SexpProcessor
|
|
16
20
|
SCORES = Hash.new(1)
|
17
21
|
BRANCHING = [ :and, :case, :else, :if, :or, :rescue, :until, :when, :while ]
|
18
22
|
|
23
|
+
##
|
19
24
|
# various non-call constructs
|
25
|
+
|
20
26
|
OTHER_SCORES = {
|
21
|
-
:alias
|
22
|
-
:assignment
|
23
|
-
:block
|
24
|
-
:branch
|
25
|
-
:lit_fixnum
|
26
|
-
:sclass
|
27
|
-
:super
|
28
|
-
:to_proc_icky!
|
27
|
+
:alias => 2,
|
28
|
+
:assignment => 1,
|
29
|
+
:block => 1,
|
30
|
+
:branch => 1,
|
31
|
+
:lit_fixnum => 0.25,
|
32
|
+
:sclass => 5,
|
33
|
+
:super => 1,
|
34
|
+
:to_proc_icky! => 10,
|
29
35
|
:to_proc_normal => 5,
|
30
|
-
:yield
|
36
|
+
:yield => 1,
|
31
37
|
}
|
32
38
|
|
39
|
+
##
|
33
40
|
# eval forms
|
41
|
+
|
34
42
|
SCORES.merge!(:define_method => 5,
|
35
|
-
:eval
|
36
|
-
:module_eval
|
37
|
-
:class_eval
|
43
|
+
:eval => 5,
|
44
|
+
:module_eval => 5,
|
45
|
+
:class_eval => 5,
|
38
46
|
:instance_eval => 5)
|
39
47
|
|
48
|
+
##
|
40
49
|
# 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
50
|
|
51
|
+
SCORES.merge!(:alias_method => 2,
|
52
|
+
:extend => 2,
|
53
|
+
:include => 2,
|
54
|
+
:instance_method => 2,
|
55
|
+
:instance_methods => 2,
|
56
|
+
:method_added => 2,
|
57
|
+
:method_defined? => 2,
|
58
|
+
:method_removed => 2,
|
59
|
+
:method_undefined => 2,
|
60
|
+
:private_class_method => 2,
|
61
|
+
:private_instance_methods => 2,
|
62
|
+
:private_method_defined? => 2,
|
63
|
+
:protected_instance_methods => 2,
|
64
|
+
:protected_method_defined? => 2,
|
65
|
+
:public_class_method => 2,
|
66
|
+
:public_instance_methods => 2,
|
67
|
+
:public_method_defined? => 2,
|
68
|
+
:remove_method => 2,
|
69
|
+
:send => 3,
|
70
|
+
:undef_method => 2)
|
71
|
+
|
72
|
+
##
|
62
73
|
# calls I don't like and usually see being abused
|
74
|
+
|
63
75
|
SCORES.merge!(:inject => 2)
|
64
76
|
|
65
77
|
@@no_class = :main
|
@@ -81,16 +93,31 @@ class Flog < SexpProcessor
|
|
81
93
|
@calls["#{self.klass_name}##{self.method_name}"][name] += score * @multiplier
|
82
94
|
end
|
83
95
|
|
96
|
+
##
|
97
|
+
# For the duration of the block the complexity factor is increased
|
98
|
+
# by #bonus This allows the complexity of sub-expressions to be
|
99
|
+
# influenced by the expressions in which they are found. Yields 42
|
100
|
+
# to the supplied block.
|
101
|
+
|
84
102
|
def bad_dog! bonus
|
85
103
|
@multiplier += bonus
|
86
104
|
yield 42
|
87
105
|
@multiplier -= bonus
|
88
106
|
end
|
89
107
|
|
108
|
+
##
|
109
|
+
# process each element of #exp in turn.
|
110
|
+
|
90
111
|
def bleed exp
|
91
112
|
process exp.shift until exp.empty?
|
92
113
|
end
|
93
114
|
|
115
|
+
##
|
116
|
+
# Process #files with flog, recursively descending directories.
|
117
|
+
#--
|
118
|
+
# There is no way to exclude directories at present (RCS, SCCS, .svn)
|
119
|
+
#++
|
120
|
+
|
94
121
|
def flog_files *files
|
95
122
|
files.flatten.each do |file|
|
96
123
|
if File.directory? file then
|
@@ -103,10 +130,12 @@ class Flog < SexpProcessor
|
|
103
130
|
process Sexp.from_array(sexp).first
|
104
131
|
rescue SyntaxError => e
|
105
132
|
if e.inspect =~ /<%|%>/ then
|
106
|
-
warn e.inspect
|
133
|
+
warn "#{e.inspect} at #{e.backtrace.first(5).join(', ')}"
|
107
134
|
warn "...stupid lemmings and their bad erb templates... skipping"
|
108
135
|
else
|
109
|
-
raise e
|
136
|
+
raise e unless $c
|
137
|
+
warn file
|
138
|
+
warn "#{e.inspect} at #{e.backtrace.first(5).join(', ')}"
|
110
139
|
end
|
111
140
|
end
|
112
141
|
end
|
@@ -119,40 +148,65 @@ class Flog < SexpProcessor
|
|
119
148
|
@klasses.shift
|
120
149
|
end
|
121
150
|
|
151
|
+
##
|
152
|
+
# returns the first class in the list, or @@no_class if there are
|
153
|
+
# none.
|
154
|
+
|
122
155
|
def klass_name
|
123
|
-
@klasses.first || @@no_class
|
156
|
+
name = @klasses.first || @@no_class
|
157
|
+
if Sexp === name then
|
158
|
+
case name.first
|
159
|
+
when :colon2 then
|
160
|
+
name = name.flatten
|
161
|
+
name.delete :const
|
162
|
+
name.delete :colon2
|
163
|
+
name = name.join("::")
|
164
|
+
when :colon3 then
|
165
|
+
name = name.last
|
166
|
+
end
|
167
|
+
end
|
168
|
+
name
|
124
169
|
end
|
125
170
|
|
171
|
+
##
|
172
|
+
# Adds name to the list of methods, for the duration of the block
|
173
|
+
|
126
174
|
def method name
|
127
175
|
@methods.unshift name
|
128
176
|
yield
|
129
177
|
@methods.shift
|
130
178
|
end
|
131
179
|
|
180
|
+
##
|
181
|
+
# returns the first method in the list, or @@no_method if there are
|
182
|
+
# none.
|
183
|
+
|
132
184
|
def method_name
|
133
185
|
@methods.first || @@no_method
|
134
186
|
end
|
135
187
|
|
188
|
+
##
|
189
|
+
# Report results to #io, STDOUT by default.
|
190
|
+
|
136
191
|
def report io = $stdout
|
137
192
|
current = 0
|
193
|
+
totals = self.totals
|
138
194
|
total_score = self.total
|
139
195
|
max = total_score * THRESHOLD
|
140
|
-
totals = self.totals
|
141
|
-
|
142
|
-
if $s then
|
143
|
-
io.puts total_score
|
144
|
-
exit 0
|
145
|
-
end
|
146
196
|
|
147
|
-
io.puts "Total
|
197
|
+
io.puts "Total Flog = %.1f (%.1f +/- %.1f flog / method)" % [total_score, self.average, self.stddev]
|
148
198
|
io.puts
|
149
199
|
|
200
|
+
exit 0 if $s
|
201
|
+
|
150
202
|
@calls.sort_by { |k,v| -totals[k] }.each do |klass_method, calls|
|
203
|
+
next if $m and klass_method =~ /##{@@no_method}/
|
151
204
|
total = totals[klass_method]
|
152
205
|
io.puts "%s: (%.1f)" % [klass_method, total]
|
206
|
+
next if $q
|
153
207
|
calls.sort_by { |k,v| -v }.each do |call, count|
|
154
208
|
io.puts " %6.1f: %s" % [count, call]
|
155
|
-
end
|
209
|
+
end unless $n
|
156
210
|
|
157
211
|
current += total
|
158
212
|
break if current >= max
|
@@ -162,22 +216,23 @@ class Flog < SexpProcessor
|
|
162
216
|
end
|
163
217
|
|
164
218
|
def reset
|
165
|
-
|
219
|
+
# TODO: rename @totals
|
220
|
+
@totals = @total = nil
|
166
221
|
@multiplier = 1.0
|
167
222
|
@calls = Hash.new { |h,k| h[k] = Hash.new 0 }
|
168
223
|
end
|
169
224
|
|
170
|
-
|
171
|
-
self.totals unless @total_score # calculates total_score as well
|
225
|
+
attr_reader :total, :average, :stddev
|
172
226
|
|
173
|
-
|
174
|
-
|
227
|
+
##
|
228
|
+
# Return the total score and populates @totals.
|
175
229
|
|
176
230
|
def totals
|
177
231
|
unless @totals then
|
178
|
-
@
|
232
|
+
@total = 0
|
179
233
|
@totals = Hash.new(0)
|
180
234
|
self.calls.each do |meth, tally|
|
235
|
+
next if $m and meth =~ /##{@@no_method}$/
|
181
236
|
a, b, c = 0, 0, 0
|
182
237
|
tally.each do |cat, score|
|
183
238
|
case cat
|
@@ -188,9 +243,17 @@ class Flog < SexpProcessor
|
|
188
243
|
end
|
189
244
|
score = Math.sqrt(a*a + b*b + c*c)
|
190
245
|
@totals[meth] = score
|
191
|
-
@
|
246
|
+
@total += score
|
192
247
|
end
|
193
248
|
end
|
249
|
+
|
250
|
+
size = self.calls.size.to_f
|
251
|
+
@average = @total / size
|
252
|
+
|
253
|
+
sum = 0
|
254
|
+
@totals.values.each { |i| sum += (i - @average) ** 2 }
|
255
|
+
@stddev = (1 / size * sum)
|
256
|
+
|
194
257
|
@totals
|
195
258
|
end
|
196
259
|
|
@@ -212,6 +275,7 @@ class Flog < SexpProcessor
|
|
212
275
|
end
|
213
276
|
s()
|
214
277
|
end
|
278
|
+
alias :process_or :process_and
|
215
279
|
|
216
280
|
def process_attrasgn(exp)
|
217
281
|
add_to_score :assignment, OTHER_SCORES[:assignment]
|
@@ -234,7 +298,6 @@ class Flog < SexpProcessor
|
|
234
298
|
s()
|
235
299
|
end
|
236
300
|
|
237
|
-
# [:block_pass, [:lit, :blah], [:fcall, :foo]]
|
238
301
|
def process_block_pass(exp)
|
239
302
|
arg = exp.shift
|
240
303
|
call = exp.shift
|
@@ -298,6 +361,8 @@ class Flog < SexpProcessor
|
|
298
361
|
process exp.shift # assigment, if any
|
299
362
|
s()
|
300
363
|
end
|
364
|
+
alias :process_iasgn :process_dasgn_curr
|
365
|
+
alias :process_lasgn :process_dasgn_curr
|
301
366
|
|
302
367
|
def process_defn(exp)
|
303
368
|
self.method exp.shift do
|
@@ -321,13 +386,8 @@ class Flog < SexpProcessor
|
|
321
386
|
end
|
322
387
|
s()
|
323
388
|
end
|
324
|
-
|
325
|
-
|
326
|
-
add_to_score :assignment, OTHER_SCORES[:assignment]
|
327
|
-
exp.shift # name
|
328
|
-
process exp.shift # rhs
|
329
|
-
s()
|
330
|
-
end
|
389
|
+
alias :process_rescue :process_else
|
390
|
+
alias :process_when :process_else
|
331
391
|
|
332
392
|
def process_if(exp)
|
333
393
|
add_to_score :branch, OTHER_SCORES[:branch]
|
@@ -343,7 +403,8 @@ class Flog < SexpProcessor
|
|
343
403
|
context = (self.context - [:class, :module, :scope])
|
344
404
|
if context.uniq.sort_by {|s|s.to_s} == [:block, :iter] then
|
345
405
|
recv = exp.first
|
346
|
-
if recv[0] == :call and recv[1] == nil and recv.arglist[1] and
|
406
|
+
if (recv[0] == :call and recv[1] == nil and recv.arglist[1] and
|
407
|
+
[:lit, :str].include? recv.arglist[1][0]) then
|
347
408
|
msg = recv[2]
|
348
409
|
submsg = recv.arglist[1][1]
|
349
410
|
self.method submsg do
|
@@ -366,13 +427,6 @@ class Flog < SexpProcessor
|
|
366
427
|
s()
|
367
428
|
end
|
368
429
|
|
369
|
-
def process_lasgn(exp)
|
370
|
-
add_to_score :assignment, OTHER_SCORES[:assignment]
|
371
|
-
exp.shift # name
|
372
|
-
process exp.shift # rhs
|
373
|
-
s()
|
374
|
-
end
|
375
|
-
|
376
430
|
def process_lit(exp)
|
377
431
|
value = exp.shift
|
378
432
|
case value
|
@@ -390,8 +444,7 @@ class Flog < SexpProcessor
|
|
390
444
|
|
391
445
|
def process_masgn(exp)
|
392
446
|
add_to_score :assignment, OTHER_SCORES[:assignment]
|
393
|
-
|
394
|
-
process exp.shift # rhs
|
447
|
+
bleed exp
|
395
448
|
s()
|
396
449
|
end
|
397
450
|
|
@@ -402,23 +455,6 @@ class Flog < SexpProcessor
|
|
402
455
|
s()
|
403
456
|
end
|
404
457
|
|
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
|
418
|
-
end
|
419
|
-
s()
|
420
|
-
end
|
421
|
-
|
422
458
|
def process_sclass(exp)
|
423
459
|
bad_dog! 0.5 do
|
424
460
|
recv = process exp.shift
|
@@ -435,24 +471,6 @@ class Flog < SexpProcessor
|
|
435
471
|
s()
|
436
472
|
end
|
437
473
|
|
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
474
|
def process_while(exp)
|
457
475
|
add_to_score :branch, OTHER_SCORES[:branch]
|
458
476
|
bad_dog! 0.1 do
|
@@ -462,6 +480,7 @@ class Flog < SexpProcessor
|
|
462
480
|
exp.shift # pre/post
|
463
481
|
s()
|
464
482
|
end
|
483
|
+
alias :process_until :process_while
|
465
484
|
|
466
485
|
def process_yield(exp)
|
467
486
|
add_to_score :yield, OTHER_SCORES[:yield]
|
data/unpack.rb
CHANGED
File without changes
|
data/update_scores.rb
CHANGED
@@ -1,70 +1,63 @@
|
|
1
|
-
#!/usr/
|
1
|
+
#!/usr/bin/env ruby -ws
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
# Update the flog scores for a specific set of gems.
|
4
|
+
|
5
|
+
$: << 'lib' << '../../ParseTree/dev/lib'
|
6
|
+
$:.unshift File.expand_path("~/Work/svn/rubygems/lib")
|
7
|
+
|
8
|
+
require 'yaml'
|
5
9
|
require 'flog'
|
6
|
-
require '
|
10
|
+
require 'gem_updater'
|
7
11
|
|
8
12
|
$u ||= false
|
9
13
|
$f ||= false
|
10
14
|
|
11
|
-
$score_file
|
12
|
-
$misc_error
|
13
|
-
$syntax_error =
|
14
|
-
$
|
15
|
-
$no_gem = [-4]
|
15
|
+
$score_file = '../dev/scores.yml'
|
16
|
+
$misc_error = {:total => -1, :average => -1, :methods => {}}
|
17
|
+
$syntax_error = {:total => -2, :average => -2, :methods => {}}
|
18
|
+
$no_gem = {:total => -4, :average => -4, :methods => {}}
|
16
19
|
|
17
|
-
max
|
20
|
+
max = (ARGV.shift || 10).to_i
|
18
21
|
|
19
22
|
scores = YAML.load(File.read($score_file)) rescue {}
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
##
|
25
|
+
# Save the scores in $score_file.
|
26
|
+
#--
|
27
|
+
# Creates a new file, then renames to overwrite the old one.
|
28
|
+
# Wouldn't it be better to copy the old one, then create a new
|
29
|
+
# one so you can do a diff?
|
30
|
+
#++
|
31
|
+
|
32
|
+
def save_scores scores
|
33
|
+
File.open("#{$score_file}.new", 'w') do |f|
|
34
|
+
warn "*** saving scores"
|
35
|
+
YAML.dump scores, f
|
36
|
+
end
|
37
|
+
File.rename "#{$score_file}.new", $score_file
|
38
|
+
end
|
39
|
+
|
40
|
+
GemUpdater::stupid_gems.each do|p|
|
27
41
|
scores[p] = $no_gem.dup
|
28
42
|
end
|
29
43
|
|
30
|
-
|
44
|
+
GemUpdater::initialize_dir
|
31
45
|
|
32
46
|
if $u then
|
33
|
-
|
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
|
47
|
+
GemUpdater.update_gem_tarballs
|
48
|
+
exit 1
|
61
49
|
end
|
62
50
|
|
63
|
-
my_projects = Regexp.union("InlineFortran", "ParseTree", "RubyInline",
|
51
|
+
my_projects = Regexp.union("InlineFortran", "ParseTree", "RubyInline",
|
52
|
+
"RubyToC", "ZenHacks", "ZenTest", "bfts",
|
53
|
+
"box_layout", "flog", "heckle", "hoe",
|
54
|
+
"image_science", "miniunit", "png", "ruby2ruby",
|
55
|
+
"rubyforge", "vlad", "zentest")
|
64
56
|
|
65
57
|
$owners = {}
|
66
|
-
|
67
|
-
|
58
|
+
|
59
|
+
GemUpdater.get_latest_gems.each do |spec|
|
60
|
+
name = spec.name
|
68
61
|
owner = spec.authors.compact
|
69
62
|
owner = Array(spec.email) if owner.empty?
|
70
63
|
owner.map! { |o| o.sub(/\s*[^ \w@.].*$/, '') }
|
@@ -73,17 +66,22 @@ cache['http://gems.rubyforge.org'].source_index.latest_specs.map { |name, spec|
|
|
73
66
|
# because we screwed these up back before hoe
|
74
67
|
owner << "Eric Hodel" if name =~ /bfts|RubyToC|ParseTree|heckle/
|
75
68
|
|
76
|
-
$owners["#{spec.full_name}.
|
77
|
-
|
69
|
+
$owners["#{spec.full_name}.tgz"] = owner.uniq || 'omg I have no idea'
|
70
|
+
end
|
78
71
|
|
79
72
|
def score_for dir
|
80
73
|
files = `find #{dir} -name \\*.rb | grep -v gen.*templ`.split(/\n/)
|
81
74
|
|
82
75
|
flogger = Flog.new
|
83
76
|
flogger.flog_files files
|
84
|
-
methods = flogger.totals.reject { |k,v| k =~ /\#none$/ }
|
85
|
-
|
86
|
-
|
77
|
+
methods = flogger.totals.reject { |k,v| k =~ /\#none$/ }
|
78
|
+
{
|
79
|
+
:total => flogger.total,
|
80
|
+
:size => methods.size,
|
81
|
+
:average => flogger.average,
|
82
|
+
:stddev => flogger.stddev,
|
83
|
+
:methods => methods
|
84
|
+
}
|
87
85
|
rescue SyntaxError => e
|
88
86
|
warn e.inspect + " at " + e.backtrace.first(5).join(', ') if $v
|
89
87
|
$syntax_error.dup
|
@@ -92,44 +90,40 @@ rescue => e
|
|
92
90
|
$misc_error.dup
|
93
91
|
end
|
94
92
|
|
95
|
-
|
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
|
-
|
93
|
+
# extract all the gems and process the data for them.
|
103
94
|
begin
|
104
95
|
dirty = false
|
105
96
|
Dir.chdir "../gems" do
|
106
|
-
Dir["*.
|
97
|
+
Dir["*.tgz"].each_with_index do |gem, i|
|
107
98
|
project = File.basename gem
|
108
|
-
next if scores.has_key? project unless $f and scores[project][
|
99
|
+
next if scores.has_key? project unless $f and scores[project][:total] < 0
|
109
100
|
dirty = true
|
110
101
|
begin
|
111
102
|
warn gem
|
112
|
-
dir = gem.sub(/\.
|
113
|
-
|
103
|
+
dir = gem.sub(/\.tgz$/, '')
|
104
|
+
|
105
|
+
system "tar -zmxf #{gem} 2> /dev/null"
|
106
|
+
|
114
107
|
Dir.chdir dir do
|
115
|
-
system "(tar -Oxf ../#{gem} data.tar.gz | tar zxf -) 2> /dev/null"
|
116
108
|
system "chmod -R a+r ."
|
117
109
|
scores[project] = score_for(File.directory?('lib') ? 'lib' : '.')
|
118
110
|
end
|
119
111
|
ensure
|
120
112
|
system "rm -rf #{dir}"
|
121
113
|
end
|
122
|
-
|
123
|
-
if i % 500 == 0 then
|
124
|
-
save_scores scores
|
125
|
-
end
|
126
114
|
end
|
127
115
|
end
|
128
116
|
ensure
|
129
117
|
save_scores scores if dirty
|
130
118
|
end
|
131
119
|
|
132
|
-
scores.reject! { |k,v| v.
|
120
|
+
scores.reject! { |k,v| v[:total].nil? or v[:methods].empty? }
|
121
|
+
|
122
|
+
class Hash
|
123
|
+
def sorted_methods
|
124
|
+
self[:methods].sort_by { |k,v| -v }
|
125
|
+
end
|
126
|
+
end
|
133
127
|
|
134
128
|
class Array
|
135
129
|
def sum
|
@@ -170,13 +164,13 @@ def report title, data
|
|
170
164
|
end
|
171
165
|
end
|
172
166
|
|
173
|
-
project_numbers = scores.map { |k,v| [k, v[
|
167
|
+
project_numbers = scores.map { |k,v| [k, v[:methods].map {|_,n| n}.flatten] }
|
174
168
|
project_stats = project_numbers.map { |k,v| [k, v.size, v.average, v.stddev] }
|
175
169
|
|
176
170
|
title "Statistics" do
|
177
|
-
flog_numbers = scores.map { |k,v| v
|
178
|
-
all_scores = scores.map { |k,v| v[
|
179
|
-
method_counts =
|
171
|
+
flog_numbers = scores.map { |k,v| v[:total] }
|
172
|
+
all_scores = scores.map { |k,v| v[:methods].values }.flatten
|
173
|
+
method_counts = scores.map { |k,v| v[:size] }
|
180
174
|
|
181
175
|
puts "total # gems : %8d" % scores.size
|
182
176
|
puts "total # methods : %8d" % all_scores.size
|
@@ -194,14 +188,27 @@ def report_worst section, data
|
|
194
188
|
end
|
195
189
|
end
|
196
190
|
|
197
|
-
worst = scores.sort_by { |k,v| -v
|
191
|
+
worst = scores.sort_by { |k,v| -v[:total] }.first(max)
|
198
192
|
report_worst "Worst Projects EVAR", worst do |project, score|
|
199
|
-
|
193
|
+
owner = $owners[project].join(', ') rescue nil
|
194
|
+
raise "#{project} seems not to have an owner" if owner.nil?
|
195
|
+
[score[:total], project, owner]
|
200
196
|
end
|
201
197
|
|
202
|
-
worst =
|
203
|
-
|
204
|
-
|
198
|
+
worst = {}
|
199
|
+
scores.each do |long_name, spec|
|
200
|
+
name = long_name.sub(/-(\d+\.)*\d+\.gem$/, '')
|
201
|
+
spec[:methods].each do |method_name, score|
|
202
|
+
worst[[name, method_name]] = score
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
worst = worst.sort_by { |_,v| -v }.first(max)
|
207
|
+
|
208
|
+
max_size = worst.map { |(name, meth), score| name.size }.max
|
209
|
+
title "Worth Methods EVAR"
|
210
|
+
worst.each_with_index do |((name, meth), score), i|
|
211
|
+
puts "%3d: %9.2f: %-#{max_size}s %s" % [i + 1, score, name, meth]
|
205
212
|
end
|
206
213
|
|
207
214
|
report "Methods per Gem", project_stats.sort_by { |n, c, a, sd| -c }.first(max)
|
@@ -212,7 +219,7 @@ $projects_per_owner = Hash.new { |h,k| h[k] = {} }
|
|
212
219
|
$owners.each do |project, owners|
|
213
220
|
next unless scores.has_key? project # bad project
|
214
221
|
owners.each do |owner|
|
215
|
-
score = scores[project]
|
222
|
+
score = scores[project][:total] || 1000000
|
216
223
|
$projects_per_owner[owner][project] = score
|
217
224
|
$score_per_owner[owner] += score
|
218
225
|
end
|
@@ -234,5 +241,5 @@ report_bad_people "Top Flog Scores per Developer" do
|
|
234
241
|
end
|
235
242
|
|
236
243
|
report_bad_people "Most Prolific Developers" do |k,v|
|
237
|
-
$projects_per_owner.sort_by { |k,v| -v.size }.first(max)
|
244
|
+
$projects_per_owner.sort_by { |k,v| [-v.size, -$score_per_owner[k]] }.first(max)
|
238
245
|
end
|
metadata
CHANGED
@@ -1,73 +1,94 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: flog
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-08-21 00:00:00 -07:00
|
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
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: ryand-ruby@zenspider.com
|
12
|
-
homepage: http://ruby.sadi.st/
|
13
|
-
rubyforge_project: seattlerb
|
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
|
-
autorequire:
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
version: 1.2.0
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Ryan Davis
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
- README.txt
|
35
|
-
- Rakefile
|
36
|
-
- bin/flog
|
37
|
-
- lib/flog.rb
|
38
|
-
- unpack.rb
|
39
|
-
- update_scores.rb
|
40
|
-
test_files: []
|
41
|
-
|
42
|
-
rdoc_options:
|
43
|
-
- --main
|
44
|
-
- README.txt
|
45
|
-
extra_rdoc_files:
|
46
|
-
- History.txt
|
47
|
-
- Manifest.txt
|
48
|
-
- README.txt
|
49
|
-
executables:
|
50
|
-
- flog
|
51
|
-
extensions: []
|
52
|
-
|
53
|
-
requirements: []
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
54
11
|
|
12
|
+
date: 2008-10-22 00:00:00 -05:00
|
13
|
+
default_executable:
|
55
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sexp_processor
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "3.0"
|
24
|
+
version:
|
56
25
|
- !ruby/object:Gem::Dependency
|
57
26
|
name: ParseTree
|
27
|
+
type: :runtime
|
58
28
|
version_requirement:
|
59
|
-
version_requirements: !ruby/object:Gem::
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
30
|
requirements:
|
61
|
-
- -
|
31
|
+
- - ~>
|
62
32
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
33
|
+
version: "3.0"
|
64
34
|
version:
|
65
35
|
- !ruby/object:Gem::Dependency
|
66
36
|
name: hoe
|
37
|
+
type: :development
|
67
38
|
version_requirement:
|
68
|
-
version_requirements: !ruby/object:Gem::
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
69
40
|
requirements:
|
70
41
|
- - ">="
|
71
42
|
- !ruby/object:Gem::Version
|
72
|
-
version: 1.
|
43
|
+
version: 1.8.0
|
73
44
|
version:
|
45
|
+
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.
|
46
|
+
email:
|
47
|
+
- ryand-ruby@zenspider.com
|
48
|
+
executables:
|
49
|
+
- flog
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files:
|
53
|
+
- History.txt
|
54
|
+
- Manifest.txt
|
55
|
+
- README.txt
|
56
|
+
files:
|
57
|
+
- History.txt
|
58
|
+
- Manifest.txt
|
59
|
+
- README.txt
|
60
|
+
- Rakefile
|
61
|
+
- bin/flog
|
62
|
+
- gem_updater.rb
|
63
|
+
- lib/flog.rb
|
64
|
+
- unpack.rb
|
65
|
+
- update_scores.rb
|
66
|
+
has_rdoc: true
|
67
|
+
homepage: http://ruby.sadi.st/
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options:
|
70
|
+
- --main
|
71
|
+
- README.txt
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
version:
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project: seattlerb
|
89
|
+
rubygems_version: 1.3.0
|
90
|
+
signing_key:
|
91
|
+
specification_version: 2
|
92
|
+
summary: Flog reports the most tortured code in an easy to read pain report
|
93
|
+
test_files: []
|
94
|
+
|