lorax 0.1.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/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
|