lorax 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG.rdoc +6 -0
- data/LICENSE +20 -0
- data/Manifest.txt +37 -0
- data/README.rdoc +70 -0
- data/Rakefile +50 -0
- data/TODO +40 -0
- data/bin/lorax +15 -0
- data/lib/lorax.rb +35 -0
- data/lib/lorax/delta.rb +28 -0
- data/lib/lorax/delta/delete_delta.rb +19 -0
- data/lib/lorax/delta/insert_delta.rb +22 -0
- data/lib/lorax/delta/modify_delta.rb +51 -0
- data/lib/lorax/delta_set.rb +24 -0
- data/lib/lorax/delta_set_generator.rb +36 -0
- data/lib/lorax/fast_matcher.rb +108 -0
- data/lib/lorax/match.rb +22 -0
- data/lib/lorax/match_set.rb +30 -0
- data/lib/lorax/signature.rb +101 -0
- data/spec/fast_matcher_spec.rb +400 -0
- data/spec/files/Michael-Dalessio-200909.html +147 -0
- data/spec/files/Michael-Dalessio-201001.html +153 -0
- data/spec/files/slashdot-1.html +3236 -0
- data/spec/files/slashdot-2.html +3216 -0
- data/spec/files/slashdot-3.html +3228 -0
- data/spec/files/slashdot-4.html +3278 -0
- data/spec/integration/lorax_spec.rb +130 -0
- data/spec/match_spec.rb +54 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/unit/delta/delete_delta_spec.rb +50 -0
- data/spec/unit/delta/insert_delta_spec.rb +109 -0
- data/spec/unit/delta/modify_delta_spec.rb +94 -0
- data/spec/unit/delta_set_generator_spec.rb +157 -0
- data/spec/unit/delta_set_spec.rb +40 -0
- data/spec/unit/lorax_spec.rb +9 -0
- data/spec/unit/match_set_spec.rb +93 -0
- data/spec/unit/signature_spec.rb +473 -0
- metadata +216 -0
- metadata.gz.sig +3 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Lorax do
|
4
|
+
def round_trip_should_succeed(doc1, doc2)
|
5
|
+
delta_set = Lorax.diff(doc1, doc2)
|
6
|
+
new_doc = delta_set.apply(doc1)
|
7
|
+
|
8
|
+
unless Lorax::Signature.new(new_doc.root).signature == Lorax::Signature.new(doc2.root).signature
|
9
|
+
errmsg = []
|
10
|
+
errmsg << "Documents are not identical after a round-trip diff and patch:"
|
11
|
+
errmsg << doc1.root.to_xml
|
12
|
+
errmsg << "-----"
|
13
|
+
errmsg << doc2.root.to_xml
|
14
|
+
errmsg << "=> patch: #{delta_set.deltas.inspect}"
|
15
|
+
errmsg << new_doc.root.to_xml
|
16
|
+
fail errmsg.join("\n")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "inserted nodes" do
|
21
|
+
it "handles appends to matching siblings" do
|
22
|
+
doc1 = xml { root {
|
23
|
+
a1 "hello"
|
24
|
+
} }
|
25
|
+
doc2 = xml { root {
|
26
|
+
a1 "hello"
|
27
|
+
a2 "goodbye"
|
28
|
+
} }
|
29
|
+
round_trip_should_succeed doc1, doc2
|
30
|
+
end
|
31
|
+
|
32
|
+
it "inserts into matching siblings" do
|
33
|
+
doc1 = xml { root {
|
34
|
+
a1 "hello"
|
35
|
+
a3 "goodbye"
|
36
|
+
} }
|
37
|
+
doc2 = xml { root {
|
38
|
+
a1 "hello"
|
39
|
+
a2
|
40
|
+
a3 "goodbye"
|
41
|
+
} }
|
42
|
+
round_trip_should_succeed doc1, doc2
|
43
|
+
end
|
44
|
+
|
45
|
+
it "inserts under an existing sibling node" do
|
46
|
+
doc1 = xml { root {
|
47
|
+
a1 "hello"
|
48
|
+
a2
|
49
|
+
} }
|
50
|
+
doc2 = xml { root {
|
51
|
+
a1 "hello"
|
52
|
+
a2 { b1 "subnode" }
|
53
|
+
} }
|
54
|
+
round_trip_should_succeed doc1, doc2
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "deleted nodes" do
|
59
|
+
it "handles deleting nodes" do
|
60
|
+
doc1 = xml { root {
|
61
|
+
a1 "hello"
|
62
|
+
a2 "goodbye"
|
63
|
+
a3 "natch"
|
64
|
+
} }
|
65
|
+
doc2 = xml { root {
|
66
|
+
a1 "hello"
|
67
|
+
a3 "natch"
|
68
|
+
} }
|
69
|
+
round_trip_should_succeed doc1, doc2
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "modified nodes" do
|
74
|
+
it "handles modifying nodes" do
|
75
|
+
doc1 = xml { root {
|
76
|
+
a1 "hello"
|
77
|
+
a2 "goodbye"
|
78
|
+
a3 "natch"
|
79
|
+
} }
|
80
|
+
doc2 = xml { root {
|
81
|
+
a1 "hello"
|
82
|
+
a2 "good buy"
|
83
|
+
a3 "natch"
|
84
|
+
} }
|
85
|
+
round_trip_should_succeed doc1, doc2
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "mixed operations" do
|
90
|
+
it "handles mixed deletions and modifications" do
|
91
|
+
doc1 = xml { root {
|
92
|
+
a1 "hello"
|
93
|
+
a2 "goodbye"
|
94
|
+
a3 "natch"
|
95
|
+
a4 "jimmy"
|
96
|
+
} }
|
97
|
+
doc2 = xml { root {
|
98
|
+
a1 "hello"
|
99
|
+
a3 "not"
|
100
|
+
a4 "jimmy"
|
101
|
+
} }
|
102
|
+
round_trip_should_succeed doc1, doc2
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with whitespace interleaved" do
|
107
|
+
it "handles whitespace nodes" do
|
108
|
+
doc1 = xml { root {
|
109
|
+
a1
|
110
|
+
text "\n\n"
|
111
|
+
a4
|
112
|
+
text "\n\n"
|
113
|
+
a5
|
114
|
+
} }
|
115
|
+
doc2 = xml { root {
|
116
|
+
a1
|
117
|
+
text "\n\n"
|
118
|
+
a2
|
119
|
+
text "\n\n"
|
120
|
+
a3
|
121
|
+
text "\n\n"
|
122
|
+
a4
|
123
|
+
text "\n\n"
|
124
|
+
a5
|
125
|
+
} }
|
126
|
+
round_trip_should_succeed doc1, doc2
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
data/spec/match_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Lorax::Match do
|
4
|
+
before do
|
5
|
+
@doc1 = xml { root }
|
6
|
+
@doc2 = xml { root }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#new" do
|
10
|
+
it "takes two nodes as arguments" do
|
11
|
+
proc { Lorax::Match.new(@doc1.root, @doc2.root) }.should_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it "takes optional options" do
|
15
|
+
proc { Lorax::Match.new(@doc1.root, @doc2.root, {:perfect => true}) }.should_not raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#perfect" do
|
20
|
+
it "returns true if {:perfect => true} option was passed to #new" do
|
21
|
+
Lorax::Match.new(@doc1.root, @doc2.root, {:perfect => true}).should be_perfect
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns false if {:perfect => false} option was passed to #new" do
|
25
|
+
Lorax::Match.new(@doc1.root, @doc2.root, {:perfect => false}).should_not be_perfect
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns false if no :perfect option was passed to #new" do
|
29
|
+
Lorax::Match.new(@doc1.root, @doc2.root).should_not be_perfect
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#pair" do
|
34
|
+
it "returns the two nodes in an array" do
|
35
|
+
Lorax::Match.new(@doc1.root, @doc2.root).pair.should == [@doc1.root, @doc2.root]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#other" do
|
40
|
+
context "the node is in the pair" do
|
41
|
+
it "returns the other node" do
|
42
|
+
match = Lorax::Match.new :a, :b
|
43
|
+
match.other(:a).should == :b
|
44
|
+
match.other(:b).should == :a
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "the node is not in the pair" do
|
49
|
+
it "returns nil" do
|
50
|
+
Lorax::Match.new(:a, :b).other(:c).should be_nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
require 'lorax'
|
6
|
+
|
7
|
+
require 'spec'
|
8
|
+
require 'spec/autorun'
|
9
|
+
require 'rr'
|
10
|
+
require 'pp'
|
11
|
+
|
12
|
+
warn "#{__FILE__}:#{__LINE__}: libxml version info: #{Nokogiri::VERSION_INFO.inspect}"
|
13
|
+
|
14
|
+
module XmlBuilderHelper
|
15
|
+
def xml(&block)
|
16
|
+
Nokogiri::XML::Builder.new(&block).doc
|
17
|
+
end
|
18
|
+
|
19
|
+
def assert_no_match_exists(match_set, node1, node2)
|
20
|
+
match_set.match(node1).should be_nil
|
21
|
+
match_set.match(node2).should be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_perfect_match_exists(match_set, node1, node2)
|
25
|
+
match = match_set.match(node1)
|
26
|
+
fail "#{node1.inspect} was not matched" if match.nil?
|
27
|
+
match.other(node1).should == node2
|
28
|
+
match.should be_perfect
|
29
|
+
end
|
30
|
+
|
31
|
+
def assert_forced_match_exists(match_set, node1, node2)
|
32
|
+
match = match_set.match(node1)
|
33
|
+
fail "#{node1.inspect} was not matched" if match.nil?
|
34
|
+
match.other(node1).should == node2
|
35
|
+
match.should_not be_perfect
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Spec::Runner.configure do |config|
|
40
|
+
config.mock_with :rr
|
41
|
+
config.include XmlBuilderHelper
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Lorax::DeleteDelta do
|
4
|
+
describe ".new" do
|
5
|
+
it "takes one argument" do
|
6
|
+
proc { Lorax::DeleteDelta.new(:foo) }.should_not raise_error(ArgumentError)
|
7
|
+
proc { Lorax::DeleteDelta.new(:foo, :bar)}.should raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#node" do
|
12
|
+
it "returns the initalizer argument" do
|
13
|
+
Lorax::DeleteDelta.new(:foo).node.should == :foo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#apply!" do
|
18
|
+
context "for an atomic node delta" do
|
19
|
+
it "should delete the node" do
|
20
|
+
doc1 = xml { root { a1 } }
|
21
|
+
doc2 = xml { root }
|
22
|
+
node = doc1.at_css("a1")
|
23
|
+
delta = Lorax::DeleteDelta.new node
|
24
|
+
|
25
|
+
delta.apply!(doc1)
|
26
|
+
|
27
|
+
doc1.at_css("a1").should be_nil
|
28
|
+
node.parent.should == nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "for a subtree delta" do
|
33
|
+
it "should delete the subtree" do
|
34
|
+
doc1 = xml { root { a1 { b1 ; b2 "hello" } } }
|
35
|
+
doc2 = xml { root }
|
36
|
+
node = doc1.at_css("a1")
|
37
|
+
delta = Lorax::DeleteDelta.new node
|
38
|
+
|
39
|
+
delta.apply!(doc1)
|
40
|
+
|
41
|
+
doc1.at_css("a1,b1,b2").should be_nil
|
42
|
+
node.parent.should == nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#descriptor" do
|
48
|
+
it "needs a spec"
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Lorax::InsertDelta do
|
4
|
+
describe ".new" do
|
5
|
+
it "takes three arguments" do
|
6
|
+
proc { Lorax::InsertDelta.new(:foo, :bar) }.should raise_error(ArgumentError)
|
7
|
+
proc { Lorax::InsertDelta.new(:foo, :bar, :quux) }.should_not raise_error(ArgumentError)
|
8
|
+
proc { Lorax::InsertDelta.new(:foo, :bar, :quux, :fuzz)}.should raise_error(ArgumentError)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#node" do
|
13
|
+
it "returns the first argument to #new" do
|
14
|
+
Lorax::InsertDelta.new(:foo, :bar, :quux).node.should == :foo
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#xpath" do
|
19
|
+
it "returns the second argument to #new" do
|
20
|
+
Lorax::InsertDelta.new(:foo, :bar, :quux).xpath.should == :bar
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#position" do
|
25
|
+
it "returns the third argument to #new" do
|
26
|
+
Lorax::InsertDelta.new(:foo, :bar, :quux).position.should == :quux
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#apply!" do
|
31
|
+
context "for an atomic node delta" do
|
32
|
+
it "should insert a copy into the document" do
|
33
|
+
doc1 = xml { root }
|
34
|
+
doc2 = xml { root { a1 } }
|
35
|
+
node = doc2.at_css("a1")
|
36
|
+
delta = Lorax::InsertDelta.new node, node.parent.path, 0
|
37
|
+
|
38
|
+
delta.apply!(doc1)
|
39
|
+
|
40
|
+
doc1.at_css("a1").should_not be_nil
|
41
|
+
node.parent.should == doc2.root
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "for a subtree node delta" do
|
46
|
+
it "should insert a copy into the document" do
|
47
|
+
doc1 = xml { root }
|
48
|
+
doc2 = xml { root { a1 { b1 ; b2 "hello" } } }
|
49
|
+
node = doc2.at_css("a1")
|
50
|
+
delta = Lorax::InsertDelta.new node, node.parent.path, 0
|
51
|
+
|
52
|
+
delta.apply!(doc1)
|
53
|
+
|
54
|
+
doc1.at_css("a1").should_not be_nil
|
55
|
+
node.parent.should == doc2.root
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "sibling node insertions" do
|
60
|
+
it "should insert at the front" do
|
61
|
+
doc1 = xml { root { a2 } }
|
62
|
+
doc2 = xml { root { a1 ; a2 } }
|
63
|
+
node = doc2.at_css("a1")
|
64
|
+
delta = Lorax::InsertDelta.new node, node.parent.path, 0
|
65
|
+
|
66
|
+
delta.apply! doc1
|
67
|
+
|
68
|
+
doc1.root.children.map {|child| child.name}.should == %w[a1 a2]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should insert at the middle" do
|
72
|
+
doc1 = xml { root { a1 ; a3 } }
|
73
|
+
doc2 = xml { root { a1 ; a2 ; a3 } }
|
74
|
+
node = doc2.at_css("a2")
|
75
|
+
delta = Lorax::InsertDelta.new node, node.parent.path, 1
|
76
|
+
|
77
|
+
delta.apply! doc1
|
78
|
+
|
79
|
+
doc1.root.children.map {|child| child.name}.should == %w[a1 a2 a3]
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should insert at the end" do
|
83
|
+
doc1 = xml { root { a1 } }
|
84
|
+
doc2 = xml { root { a1 ; a2 } }
|
85
|
+
node = doc2.at_css("a2")
|
86
|
+
delta = Lorax::InsertDelta.new node, node.parent.path, 1
|
87
|
+
|
88
|
+
delta.apply! doc1
|
89
|
+
|
90
|
+
doc1.root.children.map {|child| child.name}.should == %w[a1 a2]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "delta with unresolvable xpath" do
|
95
|
+
it "should raise a Conflict exception" do
|
96
|
+
doc1 = xml { root }
|
97
|
+
doc2 = xml { root { a1 } }
|
98
|
+
node = doc2.at_css("a1")
|
99
|
+
delta = Lorax::InsertDelta.new node, "/foo/bar/quux", 0
|
100
|
+
|
101
|
+
proc { delta.apply!(doc1) }.should raise_error(Lorax::Delta::NodeNotFoundError)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#descriptor" do
|
107
|
+
it "needs a spec"
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe Lorax::ModifyDelta do
|
4
|
+
describe ".new" do
|
5
|
+
it "takes two arguments" do
|
6
|
+
proc { Lorax::ModifyDelta.new(:foo) }.should raise_error(ArgumentError)
|
7
|
+
proc { Lorax::ModifyDelta.new(:foo, :bar) }.should_not raise_error(ArgumentError)
|
8
|
+
proc { Lorax::ModifyDelta.new(:foo, :bar, :quux)}.should raise_error(ArgumentError)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#node1" do
|
13
|
+
it "returns the first initializer parameter" do
|
14
|
+
Lorax::ModifyDelta.new(:foo, :bar).node1.should == :foo
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#node2" do
|
19
|
+
it "returns the first initializer parameter" do
|
20
|
+
Lorax::ModifyDelta.new(:foo, :bar).node2.should == :bar
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#apply!" do
|
25
|
+
context "element node" do
|
26
|
+
context "when attributes differ" do
|
27
|
+
it "should set the attributes properly" do
|
28
|
+
doc1 = xml { root { a1(:foo => :bar) } }
|
29
|
+
doc2 = xml { root { a1(:bazz => :quux, :once => :twice) } }
|
30
|
+
doc3 = doc1.dup
|
31
|
+
node1 = doc1.at_css("a1")
|
32
|
+
node2 = doc2.at_css("a1")
|
33
|
+
node3 = doc3.at_css("a1")
|
34
|
+
|
35
|
+
delta = Lorax::ModifyDelta.new(node1, node2)
|
36
|
+
delta.apply!(doc3)
|
37
|
+
|
38
|
+
node3["foo"].should be_nil
|
39
|
+
node3["bazz"].should == "quux"
|
40
|
+
node3["once"].should == "twice"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "text node" do
|
46
|
+
it "should set the content properly" do
|
47
|
+
doc1 = xml { root "hello" }
|
48
|
+
doc2 = xml { root "goodbye" }
|
49
|
+
doc3 = doc1.dup
|
50
|
+
|
51
|
+
delta = Lorax::ModifyDelta.new(doc1.root.children.first, doc2.root.children.first)
|
52
|
+
delta.apply!(doc3)
|
53
|
+
|
54
|
+
doc3.root.content.should == "goodbye"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "when positions differ" do
|
59
|
+
it "should move the node" do
|
60
|
+
doc1 = xml { root {
|
61
|
+
a1 { b1 }
|
62
|
+
a2
|
63
|
+
} }
|
64
|
+
doc2 = xml { root {
|
65
|
+
a1
|
66
|
+
a2 { b1 }
|
67
|
+
} }
|
68
|
+
delta = Lorax::ModifyDelta.new(doc1.at_css("b1"), doc2.at_css("b1"))
|
69
|
+
doc3 = doc1.dup
|
70
|
+
delta.apply!(doc3)
|
71
|
+
doc3.at_xpath("/root/a2/b1").should_not be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should move the node to the correct position" do
|
75
|
+
doc1 = xml { root {
|
76
|
+
a1 { b2 }
|
77
|
+
a2 { b1 ; b3 }
|
78
|
+
} }
|
79
|
+
doc2 = xml { root {
|
80
|
+
a1
|
81
|
+
a2 { b1 ; b2 ; b3 }
|
82
|
+
} }
|
83
|
+
delta = Lorax::ModifyDelta.new(doc1.at_css("b2"), doc2.at_css("b2"))
|
84
|
+
doc3 = doc1.dup
|
85
|
+
delta.apply!(doc3)
|
86
|
+
doc3.at_xpath("/root/a2/*[2]").name.should == "b2"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#descriptor" do
|
92
|
+
it "needs a spec"
|
93
|
+
end
|
94
|
+
end
|