flog 1.2.0 → 2.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,193 @@
1
+ #!/usr/bin/ruby -ws
2
+
3
+ $: << 'lib' << '../../ParseTree/dev/lib' << '../../flog/dev/lib'
4
+
5
+ $v ||= false # HACK
6
+
7
+ require 'rubygems'
8
+ require 'flog'
9
+
10
+ require 'gauntlet'
11
+ require 'pp'
12
+
13
+ class FlogGauntlet < Gauntlet
14
+ $owners = {}
15
+ $score_file = 'flog-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 => {}}
19
+
20
+ # copied straight from hoedown.rb
21
+ my_projects = %w[InlineFortran ParseTree RubyInline RubyToC
22
+ ZenHacks ZenTest bfts box_layout
23
+ change_class flay flog gauntlet heckle
24
+ hoe image_science miniunit minitest
25
+ minitest_tu_shim png ruby2ruby ruby_parser
26
+ rubyforge test-unit un vlad zenprofile
27
+ zentest]
28
+
29
+ MY_PROJECTS = Regexp.union(*my_projects)
30
+
31
+ def run name
32
+ warn name
33
+ self.data[name] = score_for '.'
34
+ self.dirty = true
35
+ end
36
+
37
+ def display_report max = 10
38
+ scores = @data.reject { |k,v| v[:total].nil? or v[:methods].empty? }
39
+ project_numbers = scores.map { |k,v| [k, v[:methods].values] }
40
+ project_stats = project_numbers.map { |k,v| [k, scores[k][:size], v.average, v.stddev] }
41
+
42
+ method_count = 0
43
+ project_stats.each do |_, n, _, _|
44
+ method_count += n
45
+ end
46
+
47
+ group_by_owner
48
+
49
+ title "Statistics" do
50
+ flog_numbers = scores.map { |k,v| v[:total] }
51
+ method_counts = scores.map { |k,v| v[:size] }
52
+
53
+ puts "total # gems : %8d" % scores.size
54
+ puts "total # methods : %8d" % method_count
55
+ puts "avg methods / gem : %8.2f (%8.2f stddev)" % [method_counts.average, method_counts.stddev]
56
+ puts "avg flog / gem : %8.2f (%8.2f stddev)" % [flog_numbers.average, flog_numbers.stddev]
57
+ end
58
+
59
+ worst = scores.sort_by { |k,v| -v[:total] }.first(max)
60
+ report_worst "Worst Projects EVAR", worst do |project, score|
61
+ owner = $owners[project].join(', ') rescue nil
62
+ owner = "Some Lazy Bastard" if owner.empty?
63
+ raise "#{project} seems not to have an owner" if owner.nil?
64
+ [score[:total], project, owner]
65
+ end
66
+
67
+ worst = {}
68
+ scores.each do |long_name, spec|
69
+ name = long_name.sub(/-(\d+\.)*\d+\.gem$/, '')
70
+ spec[:methods].each do |method_name, score|
71
+ worst[[name, method_name]] = score
72
+ end
73
+ end
74
+
75
+ worst = worst.sort_by { |_,v| -v }.first(max)
76
+
77
+ max_size = worst.map { |(name, meth), score| name.size }.max
78
+ title "Worth Methods EVAR"
79
+ worst.each_with_index do |((name, meth), score), i|
80
+ puts "%3d: %9.2f: %-#{max_size}s %s" % [i + 1, score, name, meth]
81
+ end
82
+
83
+ report "Methods per Gem", project_stats.sort_by { |n, c, a, sd| -c }.first(max)
84
+ report "Avg Flog / Method", project_stats.sort_by { |n, c, a, sd| -a }.first(max)
85
+
86
+ $score_per_owner = Hash.new(0.0)
87
+ $projects_per_owner = Hash.new { |h,k| h[k] = {} }
88
+ $owners.each do |project, owners|
89
+ next unless scores.has_key? project # bad project
90
+ owners.each do |owner|
91
+ next if owner =~ /FI\XME full name|NOT Ryan Davis/
92
+ score = scores[project][:total] || 1000000
93
+ $projects_per_owner[owner][project] = score
94
+ $score_per_owner[owner] += score
95
+ end
96
+ end
97
+
98
+ report_bad_people "Top Flog Scores per Developer" do
99
+ $projects_per_owner.sort_by { |k,v| -v.values.average }.first(max)
100
+ end
101
+
102
+ report_bad_people "Most Prolific Developers" do |k,v|
103
+ $projects_per_owner.sort_by { |k,v| [-v.size, -$score_per_owner[k]] }.first(max)
104
+ end
105
+ end
106
+
107
+ ############################################################
108
+ # OTHER
109
+ ############################################################
110
+
111
+ def score_for dir
112
+ # files = `find #{dir} -name \\*.rb | grep -v gen.*templ`.split(/\n/)
113
+ files = Dir["**/*.rb"].reject { |f| f =~ /gen.*templ|gemspec.rb/ }
114
+
115
+ flogger = Flog.new
116
+ flogger.flog_files files
117
+ methods = flogger.totals.reject { |k,v| k =~ /\#none$/ }
118
+ n = 20
119
+ topN = Hash[*methods.sort_by { |k,v| -v }.first(n).flatten]
120
+ {
121
+ :max => methods.values.max,
122
+ :total => flogger.total,
123
+ :size => methods.size,
124
+ :average => flogger.average,
125
+ :stddev => flogger.stddev,
126
+ :methods => topN,
127
+ }
128
+ rescue SyntaxError => e
129
+ warn e.inspect + " at " + e.backtrace.first(5).join(', ') if $v
130
+ $syntax_error.dup
131
+ rescue => e
132
+ warn e.inspect + " at " + e.backtrace.first(5).join(', ') if $v
133
+ $misc_error.dup
134
+ end
135
+
136
+ def title heading
137
+ puts
138
+ puts "#{heading}:"
139
+ puts
140
+ yield if block_given?
141
+ end
142
+
143
+ def report title, data
144
+ max = data.map { |d| d.first.size }.max
145
+
146
+ title "Top #{data.size} #{title}" if title
147
+ data.each_with_index do |(n, c, a, s), i|
148
+ puts "%4d: %-#{max}s: %5d methods, %8.2f +/- %8.2f flog" % [i + 1, n, c, a, s]
149
+ end
150
+ end
151
+
152
+ def report_bad_people section
153
+ title section
154
+ bad_people = yield
155
+ max_size = bad_people.map { |a| a.first.size }.max
156
+ fmt = "%4d: %-#{max_size}s: %2d projects %8.1f tot %8.1f avg"
157
+ bad_people.each_with_index do |(name, projects), i|
158
+ avg = projects.values.average
159
+ puts fmt % [i + 1, name, projects.size, $score_per_owner[name], avg]
160
+ end
161
+ end
162
+
163
+ def report_worst section, data
164
+ title section do
165
+ max_size = data.map { |k| k.first.size }.max
166
+ data.each_with_index do |(k,v), i|
167
+ puts "%3d: %9.2f: %-#{max_size}s %s" % [i + 1, *yield(k, v)]
168
+ end
169
+ end
170
+ end
171
+
172
+ def group_by_owner
173
+ latest_gems.each do |spec|
174
+ name = spec.name
175
+ owner = spec.authors.compact
176
+ owner = Array(spec.email) if owner.empty?
177
+ owner.map! { |o| o.sub(/\s*[^ \w@.].*$/, '') }
178
+ owner = ["NOT Ryan Davis"] if owner.include? "Ryan Davis" and name !~ MY_PROJECTS
179
+
180
+ # because we screwed these up back before hoe
181
+ owner << "Eric Hodel" if name =~ /bfts|RubyToC|ParseTree|heckle/
182
+
183
+ $owners[spec.full_name] = owner.uniq || 'omg I have no idea'
184
+ end
185
+ end
186
+ end
187
+
188
+ max = (ARGV.shift || 10).to_i
189
+ filter = ARGV.shift
190
+ filter = Regexp.new filter if filter
191
+ flogger = FlogGauntlet.new
192
+ flogger.run_the_gauntlet filter
193
+ flogger.display_report max
@@ -0,0 +1,352 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require 'flog'
3
+
4
+ describe 'flog command' do
5
+ before :each do
6
+ @flog = stub('Flog', :flog_files => true, :report => true)
7
+ Flog.stubs(:new).returns(@flog)
8
+ self.stubs(:exit)
9
+ self.stubs(:puts)
10
+ end
11
+
12
+ def run_command
13
+ eval File.read(File.join(File.dirname(__FILE__), *%w[.. bin flog]))
14
+ end
15
+
16
+ describe 'when no command-line arguments are specified' do
17
+ before :each do
18
+ Object.send(:remove_const, :ARGV)
19
+ ARGV = []
20
+ end
21
+
22
+ it 'should run' do
23
+ lambda { run_command }.should_not raise_error(Errno::ENOENT)
24
+ end
25
+
26
+ it 'should not alter the include path' do
27
+ @paths = $:.dup
28
+ run_command
29
+ $:.should == @paths
30
+ end
31
+
32
+ it 'should create a Flog instance' do
33
+ Flog.expects(:new).returns(@flog)
34
+ run_command
35
+ end
36
+
37
+ it 'should not have any options flags set' do
38
+ Flog.expects(:new).with({}).returns(@flog)
39
+ run_command
40
+ end
41
+
42
+ it 'should call flog_files on the Flog instance' do
43
+ @flog.expects(:flog_files)
44
+ run_command
45
+ end
46
+
47
+ it "should pass '-' (for the file path) to flog_files on the instance" do
48
+ @flog.expects(:flog_files).with(['-'])
49
+ run_command
50
+ end
51
+
52
+ it 'should call report on the Flog instance' do
53
+ @flog.expects(:report)
54
+ run_command
55
+ end
56
+
57
+ it 'should exit with status 0' do
58
+ self.expects(:exit).with(0)
59
+ run_command
60
+ end
61
+ end
62
+
63
+ describe "when -a is specified on the command-line" do
64
+ before :each do
65
+ Object.send(:remove_const, :ARGV)
66
+ ARGV = ['-a']
67
+ end
68
+
69
+ it 'should create a Flog instance' do
70
+ Flog.expects(:new).returns(@flog)
71
+ run_command
72
+ end
73
+
74
+ it "should set the option to show all methods" do
75
+ Flog.expects(:new).with(:all => true).returns(@flog)
76
+ run_command
77
+ end
78
+
79
+ it 'should exit with status 0' do
80
+ self.expects(:exit).with(0)
81
+ run_command
82
+ end
83
+ end
84
+
85
+ describe "when --all is specified on the command-line" do
86
+ before :each do
87
+ Object.send(:remove_const, :ARGV)
88
+ ARGV = ['--all']
89
+ end
90
+
91
+ it 'should create a Flog instance' do
92
+ Flog.expects(:new).returns(@flog)
93
+ run_command
94
+ end
95
+
96
+ it "should set the option to show all methods" do
97
+ Flog.expects(:new).with(:all => true).returns(@flog)
98
+ run_command
99
+ end
100
+
101
+ it 'should exit with status 0' do
102
+ self.expects(:exit).with(0)
103
+ run_command
104
+ end
105
+ end
106
+
107
+ describe "when -s is specified on the command-line" do
108
+ before :each do
109
+ Object.send(:remove_const, :ARGV)
110
+ ARGV = ['-s']
111
+ end
112
+
113
+ it 'should create a Flog instance' do
114
+ Flog.expects(:new).returns(@flog)
115
+ run_command
116
+ end
117
+
118
+ it "should set the option to show only the score" do
119
+ Flog.expects(:new).with(:score => true).returns(@flog)
120
+ run_command
121
+ end
122
+
123
+ it 'should exit with status 0' do
124
+ self.expects(:exit).with(0)
125
+ run_command
126
+ end
127
+ end
128
+
129
+ describe "when --score is specified on the command-line" do
130
+ before :each do
131
+ Object.send(:remove_const, :ARGV)
132
+ ARGV = ['--score']
133
+ end
134
+
135
+ it 'should create a Flog instance' do
136
+ Flog.expects(:new).returns(@flog)
137
+ run_command
138
+ end
139
+
140
+ it "should set the option to show only the score" do
141
+ Flog.expects(:new).with(:score => true).returns(@flog)
142
+ run_command
143
+ end
144
+
145
+ it 'should exit with status 0' do
146
+ self.expects(:exit).with(0)
147
+ run_command
148
+ end
149
+ end
150
+
151
+ describe "when -m is specified on the command-line" do
152
+ before :each do
153
+ Object.send(:remove_const, :ARGV)
154
+ ARGV = ['-m']
155
+ end
156
+
157
+ it 'should create a Flog instance' do
158
+ Flog.expects(:new).returns(@flog)
159
+ run_command
160
+ end
161
+
162
+ it "should set the option to report on methods only" do
163
+ Flog.expects(:new).with(:methods => true).returns(@flog)
164
+ run_command
165
+ end
166
+
167
+ it 'should exit with status 0' do
168
+ self.expects(:exit).with(0)
169
+ run_command
170
+ end
171
+ end
172
+
173
+ describe "when --methods-only is specified on the command-line" do
174
+ before :each do
175
+ Object.send(:remove_const, :ARGV)
176
+ ARGV = ['--methods-only']
177
+ end
178
+
179
+ it 'should create a Flog instance' do
180
+ Flog.expects(:new).returns(@flog)
181
+ run_command
182
+ end
183
+
184
+ it "should set the option to report on methods only" do
185
+ Flog.expects(:new).with(:methods => true).returns(@flog)
186
+ run_command
187
+ end
188
+
189
+ it 'should exit with status 0' do
190
+ self.expects(:exit).with(0)
191
+ run_command
192
+ end
193
+ end
194
+
195
+ describe "when -v is specified on the command-line" do
196
+ before :each do
197
+ Object.send(:remove_const, :ARGV)
198
+ ARGV = ['-v']
199
+ end
200
+
201
+ it 'should create a Flog instance' do
202
+ Flog.expects(:new).returns(@flog)
203
+ run_command
204
+ end
205
+
206
+ it "should set the option to be verbose" do
207
+ Flog.expects(:new).with(:verbose => true).returns(@flog)
208
+ run_command
209
+ end
210
+
211
+ it 'should exit with status 0' do
212
+ self.expects(:exit).with(0)
213
+ run_command
214
+ end
215
+ end
216
+
217
+ describe "when --verbose is specified on the command-line" do
218
+ before :each do
219
+ Object.send(:remove_const, :ARGV)
220
+ ARGV = ['--verbose']
221
+ end
222
+
223
+ it 'should create a Flog instance' do
224
+ Flog.expects(:new).returns(@flog)
225
+ run_command
226
+ end
227
+
228
+ it "should set the option to be verbose" do
229
+ Flog.expects(:new).with(:verbose => true).returns(@flog)
230
+ run_command
231
+ end
232
+
233
+ it 'should exit with status 0' do
234
+ self.expects(:exit).with(0)
235
+ run_command
236
+ end
237
+ end
238
+
239
+ describe "when -h is specified on the command-line" do
240
+ before :each do
241
+ Object.send(:remove_const, :ARGV)
242
+ ARGV = ['-h']
243
+ end
244
+
245
+ it "should display help information" do
246
+ self.expects(:puts)
247
+ run_command
248
+ end
249
+
250
+ it 'should not create a Flog instance' do
251
+ Flog.expects(:new).never
252
+ run_command
253
+ end
254
+
255
+ it 'should exit with status 0' do
256
+ self.expects(:exit).with(0)
257
+ run_command
258
+ end
259
+ end
260
+
261
+ describe "when --help is specified on the command-line" do
262
+ before :each do
263
+ Object.send(:remove_const, :ARGV)
264
+ ARGV = ['--help']
265
+ end
266
+
267
+ it "should display help information" do
268
+ self.expects(:puts)
269
+ run_command
270
+ end
271
+
272
+ it 'should not create a Flog instance' do
273
+ Flog.expects(:new).never
274
+ run_command
275
+ end
276
+
277
+ it 'should exit with status 0' do
278
+ self.expects(:exit).with(0)
279
+ run_command
280
+ end
281
+ end
282
+
283
+ describe 'when -I is specified on the command-line' do
284
+ before :each do
285
+ Object.send(:remove_const, :ARGV)
286
+ ARGV = ['-I /tmp,/etc']
287
+ end
288
+
289
+ before :each do
290
+ @paths = $:.dup
291
+ end
292
+
293
+ it "should append each ':' separated path to $:" do
294
+ run_command
295
+ $:.should_not == @paths
296
+ end
297
+
298
+ it 'should create a Flog instance' do
299
+ Flog.expects(:new).returns(@flog)
300
+ run_command
301
+ end
302
+
303
+ it 'should exit with status 0' do
304
+ self.expects(:exit).with(0)
305
+ run_command
306
+ end
307
+ end
308
+
309
+ describe 'when -b is specified on the command-line' do
310
+ before :each do
311
+ Object.send(:remove_const, :ARGV)
312
+ ARGV = ['-b']
313
+ end
314
+
315
+ it 'should create a Flog instance' do
316
+ Flog.expects(:new).returns(@flog)
317
+ run_command
318
+ end
319
+
320
+ it "should set the option to provide 'blame' information" do
321
+ Flog.expects(:new).with(:blame => true).returns(@flog)
322
+ run_command
323
+ end
324
+
325
+ it 'should exit with status 0' do
326
+ self.expects(:exit).with(0)
327
+ run_command
328
+ end
329
+ end
330
+
331
+ describe 'when --blame is specified on the command-line' do
332
+ before :each do
333
+ Object.send(:remove_const, :ARGV)
334
+ ARGV = ['--blame']
335
+ end
336
+
337
+ it 'should create a Flog instance' do
338
+ Flog.expects(:new).returns(@flog)
339
+ run_command
340
+ end
341
+
342
+ it "should set the option to provide 'blame' information" do
343
+ Flog.expects(:new).with(:blame => true).returns(@flog)
344
+ run_command
345
+ end
346
+
347
+ it 'should exit with status 0' do
348
+ self.expects(:exit).with(0)
349
+ run_command
350
+ end
351
+ end
352
+ end