ruby_diff 0.1
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/.gitignore +6 -0
- data/History.txt +2 -0
- data/Manifest.txt +19 -0
- data/README.txt +37 -0
- data/Rakefile +65 -0
- data/bin/ruby_diff +84 -0
- data/lib/ruby_diff.rb +20 -0
- data/lib/ruby_diff/code_comparison.rb +70 -0
- data/lib/ruby_diff/file_feeder.rb +28 -0
- data/lib/ruby_diff/git_feeder.rb +77 -0
- data/lib/ruby_diff/patterns.rb +29 -0
- data/lib/ruby_diff/structure_processor.rb +144 -0
- data/ruby_diff.gemspec +20 -0
- data/test/file_feeder_test.rb +37 -0
- data/test/git_feeder_test.rb +44 -0
- data/test/git_sample/README +1 -0
- data/test/git_sample/book.rb +5 -0
- data/test/git_sample/lib/chapter.rb +11 -0
- data/test/structure_processor_test.rb +75 -0
- metadata +87 -0
data/.gitignore
ADDED
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
.gitignore
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
bin/ruby_diff
|
7
|
+
lib/ruby_diff.rb
|
8
|
+
lib/ruby_diff/code_comparison.rb
|
9
|
+
lib/ruby_diff/file_feeder.rb
|
10
|
+
lib/ruby_diff/git_feeder.rb
|
11
|
+
lib/ruby_diff/patterns.rb
|
12
|
+
lib/ruby_diff/structure_processor.rb
|
13
|
+
ruby_diff.gemspec
|
14
|
+
test/file_feeder_test.rb
|
15
|
+
test/git_feeder_test.rb
|
16
|
+
test/git_sample/README
|
17
|
+
test/git_sample/book.rb
|
18
|
+
test/git_sample/lib/chapter.rb
|
19
|
+
test/structure_processor_test.rb
|
data/README.txt
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
= RubyDiff
|
2
|
+
|
3
|
+
RubyDiff does higher level comparisons of ruby code.
|
4
|
+
|
5
|
+
When looking at large sets of changes between versions of files, it is
|
6
|
+
important to get an idea of what classes, modules, and methods change.
|
7
|
+
RubyDiff aims to reveal substantive changes to code, and let you ignore
|
8
|
+
trivial changes like whitespace being added to a document.
|
9
|
+
|
10
|
+
Code and Releases:
|
11
|
+
http://rubyforge.org/projects/rubydiff
|
12
|
+
|
13
|
+
Browse or Branch:
|
14
|
+
http://github.com/adamsanderson/ruby_diff/tree/master
|
15
|
+
|
16
|
+
RDoc for internals (a bit sparse at the moment)
|
17
|
+
http://rubydiff.rubyforge.org/
|
18
|
+
|
19
|
+
== Requirements
|
20
|
+
RubyDiff depends on ParseTree, the easiest way to get it is via ruby gems:
|
21
|
+
gem install ParseTree
|
22
|
+
|
23
|
+
== Caveats
|
24
|
+
This is just the beginnings of an experiment, so all that is captured are
|
25
|
+
changes to instance methods. The API is likely to change drastically.
|
26
|
+
|
27
|
+
== Usage
|
28
|
+
This is likely to change a bunch, but for the moment:
|
29
|
+
ruby_diff old_file new_file
|
30
|
+
Or for git repositories, etc.
|
31
|
+
ruby_diff --git HEAD --file ./
|
32
|
+
|
33
|
+
See help for more information.
|
34
|
+
|
35
|
+
== Contact
|
36
|
+
Feel free to get in touch with me:
|
37
|
+
netghost@gmail.com
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
require './lib/ruby_diff.rb'
|
4
|
+
|
5
|
+
task :default => "git:changed"
|
6
|
+
task :release => "git:push"
|
7
|
+
|
8
|
+
HOE = Hoe.new('ruby_diff', RubyDiff::VERSION) do |p|
|
9
|
+
p.rubyforge_name = 'rubydiff'
|
10
|
+
p.developer('Adam Sanderson', 'netghost@gmail.com')
|
11
|
+
p.remote_rdoc_dir = '' # Release to root
|
12
|
+
p.test_globs = ['test/*_test.rb']
|
13
|
+
p.summary = "a higher level diff application for analyzing changes to ruby code"
|
14
|
+
p.extra_deps << ['ParseTree', '>= 2.0.2']
|
15
|
+
end
|
16
|
+
SPEC = HOE.spec
|
17
|
+
|
18
|
+
|
19
|
+
# Add some more dependencies to packaging:
|
20
|
+
task :package => ['git:update_manifest','git:update_manifest']
|
21
|
+
|
22
|
+
desc "Shows what has changed since the last commit"
|
23
|
+
namespace :git do
|
24
|
+
task :changed do |t|
|
25
|
+
_divider
|
26
|
+
puts "Changes since last commit:"
|
27
|
+
puts `./bin/ruby_diff --git HEAD --file ./`
|
28
|
+
_divider
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Updates the manifest to match the git repository"
|
32
|
+
task :update_manifest do |t|
|
33
|
+
`git-ls-files > Manifest.txt`
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Pushes git repository out"
|
37
|
+
task :push do |t|
|
38
|
+
['origin', 'rubyforge'].each do |source|
|
39
|
+
puts "Pushing to #{source}..."
|
40
|
+
puts `git push #{source}`
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Exports the gemspec for this project"
|
47
|
+
task :gemspec do |t|
|
48
|
+
open("ruby_diff.gemspec",'w'){|io| io << SPEC.to_ruby}
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :github do
|
52
|
+
desc "Simulates loading the Gem on GitHub"
|
53
|
+
task :simulate_gem => :gemspec do |t|
|
54
|
+
require 'rubygems/specification'
|
55
|
+
data = File.read('ruby_diff.gemspec')
|
56
|
+
spec = nil
|
57
|
+
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
58
|
+
puts spec
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def _divider
|
64
|
+
puts "-"*(ENV['COLUMNS'] || 80).to_i
|
65
|
+
end
|
data/bin/ruby_diff
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ruby_diff'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
@options = {}
|
7
|
+
|
8
|
+
feeder_mapping = {
|
9
|
+
:file => FileFeeder,
|
10
|
+
:git => GitFeeder
|
11
|
+
}
|
12
|
+
|
13
|
+
feeders = []
|
14
|
+
|
15
|
+
opts = OptionParser.new
|
16
|
+
opts.banner = "Usage: ruby_diff.rb [options]"
|
17
|
+
|
18
|
+
opts.separator ""
|
19
|
+
opts.separator "Specific options:"
|
20
|
+
|
21
|
+
opts.define_head <<-HEAD
|
22
|
+
|
23
|
+
Examples:
|
24
|
+
Changes between git HEAD and current file system:
|
25
|
+
ruby_diff --git HEAD --file ./
|
26
|
+
|
27
|
+
Changes between HEAD and previous revision:
|
28
|
+
ruby_diff --git HEAD~1 --git HEAD
|
29
|
+
HEAD
|
30
|
+
|
31
|
+
opts.on('--sexp', "Show the s expressions for each input"){
|
32
|
+
@options[:sexp] = true
|
33
|
+
}
|
34
|
+
|
35
|
+
opts.on('--git PATH', "Use a git repository as a code source"){|path|
|
36
|
+
feeders << feeder_mapping[:git].new(path)
|
37
|
+
}
|
38
|
+
opts.on('--file PATH', "Use a file system path as a code source"){|path|
|
39
|
+
feeders << feeder_mapping[:file].new(path)
|
40
|
+
}
|
41
|
+
|
42
|
+
opts.on_tail('-v', '--version') { puts "ruby_diff v:0" ; exit }
|
43
|
+
opts.on_tail('-h', '--help') { puts opts; exit }
|
44
|
+
|
45
|
+
opts.parse!(ARGV)
|
46
|
+
|
47
|
+
# Map remaining options as file feeders
|
48
|
+
ARGV.each do |path|
|
49
|
+
feeders << feeder_mapping[:file].new(path)
|
50
|
+
end
|
51
|
+
|
52
|
+
if feeders.length > 2
|
53
|
+
puts opts
|
54
|
+
puts "Too many code sources (#{feeders.length})"
|
55
|
+
exit 1
|
56
|
+
elsif feeders.length < 2
|
57
|
+
puts opts
|
58
|
+
puts "Must supply least 2 code sources (#{feeders.length})"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
|
62
|
+
processors = feeders.map do |feeder|
|
63
|
+
puts "#{feeder.class}: #{feeder.path}" if @options[:sexp]
|
64
|
+
processor = StructureProcessor.new
|
65
|
+
feeder.each do |code|
|
66
|
+
sexp = ParseTree.new.parse_tree_for_string(code)
|
67
|
+
if @options[:sexp]
|
68
|
+
pp sexp
|
69
|
+
puts "--"
|
70
|
+
end
|
71
|
+
|
72
|
+
processor.process(*sexp)
|
73
|
+
end
|
74
|
+
|
75
|
+
processor
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
old_processor, new_processor = *processors
|
80
|
+
|
81
|
+
changes = old_processor.diff(new_processor).sort_by{|c| c.signature}
|
82
|
+
changes.each do |change|
|
83
|
+
puts change.to_s
|
84
|
+
end
|
data/lib/ruby_diff.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
# ParseTree
|
4
|
+
require 'parse_tree'
|
5
|
+
require 'sexp_processor'
|
6
|
+
require 'unified_ruby'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
# Standard library
|
10
|
+
require 'set'
|
11
|
+
require 'pp'
|
12
|
+
|
13
|
+
module RubyDiff
|
14
|
+
VERSION = "0.1"
|
15
|
+
end
|
16
|
+
|
17
|
+
# RubyDiff
|
18
|
+
%w(code_comparison structure_processor file_feeder git_feeder).each do |name|
|
19
|
+
require File.expand_path(File.dirname(__FILE__) + "/ruby_diff/#{name}")
|
20
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class CodeChange
|
2
|
+
attr_reader :signature
|
3
|
+
attr_reader :operation
|
4
|
+
attr_reader :changes
|
5
|
+
|
6
|
+
OPERATION_CHARS = {
|
7
|
+
:added => "+",
|
8
|
+
:removed => "-",
|
9
|
+
:changed => "c",
|
10
|
+
:moved => "m" #Not used yet
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(signature, operation, changes=[])
|
14
|
+
@signature = signature
|
15
|
+
@operation = operation
|
16
|
+
@changes = changes
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s(depth=1)
|
20
|
+
this_change = "#{OPERATION_CHARS[self.operation]}#{" "*depth}#{signature}"
|
21
|
+
sub_changes = changes.map{|c| c.to_s(depth+1)}
|
22
|
+
[this_change, sub_changes].flatten.join("\n")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class CodeComparison
|
27
|
+
def initialize(old_signatures, new_signatures)
|
28
|
+
@old = old_signatures
|
29
|
+
@new = new_signatures
|
30
|
+
end
|
31
|
+
|
32
|
+
def changes
|
33
|
+
changes = []
|
34
|
+
seen = Set.new
|
35
|
+
@old.each do |sig, code_object|
|
36
|
+
seen << sig
|
37
|
+
if other_code_object = @new[sig]
|
38
|
+
if code_object != other_code_object
|
39
|
+
changes << changed(sig, code_object, sig, other_code_object)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
changes << removal(sig, code_object)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@new.each do |sig, code_object|
|
47
|
+
if !seen.include?(sig)
|
48
|
+
changes << addition(sig, code_object)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
changes
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def addition(sig, code_object)
|
57
|
+
CodeChange.new(sig, :added)
|
58
|
+
end
|
59
|
+
|
60
|
+
def removal(sig, code_object)
|
61
|
+
CodeChange.new(sig, :removed)
|
62
|
+
end
|
63
|
+
|
64
|
+
def changed(old_sig, old_code_object, new_sig, new_code_object)
|
65
|
+
CodeChange.new(old_sig, :changed, CodeComparison.new(
|
66
|
+
old_code_object.child_signatures,
|
67
|
+
new_code_object.child_signatures
|
68
|
+
).changes)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class FileFeeder
|
2
|
+
attr_accessor :files
|
3
|
+
attr_accessor :path
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
# Expects something in the form of PATH
|
8
|
+
# --file [PATH]
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
|
12
|
+
if path =~ /^\s+$/
|
13
|
+
@file_pattern = "**/*.rb"
|
14
|
+
elsif File.file? path
|
15
|
+
@file_pattern = path
|
16
|
+
else
|
17
|
+
@file_pattern = File.join(path, "**/*.rb")
|
18
|
+
end
|
19
|
+
@files = Dir[@file_pattern]
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
@files.each do |file|
|
24
|
+
yield(open(file, 'r'){|io| io.read})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
GitFile = Struct.new :access, :type, :hash, :name
|
2
|
+
class GitFeeder
|
3
|
+
attr_accessor :files
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# Expects something in the form of REV:PATH
|
9
|
+
# --git REV:[PATH]
|
10
|
+
def initialize(path)
|
11
|
+
@path = path
|
12
|
+
|
13
|
+
rev,path = path.split(":",2)
|
14
|
+
raise ArgumentError.new("Must supply a git revision") unless rev
|
15
|
+
path = File.expand_path(path) if path
|
16
|
+
init_git(path || '.')
|
17
|
+
@file_pattern = if @search_path == ''
|
18
|
+
"**.rb"
|
19
|
+
elsif @search_path =~ /\.rb#{File::SEPARATOR}$/
|
20
|
+
# So appending each piece into the search path during init_git
|
21
|
+
# causes the search path to always end with a /
|
22
|
+
@search_path[0...-1]
|
23
|
+
else
|
24
|
+
File.join(@search_path,"**.rb")
|
25
|
+
end
|
26
|
+
|
27
|
+
@files = []
|
28
|
+
|
29
|
+
FileUtils.cd(@working_dir) do
|
30
|
+
git_list = git "git-ls-tree -r #{rev}"
|
31
|
+
git_list.each_line do |line|
|
32
|
+
file = GitFile.new(*line.chomp.split(/\s+/,4))
|
33
|
+
|
34
|
+
if file.type == 'blob' and File.fnmatch(@file_pattern, file.name)
|
35
|
+
@files << file
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def each
|
43
|
+
FileUtils.cd(@working_dir) do
|
44
|
+
@files.each do |file|
|
45
|
+
code = git "git-show #{file.hash}"
|
46
|
+
yield(code)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def init_git(path, search_path='')
|
52
|
+
if File.exist?(File.join(path, ".git"))
|
53
|
+
# If this is the git repository
|
54
|
+
@working_dir = path
|
55
|
+
@search_path = search_path
|
56
|
+
|
57
|
+
else
|
58
|
+
next_search = File.join( File.split(path).last, search_path )
|
59
|
+
next_path = File.dirname(path)
|
60
|
+
|
61
|
+
if next_path == path # We have reached the root, and can go no further
|
62
|
+
raise "Could not find a git working directory"
|
63
|
+
else
|
64
|
+
init_git(next_path, next_search)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def git command
|
70
|
+
output = `#{command} 2>&1`.chomp
|
71
|
+
unless $?.success?
|
72
|
+
raise RuntimeError, output
|
73
|
+
end
|
74
|
+
output
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class SexpWildCard < SexpMatchSpecial
|
2
|
+
def === (o)
|
3
|
+
return true
|
4
|
+
end
|
5
|
+
|
6
|
+
def == (o)
|
7
|
+
return true
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def _?
|
13
|
+
SexpWildCard.new
|
14
|
+
end
|
15
|
+
|
16
|
+
class Sexp
|
17
|
+
def match(pattern, &block)
|
18
|
+
if pattern == self
|
19
|
+
block.call(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
self.each do |subset|
|
23
|
+
case subset
|
24
|
+
when Sexp then
|
25
|
+
subset.match(pattern, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
class CodeObject
|
2
|
+
attr_reader :name
|
3
|
+
attr_reader :parent
|
4
|
+
attr_reader :children
|
5
|
+
attr_reader :sexp
|
6
|
+
|
7
|
+
def initialize(name, parent, sexp)
|
8
|
+
@name = name
|
9
|
+
@parent = parent
|
10
|
+
@sexp = sexp.deep_clone
|
11
|
+
@children = []
|
12
|
+
if parent
|
13
|
+
parent.children << self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def signature
|
18
|
+
self.name
|
19
|
+
end
|
20
|
+
|
21
|
+
def child_signatures
|
22
|
+
h = {}
|
23
|
+
self.children.each{|c| h[c.signature] = c}
|
24
|
+
h
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
signature
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
return false unless other.class == self.class
|
33
|
+
(other.signature == self.signature) and (other.sexp == self.sexp)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ModuleCode < CodeObject
|
38
|
+
def signature
|
39
|
+
parent_signature = self.parent ? self.parent.signature : nil
|
40
|
+
[parent_signature,self.name].compact.join('::')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ClassCode < ModuleCode
|
45
|
+
end
|
46
|
+
|
47
|
+
class MethodCode < CodeObject
|
48
|
+
def initialize(name, parent, instance, sexp)
|
49
|
+
super(name, parent, sexp)
|
50
|
+
@instance = instance
|
51
|
+
end
|
52
|
+
|
53
|
+
def signature
|
54
|
+
parent_signature = self.parent ? self.parent.signature : ""
|
55
|
+
[parent_signature,self.name].join( @instance ? '#' : ".")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class StructureProcessor < SexpProcessor
|
60
|
+
attr_accessor :code_objects
|
61
|
+
attr_accessor :root_objects
|
62
|
+
|
63
|
+
attr_accessor :scope_stack
|
64
|
+
|
65
|
+
def initialize
|
66
|
+
super
|
67
|
+
self.strict = false
|
68
|
+
self.auto_shift_type = true
|
69
|
+
|
70
|
+
@instance_scope = true
|
71
|
+
@code_objects = {}
|
72
|
+
@root_objects = {}
|
73
|
+
@scope_stack = []
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_class(exp)
|
77
|
+
name = exp.shift
|
78
|
+
super_class = exp.shift #?
|
79
|
+
body = exp.shift
|
80
|
+
|
81
|
+
record ClassCode.new(name, self.scope, body) do
|
82
|
+
s(:class, name, process(super_class), process(body))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def process_module(exp)
|
87
|
+
name = exp.shift
|
88
|
+
body = exp.shift
|
89
|
+
|
90
|
+
record ModuleCode.new(name, self.scope, body) do
|
91
|
+
s(:class, name, process(body))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def process_defn(exp)
|
96
|
+
name = exp.shift
|
97
|
+
body = process exp.shift
|
98
|
+
|
99
|
+
record MethodCode.new(name, self.scope, @instance_scope, body) do
|
100
|
+
s(:defn, name, body)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def process_defs(exp)
|
105
|
+
exp_scope = process exp.shift
|
106
|
+
name = exp.shift
|
107
|
+
body = process exp.shift
|
108
|
+
|
109
|
+
record MethodCode.new(name, self.scope, false, body) do
|
110
|
+
s(:defs, exp_scope, name, body)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def process_sclass(exp)
|
115
|
+
@instance_scope = false
|
116
|
+
exp_scope = process exp.shift
|
117
|
+
body = process exp.shift
|
118
|
+
@instance_scope = true
|
119
|
+
|
120
|
+
s(:sclass, exp_scope, body)
|
121
|
+
end
|
122
|
+
|
123
|
+
def diff(other_processor)
|
124
|
+
method_diff = CodeComparison.new(self.root_objects, other_processor.root_objects).changes
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
def record obj
|
129
|
+
signature = obj.signature
|
130
|
+
if !self.code_objects[signature]
|
131
|
+
self.code_objects[signature] = obj
|
132
|
+
self.root_objects[signature] = obj if obj.parent == nil
|
133
|
+
end
|
134
|
+
|
135
|
+
self.scope_stack << self.code_objects[signature]
|
136
|
+
result = yield if block_given?
|
137
|
+
self.scope_stack.pop
|
138
|
+
result
|
139
|
+
end
|
140
|
+
|
141
|
+
def scope
|
142
|
+
self.scope_stack.last
|
143
|
+
end
|
144
|
+
end
|
data/ruby_diff.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{ruby_diff}
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.date = %q{2008-06-03}
|
5
|
+
s.summary = %q{a higher level diff application for analyzing changes to ruby code}
|
6
|
+
s.email = ["netghost@gmail.com"]
|
7
|
+
s.homepage = %q{RubyDiff does higher level comparisons of ruby code.}
|
8
|
+
s.rubyforge_project = %q{rubydiff}
|
9
|
+
s.description = %q{}
|
10
|
+
s.default_executable = %q{ruby_diff}
|
11
|
+
s.has_rdoc = true
|
12
|
+
s.authors = ["Adam Sanderson"]
|
13
|
+
s.files = [".gitignore", "README.txt", "Rakefile", "bin/ruby_diff", "lib/ruby_diff.rb", "lib/ruby_diff/code_comparison.rb", "lib/ruby_diff/file_feeder.rb", "lib/ruby_diff/git_feeder.rb", "lib/ruby_diff/patterns.rb", "lib/ruby_diff/structure_processor.rb", "ruby_diff.gemspec", "test/file_feeder_test.rb", "test/git_feeder_test.rb", "test/git_sample/README", "test/git_sample/book.rb", "test/git_sample/lib/chapter.rb", "test/structure_processor_test.rb"]
|
14
|
+
s.test_files = ["test/git_feeder_test.rb", "test/file_feeder_test.rb", "test/structure_processor_test.rb"]
|
15
|
+
s.rdoc_options = ["--main", "README.txt"]
|
16
|
+
s.extra_rdoc_files = ["README.txt"]
|
17
|
+
s.executables = ["ruby_diff"]
|
18
|
+
s.add_dependency(%q<ParseTree>, [">= 2.0.2"])
|
19
|
+
s.add_dependency(%q<hoe>, [">= 1.5.3"])
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/testcase'
|
3
|
+
require "ruby_diff"
|
4
|
+
|
5
|
+
DIR = File.join(File.dirname(__FILE__), "git_sample")
|
6
|
+
|
7
|
+
class FileFeederTestCase < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_find_all_files
|
10
|
+
assert File.exist?(DIR)
|
11
|
+
feeder = FileFeeder.new DIR
|
12
|
+
assert_equal 2, feeder.files.length
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_find_single_file
|
16
|
+
assert File.exist?(DIR)
|
17
|
+
feeder = FileFeeder.new(File.join(DIR,'lib','chapter.rb'))
|
18
|
+
assert_equal 1, feeder.files.length
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_find_files_in_sub_dir
|
22
|
+
feeder = FileFeeder.new(File.join(DIR,'lib'))
|
23
|
+
assert_equal 1, feeder.files.length
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_files_are_suitable_for_processing
|
27
|
+
feeder = FileFeeder.new DIR
|
28
|
+
assert_nothing_raised do
|
29
|
+
sexps = feeder.map{|code| ParseTree.new.parse_tree_for_string(code)}
|
30
|
+
|
31
|
+
sexps.each do |sexp|
|
32
|
+
assert sexp.length > 0, "Parsed code should not be empty"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/testcase'
|
3
|
+
require "ruby_diff"
|
4
|
+
|
5
|
+
GIT_REPO = File.join(File.dirname(__FILE__), "git_sample")
|
6
|
+
|
7
|
+
class GitFeederTestCase < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_find_all_files
|
10
|
+
assert File.exist?(GIT_REPO)
|
11
|
+
feeder = GitFeeder.new "HEAD:#{GIT_REPO}"
|
12
|
+
assert_equal 2, feeder.files.length
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_find_single_file
|
16
|
+
assert File.exist?(GIT_REPO)
|
17
|
+
feeder = GitFeeder.new(File.join("HEAD:#{GIT_REPO}",'lib','chapter.rb'))
|
18
|
+
assert_equal 1, feeder.files.length
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_find_files_in_sub_dir
|
22
|
+
feeder = GitFeeder.new(File.join("HEAD:#{GIT_REPO}",'lib'))
|
23
|
+
assert_equal 1, feeder.files.length
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_files_are_suitable_for_processing
|
27
|
+
feeder = GitFeeder.new "HEAD:#{GIT_REPO}"
|
28
|
+
assert_nothing_raised do
|
29
|
+
sexps = feeder.map{|code| ParseTree.new.parse_tree_for_string(code)}
|
30
|
+
|
31
|
+
sexps.each do |sexp|
|
32
|
+
assert sexp.length > 0, "Parsed code should not be empty"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_revsision_required
|
38
|
+
assert_raise RuntimeError do
|
39
|
+
GitFeeder.new "hello"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
This is just a fake git repository
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/testcase'
|
3
|
+
require "ruby_diff"
|
4
|
+
|
5
|
+
class StructureProcessorTestCase < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_instance_method
|
11
|
+
assert_signatures <<-CODE, ["A#method_1"]
|
12
|
+
class A
|
13
|
+
def method_1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
CODE
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_instance_method_with_module
|
20
|
+
assert_signatures <<-CODE, ["B::A#method_1"]
|
21
|
+
module B
|
22
|
+
class A
|
23
|
+
def method_1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
CODE
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_unscoped_method
|
31
|
+
assert_signatures <<-CODE, ["#method_1"]
|
32
|
+
def method_1
|
33
|
+
end
|
34
|
+
CODE
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_class_method_self
|
38
|
+
assert_signatures <<-CODE, ["A.method_1"]
|
39
|
+
class A
|
40
|
+
def self.method_1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
CODE
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_class_method_self_explicit
|
47
|
+
assert_signatures <<-CODE, ["A.method_1"]
|
48
|
+
class A
|
49
|
+
def A.method_1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
CODE
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_class_method_append_self
|
56
|
+
assert_signatures <<-CODE, ["A.method_1"]
|
57
|
+
class A
|
58
|
+
class << self
|
59
|
+
def method_1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
CODE
|
64
|
+
end
|
65
|
+
|
66
|
+
def assert_signatures(code, signatures, types=[MethodCode])
|
67
|
+
sexp = ParseTree.new.parse_tree_for_string(code)
|
68
|
+
processor = StructureProcessor.new()
|
69
|
+
processor.process(* sexp)
|
70
|
+
|
71
|
+
found_signatures = processor.code_objects.values.select{|co| types.include?(co.class)}.map{|key| key.signature }.sort
|
72
|
+
assert_equal signatures.sort, found_signatures
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: ruby_diff
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.1"
|
7
|
+
date: 2008-06-04 00:00:00 -07:00
|
8
|
+
summary: a higher level diff application for analyzing changes to ruby code
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email:
|
12
|
+
- netghost@gmail.com
|
13
|
+
homepage: RubyDiff does higher level comparisons of ruby code.
|
14
|
+
rubyforge_project: rubydiff
|
15
|
+
description: ""
|
16
|
+
autorequire:
|
17
|
+
default_executable:
|
18
|
+
bindir: bin
|
19
|
+
has_rdoc: true
|
20
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
signing_key:
|
28
|
+
cert_chain:
|
29
|
+
post_install_message:
|
30
|
+
authors:
|
31
|
+
- Adam Sanderson
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- History.txt
|
35
|
+
- Manifest.txt
|
36
|
+
- README.txt
|
37
|
+
- Rakefile
|
38
|
+
- bin/ruby_diff
|
39
|
+
- lib/ruby_diff.rb
|
40
|
+
- lib/ruby_diff/code_comparison.rb
|
41
|
+
- lib/ruby_diff/file_feeder.rb
|
42
|
+
- lib/ruby_diff/git_feeder.rb
|
43
|
+
- lib/ruby_diff/patterns.rb
|
44
|
+
- lib/ruby_diff/structure_processor.rb
|
45
|
+
- ruby_diff.gemspec
|
46
|
+
- test/file_feeder_test.rb
|
47
|
+
- test/git_feeder_test.rb
|
48
|
+
- test/git_sample/README
|
49
|
+
- test/git_sample/book.rb
|
50
|
+
- test/git_sample/lib/chapter.rb
|
51
|
+
- test/structure_processor_test.rb
|
52
|
+
test_files:
|
53
|
+
- test/git_feeder_test.rb
|
54
|
+
- test/file_feeder_test.rb
|
55
|
+
- test/structure_processor_test.rb
|
56
|
+
rdoc_options:
|
57
|
+
- --main
|
58
|
+
- README.txt
|
59
|
+
extra_rdoc_files:
|
60
|
+
- History.txt
|
61
|
+
- Manifest.txt
|
62
|
+
- README.txt
|
63
|
+
executables:
|
64
|
+
- ruby_diff
|
65
|
+
extensions: []
|
66
|
+
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
dependencies:
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: ParseTree
|
72
|
+
version_requirement:
|
73
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.0.2
|
78
|
+
version:
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: hoe
|
81
|
+
version_requirement:
|
82
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.5.3
|
87
|
+
version:
|