flay 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2008-11-06
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/flay
6
+ lib/flay.rb
7
+ test/test_flay.rb
@@ -0,0 +1,75 @@
1
+ = flay
2
+
3
+ * http://ruby.sadi.st/
4
+ * http://rubyforge.org/projects/seattlerb
5
+
6
+ == DESCRIPTION:
7
+
8
+ Flay analyzes ruby code for structural similarities. Differences in
9
+ literal values, variable, class, method names, whitespace, programming
10
+ style, braces vs do/end, etc are all ignored. Making this totally rad.
11
+
12
+ == FEATURES/PROBLEMS:
13
+
14
+ * Differences in literal values, variable, class, and method names are ignored.
15
+ * Differences in whitespace, programming style, braces vs do/end, etc are ignored.
16
+ * Works across files.
17
+ * Reports differences at any level of code.
18
+
19
+ == TODO:
20
+
21
+ * Editor integration (emacs, textmate, other contributions welcome).
22
+ * N-way diff reporting... or... something. Not sure.
23
+ * UI improvement suggestions welcome. :)
24
+
25
+ == SYNOPSIS:
26
+
27
+ % flay lib/*.rb
28
+ Processing unit/itemconfig.rb...
29
+
30
+ Matches found in :when (mass = 572)
31
+ unit/itemconfig.rb:343
32
+ unit/itemconfig.rb:379
33
+ unit/itemconfig.rb:706
34
+ unit/itemconfig.rb:742
35
+
36
+ Matches found in :when (mass = 500)
37
+ unit/itemconfig.rb:509
38
+ unit/itemconfig.rb:539
39
+ unit/itemconfig.rb:875
40
+ unit/itemconfig.rb:905
41
+ ...
42
+
43
+ == REQUIREMENTS:
44
+
45
+ * ruby_parser
46
+ * sexp_processor
47
+
48
+ == INSTALL:
49
+
50
+ * sudo gem install flay
51
+
52
+ == LICENSE:
53
+
54
+ (The MIT License)
55
+
56
+ Copyright (c) 2008 Ryan Davis, Seattle.rb
57
+
58
+ Permission is hereby granted, free of charge, to any person obtaining
59
+ a copy of this software and associated documentation files (the
60
+ 'Software'), to deal in the Software without restriction, including
61
+ without limitation the rights to use, copy, modify, merge, publish,
62
+ distribute, sublicense, and/or sell copies of the Software, and to
63
+ permit persons to whom the Software is furnished to do so, subject to
64
+ the following conditions:
65
+
66
+ The above copyright notice and this permission notice shall be
67
+ included in all copies or substantial portions of the Software.
68
+
69
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
70
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
71
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
72
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
73
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
74
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
75
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe::add_include_dirs("../../sexp_processor/dev/lib",
7
+ "../../ruby_parser/dev/lib")
8
+
9
+ require './lib/flay.rb'
10
+
11
+ Hoe.new('flay', Flay::VERSION) do |flay|
12
+ flay.rubyforge_name = 'seattlerb'
13
+ flay.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
14
+
15
+ flay.extra_deps << ['sexp_processor', '>= 3.0.0']
16
+ flay.extra_deps << ['ruby_parser', '>= 1.1.0']
17
+ end
18
+
19
+ # vim: syntax=Ruby
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/ruby -s
2
+
3
+ $m ||= 16
4
+
5
+ require 'flay'
6
+
7
+ flay = Flay.new($m.to_i)
8
+ flay.process(*ARGV)
9
+ flay.report
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ $: << "../../sexp_processor/dev/lib" # TODO: remove
4
+ $: << "../../ruby_parser/dev/lib"
5
+
6
+ require 'rubygems'
7
+ require 'sexp_processor'
8
+ require 'ruby_parser'
9
+ require 'pp' # TODO: remove
10
+
11
+ class Flay
12
+ VERSION = '1.0.0'
13
+
14
+ attr_reader :hashes
15
+
16
+ def initialize(mass = 16)
17
+ @hashes = Hash.new { |h,k| h[k] = [] }
18
+ @mass_threshold = mass
19
+ end
20
+
21
+ def process(*files)
22
+ files.each do |file|
23
+ warn "Processing #{file}..."
24
+
25
+ t = Time.now
26
+ pt = RubyParser.new.process(File.read(file), file)
27
+
28
+ next unless pt # empty files... hahaha, suck.
29
+
30
+ t = Time.now
31
+ pt.deep_each do |node|
32
+ next unless node.any? { |sub| Sexp === sub }
33
+ next if node.mass < @mass_threshold
34
+
35
+ self.hashes[node.fuzzy_hash] << node
36
+ end
37
+ end
38
+ end
39
+
40
+ def prune
41
+ # prune trees that aren't duped at all, or are too small
42
+ self.hashes.delete_if { |_,nodes| nodes.size == 1 }
43
+
44
+ # extract all subtree hashes from all nodes
45
+ all_hashes = {}
46
+ self.hashes.values.each do |nodes|
47
+ nodes.each do |node|
48
+ node.all_subhashes.each do |h|
49
+ all_hashes[h] = true
50
+ end
51
+ end
52
+ end
53
+
54
+ # nuke subtrees so we show the biggest matching tree possible
55
+ self.hashes.delete_if { |h,_| all_hashes[h] }
56
+ end
57
+
58
+ def report prune = nil
59
+ self.prune
60
+
61
+ self.hashes.sort_by { |_,nodes|
62
+ -(nodes.first.mass * nodes.size)
63
+ }.each do |_,nodes|
64
+ next unless nodes.first.first == prune if prune
65
+ puts
66
+
67
+ node = nodes.first
68
+ puts "Matches found in %p (mass = %d)" %
69
+ [node.first, nodes.size * node.mass]
70
+
71
+ nodes.each do |node|
72
+ puts " #{node.file}:#{node.line}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ class Symbol
79
+ def hash
80
+ @hash ||= self.to_s.hash
81
+ end
82
+ end
83
+
84
+ class Sexp
85
+ def mass
86
+ @mass ||= self.structure.flatten.size
87
+ end
88
+
89
+ alias :uncached_structure :structure
90
+ def structure
91
+ @structure ||= self.uncached_structure
92
+ end
93
+
94
+ def similarity o
95
+ l, s, r = self.compare_to o
96
+ (2.0 * s) / (2.0 * s + l + r)
97
+ end
98
+
99
+ def compare_to they
100
+ l = s = r = 0
101
+
102
+ l_sexp, l_lits = self.partition { |o| Sexp === o }
103
+ r_sexp, r_lits = they.partition { |o| Sexp === o }
104
+
105
+ l += (l_lits - r_lits).size
106
+ s += (l_lits & r_lits).size
107
+ r += (r_lits - l_lits).size
108
+
109
+ # TODO: I think this is wrong, since it isn't positional. What to do?
110
+ l_sexp.zip(r_sexp).each do |l_sub, r_sub|
111
+ l2, s2, r2 = l_sub.compare_to r_sub
112
+ l += l2
113
+ s += s2
114
+ r += r2
115
+ end
116
+
117
+ return l, s, r
118
+ end
119
+
120
+ def fuzzy_hash
121
+ @fuzzy_hash ||= self.structure.hash
122
+ end
123
+
124
+ def all_subhashes
125
+ hashes = []
126
+ self.deep_each do |node|
127
+ hashes << node.fuzzy_hash
128
+ end
129
+ hashes
130
+ end
131
+
132
+ def deep_each(&block)
133
+ self.each_sexp do |sexp|
134
+ block[sexp]
135
+ sexp.deep_each(&block)
136
+ end
137
+ end
138
+
139
+ def each_sexp
140
+ self.each do |sexp|
141
+ next unless Sexp === sexp
142
+
143
+ yield sexp
144
+ end
145
+ end
146
+
147
+ alias :old_inspect :inspect
148
+ def inspect
149
+ old_inspect.sub(/\)\Z/, ":h_#{self.fuzzy_hash})")
150
+ end
151
+
152
+ alias :shut_up! :pretty_print
153
+ def pretty_print(q) # shows the hash TODO: remove
154
+ q.group(1, 'S(', ')') do
155
+ q.seplist(self + [":h_#{self.fuzzy_hash}"]) {|v| q.pp v }
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'test/unit'
4
+ require 'flay'
5
+
6
+ class SexpTest < Test::Unit::TestCase
7
+ def setup
8
+ # a(1) { |c| d }
9
+ @s = s(:iter,
10
+ s(:call, nil, :a, s(:arglist, s(:lit, 1))),
11
+ s(:lasgn, :c),
12
+ s(:call, nil, :d, s(:arglist)))
13
+ end
14
+
15
+ def test_mass
16
+ assert_equal 1, s(:a).mass
17
+ assert_equal 3, s(:a, s(:b), s(:c)).mass
18
+ assert_equal 7, @s.mass
19
+ end
20
+
21
+ def test_compare_to
22
+ s1 = s(:a, :b, :c)
23
+ s2 = s(:d, :e, :f)
24
+ assert_equal [3, 0, 3], s1.compare_to(s2), "100% different"
25
+
26
+ s1 = s(:a, :b, :c)
27
+ s2 = s(:a, :b, :c)
28
+ assert_equal [0, 3, 0], s1.compare_to(s2), "100% same"
29
+
30
+ s1 = s(:a, :b, :c, :d)
31
+ s2 = s(:a, :b, :c, :e)
32
+ assert_equal [1, 3, 1], s1.compare_to(s2), "1 element different on each"
33
+
34
+ s1 = s(:a, :d, :b, :c)
35
+ s2 = s(:a, :b, :c, :e)
36
+ assert_equal [1, 3, 1], s1.compare_to(s2), "positionally different"
37
+
38
+
39
+ s1 = s(:a, s(:d), :b, :c)
40
+ s2 = s(:a, :b, :c, s(:e))
41
+ assert_equal [1, 3, 1], s1.compare_to(s2), "simple subtree difference"
42
+ end
43
+
44
+ def test_fuzzy_hash
45
+ s = s(:iter,
46
+ s(:call, nil, :a, s(:arglist, s(:lit, 1))),
47
+ s(:lasgn, :c),
48
+ s(:call, nil, :d, s(:arglist)))
49
+
50
+ hash = 955256285
51
+
52
+ assert_equal hash, s.fuzzy_hash, "hand copy"
53
+ assert_equal hash, @s.fuzzy_hash, "ivar from setup"
54
+ assert_equal hash, @s.deep_clone.fuzzy_hash, "deep clone"
55
+ assert_equal hash, s.deep_clone.fuzzy_hash, "copy deep clone"
56
+ end
57
+
58
+ def test_all_subhashes
59
+ expected = [-704571402, -282578980, -35395725,
60
+ 160138040, 815971090, 927228382]
61
+
62
+ assert_equal expected, @s.all_subhashes.sort.uniq
63
+ assert ! @s.all_subhashes.include?(@s.fuzzy_hash)
64
+
65
+ x = []
66
+
67
+ @s.deep_each do |o|
68
+ x << o.fuzzy_hash
69
+ end
70
+
71
+ assert_equal expected, x.sort.uniq
72
+ end
73
+
74
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flay
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Davis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-06 00:00:00 -05:00
13
+ default_executable:
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.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: ruby_parser
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.8.2
44
+ version:
45
+ description: Flay analyzes ruby 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
+ email:
47
+ - ryand-ruby@zenspider.com
48
+ executables:
49
+ - flay
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/flay
62
+ - lib/flay.rb
63
+ - test/test_flay.rb
64
+ has_rdoc: true
65
+ homepage: http://ruby.sadi.st/
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --main
69
+ - README.txt
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project: seattlerb
87
+ rubygems_version: 1.3.1
88
+ signing_key:
89
+ specification_version: 2
90
+ summary: Flay analyzes ruby code for structural similarities
91
+ test_files:
92
+ - test/test_flay.rb