flay 1.1.0 → 1.2.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 +16 -4
- data/Manifest.txt +3 -1
- data/README.txt +8 -5
- data/Rakefile +2 -0
- data/bin/flay +7 -3
- data/lib/flay.rb +124 -32
- data/lib/flay_erb.rb +14 -0
- data/lib/flay_task.rb +31 -0
- data/test/test_flay.rb +5 -5
- metadata +29 -6
- metadata.gz.sig +1 -0
data.tar.gz.sig
ADDED
Binary file
|
data/History.txt
CHANGED
@@ -1,13 +1,26 @@
|
|
1
|
+
=== 1.2.0 / 2009-03-09
|
2
|
+
|
3
|
+
* 2 major enhancements:
|
4
|
+
|
5
|
+
* Added flay_task.rb
|
6
|
+
* Added plugin system (any flay_(c,java,js,etc).rb files).
|
7
|
+
|
8
|
+
* 4 minor enhancements:
|
9
|
+
|
10
|
+
* Added expand_dirs_to_files and made dirs valid arguments.
|
11
|
+
* Added flay_erb.rb plugin.
|
12
|
+
* Added optparse option processing.
|
13
|
+
* Refactored to make using w/in rake and other CI systems clean and easy.
|
14
|
+
|
1
15
|
=== 1.1.0 / 2009-01-20
|
2
16
|
|
3
|
-
*
|
17
|
+
* 7 minor enhancement:
|
4
18
|
|
5
19
|
* Added -v verbose mode to print out N-way diff of the detected code.
|
6
20
|
* Added identical node scoring and reporting.
|
7
|
-
* Added the start of copy/paste+edit detection, not even close yet
|
21
|
+
* Added the start of copy/paste+edit detection, not even close yet.
|
8
22
|
* Added more tests.
|
9
23
|
* Added rcov tasks
|
10
|
-
* Added the start of copy/paste+edit detection
|
11
24
|
* Clarified output a bit
|
12
25
|
* Refactored process_sexps to make doing other languages/systems easier.
|
13
26
|
|
@@ -16,4 +29,3 @@
|
|
16
29
|
* 1 major enhancement
|
17
30
|
|
18
31
|
* Birthday!
|
19
|
-
|
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -5,23 +5,26 @@
|
|
5
5
|
|
6
6
|
== DESCRIPTION:
|
7
7
|
|
8
|
-
Flay analyzes
|
9
|
-
|
10
|
-
|
8
|
+
Flay analyzes code for structural similarities. Differences in literal
|
9
|
+
values, variable, class, method names, whitespace, programming style,
|
10
|
+
braces vs do/end, etc are all ignored. Making this totally rad.
|
11
11
|
|
12
12
|
== FEATURES/PROBLEMS:
|
13
13
|
|
14
|
+
* Plugin system allows other languages to be flayed.
|
15
|
+
* Ships with .rb and .erb. javascript and others will be available separately.
|
16
|
+
* Includes FlayTask for Rakefiles.
|
14
17
|
* Differences in literal values, variable, class, and method names are ignored.
|
15
18
|
* Differences in whitespace, programming style, braces vs do/end, etc are ignored.
|
16
19
|
* Works across files.
|
17
20
|
* Reports differences at any level of code.
|
21
|
+
* Totally rad.
|
18
22
|
* Adds a score multiplier to identical nodes.
|
19
23
|
* Run verbose to see an N-way diff of the code.
|
20
24
|
|
21
25
|
== TODO:
|
22
26
|
|
23
27
|
* Editor integration (emacs, textmate, other contributions welcome).
|
24
|
-
* UI improvement suggestions welcome. :)
|
25
28
|
* Score sequence fragments (a;b;c;d;e) vs (b;c;d) etc.
|
26
29
|
|
27
30
|
== SYNOPSIS:
|
@@ -77,7 +80,7 @@ style, braces vs do/end, etc are all ignored. Making this totally rad.
|
|
77
80
|
|
78
81
|
(The MIT License)
|
79
82
|
|
80
|
-
Copyright (c) 2008 Ryan Davis, Seattle.rb
|
83
|
+
Copyright (c) 2008-2009 Ryan Davis, Seattle.rb
|
81
84
|
|
82
85
|
Permission is hereby granted, free of charge, to any person obtaining
|
83
86
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -12,6 +12,8 @@ Hoe.new('flay', Flay::VERSION) do |flay|
|
|
12
12
|
flay.rubyforge_name = 'seattlerb'
|
13
13
|
flay.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
|
14
14
|
|
15
|
+
flay.flay_threshold = 250
|
16
|
+
|
15
17
|
flay.extra_deps << ['sexp_processor', '>= 3.0.0']
|
16
18
|
flay.extra_deps << ['ruby_parser', '>= 1.1.0']
|
17
19
|
end
|
data/bin/flay
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
#!/usr/bin/ruby
|
1
|
+
#!/usr/bin/ruby
|
2
2
|
|
3
3
|
require 'flay'
|
4
4
|
|
5
|
-
flay = Flay.new
|
6
|
-
|
5
|
+
flay = Flay.new Flay.parse_options
|
6
|
+
|
7
|
+
ARGV << '.' if ARGV.empty?
|
8
|
+
files = Flay.expand_dirs_to_files(*ARGV)
|
9
|
+
|
10
|
+
flay.process(*files)
|
7
11
|
flay.report
|
data/lib/flay.rb
CHANGED
@@ -1,44 +1,144 @@
|
|
1
1
|
#!/usr/bin/env ruby -w
|
2
2
|
|
3
3
|
$: << "../../ruby_parser/dev/lib"
|
4
|
+
$: << "../../ruby2ruby/dev/lib"
|
4
5
|
|
6
|
+
require 'optparse'
|
5
7
|
require 'rubygems'
|
6
8
|
require 'sexp_processor'
|
7
9
|
require 'ruby_parser'
|
8
|
-
require 'pp' # TODO: remove
|
9
10
|
|
10
|
-
|
11
|
-
$v ||= false
|
12
|
-
$f ||= false
|
13
|
-
|
14
|
-
if $v then
|
15
|
-
$: << "../../ruby2ruby/dev/lib"
|
16
|
-
require 'ruby2ruby'
|
17
|
-
require 'tempfile'
|
18
|
-
end
|
11
|
+
abort "update rubygems to >= 1.3.1" unless Gem.respond_to? :find_files
|
19
12
|
|
20
13
|
class Flay
|
21
|
-
VERSION = '1.
|
14
|
+
VERSION = '1.2.0'
|
15
|
+
|
16
|
+
def self.default_options
|
17
|
+
{
|
18
|
+
:fuzzy => false,
|
19
|
+
:verbose => false,
|
20
|
+
:mass => 16,
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.parse_options
|
25
|
+
options = self.default_options
|
26
|
+
|
27
|
+
OptionParser.new do |opts|
|
28
|
+
opts.banner = 'flay [options] files_or_dirs'
|
29
|
+
opts.version = Flay::VERSION
|
30
|
+
|
31
|
+
opts.separator ""
|
32
|
+
opts.separator "Specific options:"
|
33
|
+
opts.separator ""
|
34
|
+
|
35
|
+
opts.on('-h', '--help', 'Display this help.') do
|
36
|
+
puts opts
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-f', '--fuzzy', "Attempt to do fuzzy similarities. (SLOW)") do
|
41
|
+
options[:fuzzy] = true
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-m', '--mass MASS', Integer, "Sets mass threshold") do |m|
|
45
|
+
options[:mass] = m.to_i
|
46
|
+
end
|
22
47
|
|
23
|
-
|
24
|
-
|
48
|
+
opts.on('-v', '--verbose', "Verbose. Display N-Way diff for ruby.") do
|
49
|
+
options[:verbose] = true
|
50
|
+
end
|
51
|
+
|
52
|
+
extensions = ['rb'] + Flay.load_plugins
|
53
|
+
|
54
|
+
opts.separator ""
|
55
|
+
opts.separator "Known extensions: #{extensions.join(', ')}"
|
56
|
+
end.parse!
|
57
|
+
|
58
|
+
options
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.expand_dirs_to_files *dirs
|
62
|
+
extensions = ['rb'] + Flay.load_plugins
|
63
|
+
|
64
|
+
dirs.flatten.map { |p|
|
65
|
+
if File.directory? p then
|
66
|
+
Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")]
|
67
|
+
else
|
68
|
+
p
|
69
|
+
end
|
70
|
+
}.flatten
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.load_plugins
|
74
|
+
plugins = Gem.find_files("flay_*.rb").reject { |path| path =~ /flay_task/ }
|
75
|
+
|
76
|
+
plugins.each do |plugin|
|
77
|
+
begin
|
78
|
+
load plugin
|
79
|
+
rescue LoadError => e
|
80
|
+
warn "error loading #{plugin.inspect}: #{e.message}. skipping..."
|
81
|
+
end
|
82
|
+
end
|
25
83
|
|
26
|
-
|
84
|
+
plugins.map { |f| File.basename(f, '.rb').sub(/^flay_/, '') }
|
85
|
+
end
|
86
|
+
|
87
|
+
attr_accessor :mass_threshold, :total, :identical, :masses
|
88
|
+
attr_reader :hashes, :option
|
89
|
+
|
90
|
+
def initialize option = nil
|
91
|
+
@option = option || Flay.default_options
|
27
92
|
@hashes = Hash.new { |h,k| h[k] = [] }
|
28
|
-
|
93
|
+
|
94
|
+
self.identical = {}
|
95
|
+
self.masses = {}
|
96
|
+
self.total = 0
|
97
|
+
self.mass_threshold = @option[:mass]
|
98
|
+
|
99
|
+
require 'ruby2ruby' if @option[:verbose]
|
29
100
|
end
|
30
101
|
|
31
102
|
def process(*files)
|
32
103
|
files.each do |file|
|
33
104
|
warn "Processing #{file}"
|
34
105
|
|
35
|
-
|
36
|
-
|
106
|
+
ext = File.extname(file).sub(/^\./, '')
|
107
|
+
ext = "rb" if ext.nil? || ext.empty?
|
108
|
+
msg = "process_#{ext}"
|
109
|
+
|
110
|
+
unless respond_to? msg then
|
111
|
+
warn " Unknown file type: #{ext}, defaulting to ruby"
|
112
|
+
msg = "process_rb"
|
113
|
+
end
|
114
|
+
|
115
|
+
sexp = begin
|
116
|
+
send msg, file
|
117
|
+
rescue => e
|
118
|
+
warn " #{e.message.strip}"
|
119
|
+
warn " skipping #{file}"
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
next unless sexp
|
37
124
|
|
38
|
-
process_sexp
|
125
|
+
process_sexp sexp
|
39
126
|
end
|
40
127
|
|
41
|
-
process_fuzzy_similarities if
|
128
|
+
process_fuzzy_similarities if option[:fuzzy]
|
129
|
+
|
130
|
+
self.prune
|
131
|
+
|
132
|
+
self.hashes.each do |hash,nodes|
|
133
|
+
identical[hash] = nodes[1..-1].all? { |n| n == nodes.first }
|
134
|
+
masses[hash] = nodes.first.mass * nodes.size
|
135
|
+
masses[hash] *= (nodes.size) if identical[hash]
|
136
|
+
self.total += masses[hash]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def process_rb file
|
141
|
+
RubyParser.new.process(File.read(file), file)
|
42
142
|
end
|
43
143
|
|
44
144
|
def process_sexp pt
|
@@ -136,19 +236,11 @@ class Flay
|
|
136
236
|
end
|
137
237
|
|
138
238
|
def report prune = nil
|
139
|
-
self.
|
140
|
-
|
141
|
-
identical = {}
|
142
|
-
masses = {}
|
143
|
-
|
144
|
-
self.hashes.each do |hash,nodes|
|
145
|
-
identical[hash] = nodes[1..-1].all? { |n| n == nodes.first }
|
146
|
-
masses[hash] = nodes.first.mass * nodes.size
|
147
|
-
masses[hash] *= (nodes.size) if identical[hash]
|
148
|
-
end
|
239
|
+
puts "Total score (lower is better) = #{self.total}"
|
240
|
+
puts
|
149
241
|
|
150
242
|
count = 0
|
151
|
-
masses.sort_by { |h,m| [-m, hashes[h].first.file] }.each do |hash,mass|
|
243
|
+
masses.sort_by { |h,m| [-m, hashes[h].first.file] }.each do |hash, mass|
|
152
244
|
nodes = hashes[hash]
|
153
245
|
next unless nodes.first.first == prune if prune
|
154
246
|
puts
|
@@ -167,7 +259,7 @@ class Flay
|
|
167
259
|
[count, match, node.first, bonus, mass]
|
168
260
|
|
169
261
|
nodes.each_with_index do |node, i|
|
170
|
-
if
|
262
|
+
if option[:verbose] then
|
171
263
|
c = (?A + i).chr
|
172
264
|
puts " #{c}: #{node.file}:#{node.line}"
|
173
265
|
else
|
@@ -175,7 +267,7 @@ class Flay
|
|
175
267
|
end
|
176
268
|
end
|
177
269
|
|
178
|
-
if
|
270
|
+
if option[:verbose] then
|
179
271
|
puts
|
180
272
|
r2r = Ruby2Ruby.new
|
181
273
|
puts n_way_diff(*nodes.map { |s| r2r.process(s.deep_clone) })
|
data/lib/flay_erb.rb
ADDED
data/lib/flay_task.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
class FlayTask < Rake::TaskLib
|
2
|
+
attr_accessor :name
|
3
|
+
attr_accessor :dirs
|
4
|
+
attr_accessor :threshold
|
5
|
+
attr_accessor :verbose
|
6
|
+
|
7
|
+
def initialize name = :flay, threshold = 200, dirs = nil
|
8
|
+
@name = name
|
9
|
+
@dirs = dirs || %w(app lib test spec)
|
10
|
+
@threshold = threshold
|
11
|
+
@verbose = Rake.application.options.trace
|
12
|
+
|
13
|
+
yield self if block_given?
|
14
|
+
|
15
|
+
@dirs.reject! { |f| ! File.directory? f }
|
16
|
+
|
17
|
+
define
|
18
|
+
end
|
19
|
+
|
20
|
+
def define
|
21
|
+
desc "Analyze for code duplication in: #{dirs.join(', ')}"
|
22
|
+
task name do
|
23
|
+
flay = Flay.new
|
24
|
+
flay.process(*Flay.expand_dirs_to_files(dirs))
|
25
|
+
flay.report if verbose
|
26
|
+
|
27
|
+
raise "Flay total too high! #{flay.total} > #{threshold}" if flay.total > threshold
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
data/test/test_flay.rb
CHANGED
@@ -107,7 +107,7 @@ class TestSexp < Test::Unit::TestCase
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def test_process_sexp_full
|
110
|
-
flay = Flay.new(1)
|
110
|
+
flay = Flay.new(:mass => 1)
|
111
111
|
|
112
112
|
s = RubyParser.new.process <<-RUBY
|
113
113
|
def x(n)
|
@@ -137,14 +137,14 @@ class TestSexp < Test::Unit::TestCase
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def test_process_sexp_no_structure
|
140
|
-
flay = Flay.new(1)
|
140
|
+
flay = Flay.new(:mass => 1)
|
141
141
|
flay.process_sexp s(:lit, 1)
|
142
142
|
|
143
143
|
assert flay.hashes.empty?
|
144
144
|
end
|
145
145
|
|
146
146
|
def test_process_fuzzy_similarities
|
147
|
-
flay = Flay.new 7
|
147
|
+
flay = Flay.new :mass => 7
|
148
148
|
|
149
149
|
s1 = RubyParser.new.process("def w(n); a; b; c; d; e; end")
|
150
150
|
s2 = RubyParser.new.process("def x(n); a; c; e; end")
|
@@ -161,7 +161,7 @@ class TestSexp < Test::Unit::TestCase
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def test_process_fuzzy_similarities_2
|
164
|
-
flay = Flay.new 7
|
164
|
+
flay = Flay.new :mass => 7
|
165
165
|
|
166
166
|
s1 = RubyParser.new.process("def w(n); a; b; c; d; e; end")
|
167
167
|
s2 = RubyParser.new.process("def x(n); a; c; e; end")
|
@@ -181,7 +181,7 @@ class TestSexp < Test::Unit::TestCase
|
|
181
181
|
end
|
182
182
|
|
183
183
|
def test_process_fuzzy_similarities_3
|
184
|
-
flay = Flay.new 7
|
184
|
+
flay = Flay.new :mass => 7
|
185
185
|
|
186
186
|
s1 = RubyParser.new.process("def w (n); a; b; c; d; e; end")
|
187
187
|
s2 = RubyParser.new.process("def x (n); a; c; e; end")
|
metadata
CHANGED
@@ -1,15 +1,36 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Davis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
|
-
cert_chain:
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDPjCCAiagAwIBAgIBADANBgkqhkiG9w0BAQUFADBFMRMwEQYDVQQDDApyeWFu
|
14
|
+
ZC1ydWJ5MRkwFwYKCZImiZPyLGQBGRYJemVuc3BpZGVyMRMwEQYKCZImiZPyLGQB
|
15
|
+
GRYDY29tMB4XDTA5MDMwNjE4NTMxNVoXDTEwMDMwNjE4NTMxNVowRTETMBEGA1UE
|
16
|
+
AwwKcnlhbmQtcnVieTEZMBcGCgmSJomT8ixkARkWCXplbnNwaWRlcjETMBEGCgmS
|
17
|
+
JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALda
|
18
|
+
b9DCgK+627gPJkB6XfjZ1itoOQvpqH1EXScSaba9/S2VF22VYQbXU1xQXL/WzCkx
|
19
|
+
taCPaLmfYIaFcHHCSY4hYDJijRQkLxPeB3xbOfzfLoBDbjvx5JxgJxUjmGa7xhcT
|
20
|
+
oOvjtt5P8+GSK9zLzxQP0gVLS/D0FmoE44XuDr3iQkVS2ujU5zZL84mMNqNB1znh
|
21
|
+
GiadM9GHRaDiaxuX0cIUBj19T01mVE2iymf9I6bEsiayK/n6QujtyCbTWsAS9Rqt
|
22
|
+
qhtV7HJxNKuPj/JFH0D2cswvzznE/a5FOYO68g+YCuFi5L8wZuuM8zzdwjrWHqSV
|
23
|
+
gBEfoTEGr7Zii72cx+sCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAw
|
24
|
+
HQYDVR0OBBYEFEfFe9md/r/tj/Wmwpy+MI8d9k/hMA0GCSqGSIb3DQEBBQUAA4IB
|
25
|
+
AQAY59gYvDxqSqgC92nAP9P8dnGgfZgLxP237xS6XxFGJSghdz/nI6pusfCWKM8m
|
26
|
+
vzjjH2wUMSSf3tNudQ3rCGLf2epkcU13/rguI88wO6MrE0wi4ZqLQX+eZQFskJb/
|
27
|
+
w6x9W1ur8eR01s397LSMexySDBrJOh34cm2AlfKr/jokKCTwcM0OvVZnAutaovC0
|
28
|
+
l1SVZ0ecg88bsWHA0Yhh7NFxK1utWoIhtB6AFC/+trM0FQEB/jZkIS8SaNzn96Rl
|
29
|
+
n0sZEf77FLf5peR8TP/PtmIg7Cyqz23sLM4mCOoTGIy5OcZ8TdyiyINUHtb5ej/T
|
30
|
+
FBHgymkyj/AOSqKRIpXPhjC6
|
31
|
+
-----END CERTIFICATE-----
|
11
32
|
|
12
|
-
date: 2009-
|
33
|
+
date: 2009-03-09 00:00:00 -07:00
|
13
34
|
default_executable:
|
14
35
|
dependencies:
|
15
36
|
- !ruby/object:Gem::Dependency
|
@@ -40,9 +61,9 @@ dependencies:
|
|
40
61
|
requirements:
|
41
62
|
- - ">="
|
42
63
|
- !ruby/object:Gem::Version
|
43
|
-
version: 1.
|
64
|
+
version: 1.10.0
|
44
65
|
version:
|
45
|
-
description: Flay analyzes
|
66
|
+
description: Flay analyzes code for structural similarities. Differences in literal values, variable, class, method names, whitespace, programming style, braces vs do/end, etc are all ignored. Making this totally rad.
|
46
67
|
email:
|
47
68
|
- ryand-ruby@zenspider.com
|
48
69
|
executables:
|
@@ -60,6 +81,8 @@ files:
|
|
60
81
|
- Rakefile
|
61
82
|
- bin/flay
|
62
83
|
- lib/flay.rb
|
84
|
+
- lib/flay_erb.rb
|
85
|
+
- lib/flay_task.rb
|
63
86
|
- test/test_flay.rb
|
64
87
|
has_rdoc: true
|
65
88
|
homepage: http://ruby.sadi.st/
|
@@ -87,6 +110,6 @@ rubyforge_project: seattlerb
|
|
87
110
|
rubygems_version: 1.3.1
|
88
111
|
signing_key:
|
89
112
|
specification_version: 2
|
90
|
-
summary: Flay analyzes
|
113
|
+
summary: Flay analyzes code for structural similarities
|
91
114
|
test_files:
|
92
115
|
- test/test_flay.rb
|
metadata.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
�B�쑊 �u�4`. �,�*s�O
|