ruby_diff 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|