flay 1.2.1 → 1.3.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.tar.gz.sig +0 -0
- data/History.txt +10 -0
- data/Rakefile +8 -39
- data/lib/flay.rb +67 -63
- data/test/test_flay.rb +8 -67
- metadata +15 -10
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
=== 1.3.0 / 2009-06-23
|
2
|
+
|
3
|
+
* 5 minor enhancements:
|
4
|
+
|
5
|
+
* Added --summary to display flay scores per file.
|
6
|
+
* Added --verbose to display processing progress.
|
7
|
+
* Protect against syntax errors in bad code and continue flaying.
|
8
|
+
* Removed fuzzy matching. Never got it to feel right. Slow. Broken on 1.9
|
9
|
+
* Renamed --verbose to --diff.
|
10
|
+
|
1
11
|
=== 1.2.1 / 2009-03-16
|
2
12
|
|
3
13
|
* 3 minor enhancements:
|
data/Rakefile
CHANGED
@@ -6,47 +6,16 @@ require 'hoe'
|
|
6
6
|
Hoe::add_include_dirs("../../sexp_processor/dev/lib",
|
7
7
|
"../../ruby_parser/dev/lib")
|
8
8
|
|
9
|
-
|
10
|
-
require 'flay'
|
11
|
-
rescue LoadError
|
12
|
-
load 'lib/flay.rb'
|
13
|
-
end
|
14
|
-
|
15
|
-
Hoe.new('flay', Flay::VERSION) do |flay|
|
16
|
-
flay.rubyforge_name = 'seattlerb'
|
17
|
-
flay.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
|
18
|
-
|
19
|
-
flay.flay_threshold = 250
|
20
|
-
|
21
|
-
flay.extra_deps << ['sexp_processor', '>= 3.0.0']
|
22
|
-
flay.extra_deps << ['ruby_parser', '>= 1.1.0']
|
23
|
-
end
|
24
|
-
|
25
|
-
begin
|
26
|
-
require 'rcov/rcovtask'
|
27
|
-
Rcov::RcovTask.new do |t|
|
28
|
-
pattern = ENV['PATTERN'] || 'test/test_*.rb'
|
9
|
+
Hoe.plugin :seattlerb
|
29
10
|
|
30
|
-
|
31
|
-
|
32
|
-
t.rcov_opts << "--threshold 80"
|
33
|
-
t.rcov_opts << "--no-color"
|
34
|
-
end
|
11
|
+
Hoe.spec 'flay' do
|
12
|
+
developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
|
35
13
|
|
36
|
-
|
37
|
-
|
38
|
-
ruby "-Ilib -S rcov --text-report --save coverage.info -x rcov,sexp_processor --test-unit-only #{pattern}"
|
39
|
-
end
|
14
|
+
self.rubyforge_name = 'seattlerb'
|
15
|
+
self.flay_threshold = 250
|
40
16
|
|
41
|
-
|
42
|
-
|
43
|
-
puts rcov[:lines].zip(rcov[:coverage]).map { |line, coverage|
|
44
|
-
bol, eol = eol, eol + line.length
|
45
|
-
[bol, eol, "#ffcccc"] unless coverage
|
46
|
-
}.compact.inspect
|
47
|
-
end
|
48
|
-
rescue LoadError
|
49
|
-
# skip
|
17
|
+
extra_deps << ['sexp_processor', '~> 3.0']
|
18
|
+
extra_deps << ['ruby_parser', '~> 2.0']
|
50
19
|
end
|
51
20
|
|
52
|
-
# vim: syntax=
|
21
|
+
# vim: syntax=ruby
|
data/lib/flay.rb
CHANGED
@@ -8,16 +8,15 @@ require 'rubygems'
|
|
8
8
|
require 'sexp_processor'
|
9
9
|
require 'ruby_parser'
|
10
10
|
|
11
|
-
abort "update rubygems to >= 1.3.1" unless Gem.respond_to? :find_files
|
12
|
-
|
13
11
|
class Flay
|
14
|
-
VERSION = '1.
|
12
|
+
VERSION = '1.3.0'
|
15
13
|
|
16
14
|
def self.default_options
|
17
15
|
{
|
18
|
-
:
|
19
|
-
:verbose => false,
|
16
|
+
:diff => false,
|
20
17
|
:mass => 16,
|
18
|
+
:summary => false,
|
19
|
+
:verbose => false,
|
21
20
|
}
|
22
21
|
end
|
23
22
|
|
@@ -37,23 +36,37 @@ class Flay
|
|
37
36
|
exit
|
38
37
|
end
|
39
38
|
|
40
|
-
opts.on('-f', '--fuzzy', "
|
41
|
-
|
39
|
+
opts.on('-f', '--fuzzy', "DEAD: fuzzy similarities.") do
|
40
|
+
abort "--fuzzy is no longer supported. Sorry. It sucked."
|
42
41
|
end
|
43
42
|
|
44
43
|
opts.on('-m', '--mass MASS', Integer, "Sets mass threshold") do |m|
|
45
44
|
options[:mass] = m.to_i
|
46
45
|
end
|
47
46
|
|
48
|
-
opts.on('-v', '--verbose', "Verbose.
|
47
|
+
opts.on('-v', '--verbose', "Verbose. Show progress processing files.") do
|
49
48
|
options[:verbose] = true
|
50
49
|
end
|
51
50
|
|
51
|
+
opts.on('-d', '--diff', "Diff Mode. Display N-Way diff for ruby.") do
|
52
|
+
options[:diff] = true
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on('-s', '--summary', "Summarize. Show flay score per file only.") do
|
56
|
+
options[:summary] = true
|
57
|
+
end
|
58
|
+
|
52
59
|
extensions = ['rb'] + Flay.load_plugins
|
53
60
|
|
54
61
|
opts.separator ""
|
55
62
|
opts.separator "Known extensions: #{extensions.join(', ')}"
|
56
|
-
|
63
|
+
|
64
|
+
begin
|
65
|
+
opts.parse!
|
66
|
+
rescue => e
|
67
|
+
abort "#{e}\n\n#{opts}"
|
68
|
+
end
|
69
|
+
end
|
57
70
|
|
58
71
|
options
|
59
72
|
end
|
@@ -85,6 +98,8 @@ class Flay
|
|
85
98
|
@@plugins = plugins.map { |f| File.basename(f, '.rb').sub(/^flay_/, '') }
|
86
99
|
end
|
87
100
|
@@plugins
|
101
|
+
rescue
|
102
|
+
# ignore
|
88
103
|
end
|
89
104
|
|
90
105
|
attr_accessor :mass_threshold, :total, :identical, :masses
|
@@ -99,12 +114,12 @@ class Flay
|
|
99
114
|
self.total = 0
|
100
115
|
self.mass_threshold = @option[:mass]
|
101
116
|
|
102
|
-
require 'ruby2ruby' if @option[:
|
117
|
+
require 'ruby2ruby' if @option[:diff]
|
103
118
|
end
|
104
119
|
|
105
120
|
def process(*files) # TODO: rename from process - should act as SexpProcessor
|
106
121
|
files.each do |file|
|
107
|
-
warn "Processing #{file}"
|
122
|
+
warn "Processing #{file}" if option[:verbose]
|
108
123
|
|
109
124
|
ext = File.extname(file).sub(/^\./, '')
|
110
125
|
ext = "rb" if ext.nil? || ext.empty?
|
@@ -115,21 +130,23 @@ class Flay
|
|
115
130
|
msg = "process_rb"
|
116
131
|
end
|
117
132
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
133
|
+
begin
|
134
|
+
sexp = begin
|
135
|
+
send msg, file
|
136
|
+
rescue => e
|
137
|
+
warn " #{e.message.strip}"
|
138
|
+
warn " skipping #{file}"
|
139
|
+
nil
|
140
|
+
end
|
125
141
|
|
126
|
-
|
142
|
+
next unless sexp
|
127
143
|
|
128
|
-
|
144
|
+
process_sexp sexp
|
145
|
+
rescue SyntaxError => e
|
146
|
+
warn " skipping #{file}: #{e.message}"
|
147
|
+
end
|
129
148
|
end
|
130
149
|
|
131
|
-
process_fuzzy_similarities if option[:fuzzy]
|
132
|
-
|
133
150
|
analyze
|
134
151
|
end
|
135
152
|
|
@@ -157,42 +174,6 @@ class Flay
|
|
157
174
|
end
|
158
175
|
end
|
159
176
|
|
160
|
-
def process_fuzzy_similarities
|
161
|
-
all_hashes, detected = {}, {}
|
162
|
-
|
163
|
-
self.hashes.values.each do |nodes|
|
164
|
-
nodes.each do |node|
|
165
|
-
next if node.mass > 4 * self.mass_threshold
|
166
|
-
# TODO: try out with fuzzy_hash
|
167
|
-
# all_hashes[node] = node.grep(Sexp).map { |s| [s.hash] * s.mass }.flatten
|
168
|
-
all_hashes[node] = node.grep(Sexp).map { |s| [s.hash] }.flatten
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# warn "looking for copy/paste/edit code across #{all_hashes.size} nodes"
|
173
|
-
|
174
|
-
all_hashes = all_hashes.to_a
|
175
|
-
all_hashes.each_with_index do |(s1, h1), i|
|
176
|
-
similar = [s1]
|
177
|
-
all_hashes[i+1..-1].each do |(s2, h2)|
|
178
|
-
next if detected[h2]
|
179
|
-
intersection = h1.intersection h2
|
180
|
-
max = [h1.size, h2.size].max
|
181
|
-
if intersection.size >= max * 0.60 then
|
182
|
-
similarity = s1.similarity(s2)
|
183
|
-
if similarity > 0.60 then
|
184
|
-
similar << s2
|
185
|
-
detected[h2] = true
|
186
|
-
else
|
187
|
-
p [similarity, s1, s2]
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
self.hashes[similar.first.hash].push(*similar) if similar.size > 1
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
177
|
def prune
|
197
178
|
# prune trees that aren't duped at all, or are too small
|
198
179
|
self.hashes.delete_if { |_,nodes| nodes.size == 1 }
|
@@ -242,10 +223,33 @@ class Flay
|
|
242
223
|
groups.flatten.join("\n")
|
243
224
|
end
|
244
225
|
|
226
|
+
def summary
|
227
|
+
score = Hash.new 0
|
228
|
+
|
229
|
+
masses.each do |hash, mass|
|
230
|
+
sexps = hashes[hash]
|
231
|
+
mass_per_file = mass.to_f / sexps.size
|
232
|
+
sexps.each do |sexp|
|
233
|
+
score[sexp.file] += mass_per_file
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
score
|
238
|
+
end
|
239
|
+
|
245
240
|
def report prune = nil
|
246
241
|
puts "Total score (lower is better) = #{self.total}"
|
247
242
|
puts
|
248
243
|
|
244
|
+
if option[:summary] then
|
245
|
+
|
246
|
+
self.summary.sort_by { |_,v| -v }.each do |file, score|
|
247
|
+
puts "%8.2f: %s" % [score, file]
|
248
|
+
end
|
249
|
+
|
250
|
+
return
|
251
|
+
end
|
252
|
+
|
249
253
|
count = 0
|
250
254
|
masses.sort_by { |h,m| [-m, hashes[h].first.file] }.each do |hash, mass|
|
251
255
|
nodes = hashes[hash]
|
@@ -265,16 +269,16 @@ class Flay
|
|
265
269
|
puts "%d) %s code found in %p (mass%s = %d)" %
|
266
270
|
[count, match, node.first, bonus, mass]
|
267
271
|
|
268
|
-
nodes.each_with_index do |
|
269
|
-
if option[:
|
272
|
+
nodes.each_with_index do |x, i|
|
273
|
+
if option[:diff] then
|
270
274
|
c = (?A + i).chr
|
271
|
-
puts " #{c}: #{
|
275
|
+
puts " #{c}: #{x.file}:#{x.line}"
|
272
276
|
else
|
273
|
-
puts " #{
|
277
|
+
puts " #{x.file}:#{x.line}"
|
274
278
|
end
|
275
279
|
end
|
276
280
|
|
277
|
-
if option[:
|
281
|
+
if option[:diff] then
|
278
282
|
puts
|
279
283
|
r2r = Ruby2Ruby.new
|
280
284
|
puts n_way_diff(*nodes.map { |s| r2r.process(s.deep_clone) })
|
data/test/test_flay.rb
CHANGED
@@ -5,11 +5,15 @@ require 'flay'
|
|
5
5
|
|
6
6
|
require 'pp' # TODO: remove
|
7
7
|
|
8
|
+
ON_1_9 = RUBY_VERSION =~ /1\.9/
|
9
|
+
SKIP_1_9 = true && ON_1_9 # HACK
|
10
|
+
|
8
11
|
class Symbol # for testing only, makes the tests concrete
|
9
12
|
def hash
|
10
13
|
to_s.hash
|
11
14
|
end
|
12
15
|
|
16
|
+
alias :crap :<=> if :blah.respond_to? :<=>
|
13
17
|
def <=> o
|
14
18
|
Symbol === o && self.to_s <=> o.to_s
|
15
19
|
end
|
@@ -65,11 +69,11 @@ class TestSexp < Test::Unit::TestCase
|
|
65
69
|
assert_equal hash, @s.fuzzy_hash, "ivar from setup"
|
66
70
|
assert_equal hash, @s.deep_clone.fuzzy_hash, "deep clone"
|
67
71
|
assert_equal hash, s.deep_clone.fuzzy_hash, "copy deep clone"
|
68
|
-
end
|
72
|
+
end unless SKIP_1_9
|
69
73
|
|
70
74
|
def test_all_subhashes
|
71
75
|
expected = [-704571402, -282578980, -35395725,
|
72
|
-
160138040, 815971090, 927228382]
|
76
|
+
160138040, 815971090, 927228382]
|
73
77
|
|
74
78
|
assert_equal expected, @s.all_subhashes.sort.uniq
|
75
79
|
|
@@ -80,7 +84,7 @@ class TestSexp < Test::Unit::TestCase
|
|
80
84
|
end
|
81
85
|
|
82
86
|
assert_equal expected, x.sort.uniq
|
83
|
-
end
|
87
|
+
end unless SKIP_1_9
|
84
88
|
|
85
89
|
def test_process_sexp
|
86
90
|
flay = Flay.new
|
@@ -123,7 +127,6 @@ class TestSexp < Test::Unit::TestCase
|
|
123
127
|
[:block],
|
124
128
|
[:call, :call],
|
125
129
|
[:call],
|
126
|
-
# HACK [:defn],
|
127
130
|
[:if],
|
128
131
|
[:return],
|
129
132
|
[:return],
|
@@ -133,7 +136,7 @@ class TestSexp < Test::Unit::TestCase
|
|
133
136
|
|
134
137
|
actual = flay.hashes.values.map { |sexps| sexps.map { |sexp| sexp.first } }
|
135
138
|
|
136
|
-
assert_equal expected, actual.sort_by { |a| a.
|
139
|
+
assert_equal expected, actual.sort_by { |a| a.inspect }
|
137
140
|
end
|
138
141
|
|
139
142
|
def test_process_sexp_no_structure
|
@@ -142,68 +145,6 @@ class TestSexp < Test::Unit::TestCase
|
|
142
145
|
|
143
146
|
assert flay.hashes.empty?
|
144
147
|
end
|
145
|
-
|
146
|
-
def test_process_fuzzy_similarities
|
147
|
-
flay = Flay.new :mass => 7
|
148
|
-
|
149
|
-
s1 = RubyParser.new.process("def w(n); a; b; c; d; e; end")
|
150
|
-
s2 = RubyParser.new.process("def x(n); a; c; e; end")
|
151
|
-
|
152
|
-
flay.process_sexp s1
|
153
|
-
flay.process_sexp s2
|
154
|
-
|
155
|
-
flay.process_fuzzy_similarities
|
156
|
-
|
157
|
-
b1 = s1.scope.block
|
158
|
-
b2 = s2.scope.block
|
159
|
-
|
160
|
-
assert_equal [b2, b1], flay.hashes[b2.hash]
|
161
|
-
end
|
162
|
-
|
163
|
-
def test_process_fuzzy_similarities_2
|
164
|
-
flay = Flay.new :mass => 7
|
165
|
-
|
166
|
-
s1 = RubyParser.new.process("def w(n); a; b; c; d; e; end")
|
167
|
-
s2 = RubyParser.new.process("def x(n); a; c; e; end")
|
168
|
-
s3 = RubyParser.new.process("def y(n); a; f; c; g; e; end")
|
169
|
-
|
170
|
-
flay.process_sexp s1
|
171
|
-
flay.process_sexp s2
|
172
|
-
flay.process_sexp s3
|
173
|
-
|
174
|
-
flay.process_fuzzy_similarities
|
175
|
-
|
176
|
-
b1 = s1.scope.block
|
177
|
-
b2 = s2.scope.block
|
178
|
-
b3 = s3.scope.block
|
179
|
-
|
180
|
-
assert_equal [b3, b2, b1], flay.hashes[b3.hash]
|
181
|
-
end
|
182
|
-
|
183
|
-
def test_process_fuzzy_similarities_3
|
184
|
-
flay = Flay.new :mass => 7
|
185
|
-
|
186
|
-
s1 = RubyParser.new.process("def w (n); a; b; c; d; e; end")
|
187
|
-
s2 = RubyParser.new.process("def x (n); a; c; e; end")
|
188
|
-
s3 = RubyParser.new.process("def y (n); a; f; c; g; e; end")
|
189
|
-
s4 = RubyParser.new.process("def z (n); f; g; h; i; j; end")
|
190
|
-
s5 = RubyParser.new.process("def w1(n); a; b if x; c; d if y; e; end")
|
191
|
-
|
192
|
-
flay.process_sexp s1
|
193
|
-
flay.process_sexp s2
|
194
|
-
flay.process_sexp s3
|
195
|
-
flay.process_sexp s4
|
196
|
-
flay.process_sexp s5
|
197
|
-
|
198
|
-
flay.process_fuzzy_similarities
|
199
|
-
|
200
|
-
b1 = s1.scope.block
|
201
|
-
b2 = s2.scope.block
|
202
|
-
b3 = s3.scope.block
|
203
|
-
b5 = s5.scope.block
|
204
|
-
|
205
|
-
assert_equal [b3, b5, b2, b1], flay.hashes[b3.hash]
|
206
|
-
end
|
207
148
|
end
|
208
149
|
|
209
150
|
class ArrayIntersectionTests < Test::Unit::TestCase
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Davis
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
FBHgymkyj/AOSqKRIpXPhjC6
|
31
31
|
-----END CERTIFICATE-----
|
32
32
|
|
33
|
-
date: 2009-
|
33
|
+
date: 2009-06-23 00:00:00 -07:00
|
34
34
|
default_executable:
|
35
35
|
dependencies:
|
36
36
|
- !ruby/object:Gem::Dependency
|
@@ -39,9 +39,9 @@ dependencies:
|
|
39
39
|
version_requirement:
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
requirements:
|
42
|
-
- -
|
42
|
+
- - ~>
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: 3.0
|
44
|
+
version: "3.0"
|
45
45
|
version:
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: ruby_parser
|
@@ -49,9 +49,9 @@ dependencies:
|
|
49
49
|
version_requirement:
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: "2.0"
|
55
55
|
version:
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: hoe
|
@@ -61,9 +61,12 @@ dependencies:
|
|
61
61
|
requirements:
|
62
62
|
- - ">="
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version:
|
64
|
+
version: 2.3.0
|
65
65
|
version:
|
66
|
-
description:
|
66
|
+
description: |-
|
67
|
+
Flay analyzes code for structural similarities. Differences in literal
|
68
|
+
values, variable, class, method names, whitespace, programming style,
|
69
|
+
braces vs do/end, etc are all ignored. Making this totally rad.
|
67
70
|
email:
|
68
71
|
- ryand-ruby@zenspider.com
|
69
72
|
executables:
|
@@ -87,6 +90,8 @@ files:
|
|
87
90
|
- test/test_flay.rb
|
88
91
|
has_rdoc: true
|
89
92
|
homepage: http://ruby.sadi.st/
|
93
|
+
licenses: []
|
94
|
+
|
90
95
|
post_install_message:
|
91
96
|
rdoc_options:
|
92
97
|
- --main
|
@@ -108,9 +113,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
113
|
requirements: []
|
109
114
|
|
110
115
|
rubyforge_project: seattlerb
|
111
|
-
rubygems_version: 1.3.
|
116
|
+
rubygems_version: 1.3.4
|
112
117
|
signing_key:
|
113
|
-
specification_version:
|
118
|
+
specification_version: 3
|
114
119
|
summary: Flay analyzes code for structural similarities
|
115
120
|
test_files:
|
116
121
|
- test/test_flay.rb
|
metadata.gz.sig
CHANGED
Binary file
|