diff-lcs 1.5.1 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +581 -0
- data/CODE_OF_CONDUCT.md +166 -0
- data/CONTRIBUTING.md +127 -0
- data/CONTRIBUTORS.md +59 -0
- data/LICENCE.md +68 -0
- data/Manifest.txt +99 -35
- data/README.md +105 -0
- data/Rakefile +107 -96
- data/SECURITY.md +36 -0
- data/integration/compare/array_diff_spec.rb +10 -0
- data/integration/compare/hash_diff_spec.rb +25 -0
- data/integration/compare/string_diff_spec.rb +10 -0
- data/integration/rspec_differ_spec.rb +26 -0
- data/integration/rspec_expectations_spec.rb +32 -0
- data/integration/runner +20 -0
- data/lib/diff/lcs/block.rb +29 -24
- data/lib/diff/lcs/callbacks.rb +240 -242
- data/lib/diff/lcs/change.rb +102 -104
- data/lib/diff/lcs/hunk.rb +110 -157
- data/lib/diff/lcs/internals.rb +92 -96
- data/lib/diff/lcs/ldiff.rb +81 -73
- data/lib/diff/lcs/version.rb +7 -0
- data/lib/diff/lcs.rb +440 -466
- data/{docs → licenses}/artistic.txt +1 -1
- data/licenses/dco.txt +34 -0
- data/spec/hunk_spec.rb +33 -46
- data/spec/issues_spec.rb +32 -32
- data/spec/lcs_spec.rb +6 -6
- data/spec/ldiff_spec.rb +27 -16
- data/spec/patch_spec.rb +1 -1
- data/spec/spec_helper.rb +98 -108
- data/test/fixtures/123_x +2 -0
- data/test/fixtures/456_x +2 -0
- data/test/fixtures/empty +0 -0
- data/test/fixtures/file1.bin +0 -0
- data/test/fixtures/file2.bin +0 -0
- data/test/fixtures/four_lines +4 -0
- data/test/fixtures/four_lines_with_missing_new_line +4 -0
- data/test/fixtures/ldiff/diff.missing_new_line1-e +1 -0
- data/test/fixtures/ldiff/diff.missing_new_line1-f +1 -0
- data/test/fixtures/ldiff/diff.missing_new_line2-e +1 -0
- data/test/fixtures/ldiff/diff.missing_new_line2-f +1 -0
- data/test/fixtures/ldiff/error.diff.chef-e +2 -0
- data/test/fixtures/ldiff/error.diff.chef-f +2 -0
- data/test/fixtures/ldiff/error.diff.missing_new_line1-e +1 -0
- data/test/fixtures/ldiff/error.diff.missing_new_line1-f +1 -0
- data/test/fixtures/ldiff/error.diff.missing_new_line2-e +1 -0
- data/test/fixtures/ldiff/error.diff.missing_new_line2-f +1 -0
- data/test/fixtures/ldiff/output.diff-c +7 -0
- data/test/fixtures/ldiff/output.diff-u +5 -0
- data/test/fixtures/ldiff/output.diff.bin1 +0 -0
- data/test/fixtures/ldiff/output.diff.bin1-c +0 -0
- data/test/fixtures/ldiff/output.diff.bin1-e +0 -0
- data/test/fixtures/ldiff/output.diff.bin1-f +0 -0
- data/test/fixtures/ldiff/output.diff.bin1-u +0 -0
- data/test/fixtures/ldiff/output.diff.bin2 +1 -0
- data/test/fixtures/ldiff/output.diff.bin2-c +1 -0
- data/test/fixtures/ldiff/output.diff.bin2-e +1 -0
- data/test/fixtures/ldiff/output.diff.bin2-f +1 -0
- data/test/fixtures/ldiff/output.diff.bin2-u +1 -0
- data/{spec → test}/fixtures/ldiff/output.diff.chef-c +2 -2
- data/test/fixtures/ldiff/output.diff.chef-u +9 -0
- data/{spec → test}/fixtures/ldiff/output.diff.chef2-c +2 -2
- data/{spec → test}/fixtures/ldiff/output.diff.chef2-u +2 -2
- data/test/fixtures/ldiff/output.diff.empty.vs.four_lines +5 -0
- data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c +9 -0
- data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-e +6 -0
- data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-f +6 -0
- data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u +7 -0
- data/test/fixtures/ldiff/output.diff.four_lines.vs.empty +5 -0
- data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c +9 -0
- data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-e +1 -0
- data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-f +1 -0
- data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u +7 -0
- data/test/fixtures/ldiff/output.diff.issue95_trailing_context +4 -0
- data/test/fixtures/ldiff/output.diff.issue95_trailing_context-c +9 -0
- data/{spec/fixtures/ldiff/output.diff-e → test/fixtures/ldiff/output.diff.issue95_trailing_context-e} +1 -1
- data/{spec/fixtures/ldiff/output.diff-f → test/fixtures/ldiff/output.diff.issue95_trailing_context-f} +1 -1
- data/test/fixtures/ldiff/output.diff.issue95_trailing_context-u +6 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line1 +5 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line1-c +14 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line1-e +0 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line1-f +0 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line1-u +9 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line2 +5 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line2-c +14 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line2-e +0 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line2-f +0 -0
- data/test/fixtures/ldiff/output.diff.missing_new_line2-u +9 -0
- data/test/test_block.rb +34 -0
- data/test/test_change.rb +234 -0
- data/test/test_diff.rb +53 -0
- data/test/test_helper.rb +225 -0
- data/test/test_hunk.rb +72 -0
- data/test/test_issues.rb +168 -0
- data/test/test_lcs.rb +47 -0
- data/test/test_ldiff.rb +89 -0
- data/test/test_patch.rb +362 -0
- data/test/test_sdiff.rb +167 -0
- data/test/test_traverse_balanced.rb +322 -0
- data/test/test_traverse_sequences.rb +187 -0
- metadata +211 -103
- data/.rspec +0 -1
- data/Code-of-Conduct.md +0 -74
- data/Contributing.md +0 -121
- data/History.md +0 -431
- data/License.md +0 -41
- data/README.rdoc +0 -84
- data/bin/htmldiff +0 -35
- data/lib/diff/lcs/backports.rb +0 -9
- data/lib/diff/lcs/htmldiff.rb +0 -158
- data/spec/fixtures/ldiff/output.diff-c +0 -7
- data/spec/fixtures/ldiff/output.diff-u +0 -5
- data/spec/fixtures/ldiff/output.diff.chef-e +0 -3
- data/spec/fixtures/ldiff/output.diff.chef-f +0 -3
- data/spec/fixtures/ldiff/output.diff.chef-u +0 -9
- data/spec/fixtures/ldiff/output.diff.chef2-e +0 -7
- data/spec/fixtures/ldiff/output.diff.chef2-f +0 -7
- /data/{docs → licenses}/COPYING.txt +0 -0
- /data/{spec → test}/fixtures/aX +0 -0
- /data/{spec → test}/fixtures/bXaX +0 -0
- /data/{spec → test}/fixtures/ds1.csv +0 -0
- /data/{spec → test}/fixtures/ds2.csv +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.chef +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.chef2 +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.chef2-d +0 -0
- /data/{spec → test}/fixtures/new-chef +0 -0
- /data/{spec → test}/fixtures/new-chef2 +0 -0
- /data/{spec → test}/fixtures/old-chef +0 -0
- /data/{spec → test}/fixtures/old-chef2 +0 -0
data/lib/diff/lcs.rb
CHANGED
|
@@ -2,124 +2,111 @@
|
|
|
2
2
|
|
|
3
3
|
module Diff; end unless defined? Diff
|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# ## How Diff Works (by Mark-Jason Dominus)
|
|
6
6
|
#
|
|
7
|
-
# I once read an article written by the authors of
|
|
8
|
-
#
|
|
7
|
+
# I once read an article written by the authors of `diff`; they said that they hard worked
|
|
8
|
+
# very hard on the algorithm until they found the right one.
|
|
9
9
|
#
|
|
10
|
-
# I think what they ended up using (and I hope someone will correct me, because
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# I think what they ended up using (and I hope someone will correct me, because I am not
|
|
11
|
+
# very confident about this) was the `longest common subsequence' method. In the LCS
|
|
12
|
+
# problem, you have two sequences of items:
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# ```
|
|
15
|
+
# a b c d f g h j q z
|
|
16
|
+
# a b c d e f g i j k r x y z
|
|
17
|
+
# ```
|
|
16
18
|
#
|
|
17
|
-
# and you want to find the longest sequence of items that is present in both
|
|
18
|
-
#
|
|
19
|
-
# sequence
|
|
20
|
-
# items
|
|
21
|
-
# *S* to be as long as possible. In this case *S* is:
|
|
19
|
+
# and you want to find the longest sequence of items that is present in both original
|
|
20
|
+
# sequences in the same order. That is, you want to find a new sequence *S* which can be
|
|
21
|
+
# obtained from the first sequence by deleting some items, and from the second sequence by
|
|
22
|
+
# deleting other items. You also want *S* to be as long as possible. In this case *S* is:
|
|
22
23
|
#
|
|
23
|
-
#
|
|
24
|
+
# ```
|
|
25
|
+
# a b c d f g j z
|
|
26
|
+
# ```
|
|
24
27
|
#
|
|
25
28
|
# From there it's only a small step to get diff-like output:
|
|
26
29
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
30
|
+
# ```
|
|
31
|
+
# e h i k q r x y
|
|
32
|
+
# + - + + - + + +
|
|
33
|
+
# ```
|
|
29
34
|
#
|
|
30
|
-
# This module solves the LCS problem. It also includes a canned function to
|
|
31
|
-
#
|
|
35
|
+
# This module solves the LCS problem. It also includes a canned function to generate
|
|
36
|
+
# `diff`-like output.
|
|
32
37
|
#
|
|
33
|
-
# It might seem from the example above that the LCS of two sequences is always
|
|
34
|
-
#
|
|
35
|
-
#
|
|
38
|
+
# It might seem from the example above that the LCS of two sequences is always pretty
|
|
39
|
+
# obvious, but that's not always the case, especially when the two sequences have many
|
|
40
|
+
# repeated elements. For example, consider
|
|
36
41
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
42
|
+
# ```
|
|
43
|
+
# a x b y c z p d q
|
|
44
|
+
# a b c a x b y c z
|
|
45
|
+
# ```
|
|
39
46
|
#
|
|
40
|
-
# A naive approach might start by matching up the
|
|
47
|
+
# A naive approach might start by matching up the `a` and `b` that appear at
|
|
41
48
|
# the beginning of each sequence, like this:
|
|
42
49
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
50
|
+
# ```
|
|
51
|
+
# a x b y c z p d q
|
|
52
|
+
# a b c a b y c z
|
|
53
|
+
# ```
|
|
45
54
|
#
|
|
46
|
-
# This finds the common subsequence
|
|
47
|
-
# y c z+:
|
|
55
|
+
# This finds the common subsequence `a b c z`. But actually, the LCS is `a x b y c z`:
|
|
48
56
|
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
57
|
+
# ```
|
|
58
|
+
# a x b y c z p d q
|
|
59
|
+
# a b c a x b y c z
|
|
60
|
+
# ```
|
|
51
61
|
module Diff::LCS
|
|
52
|
-
VERSION = "1.5.1"
|
|
53
62
|
end
|
|
54
63
|
|
|
64
|
+
require "diff/lcs/version"
|
|
55
65
|
require "diff/lcs/callbacks"
|
|
56
66
|
require "diff/lcs/internals"
|
|
57
67
|
|
|
58
68
|
module Diff::LCS
|
|
59
|
-
# Returns an Array containing the longest common subsequence(s) between
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# lcs = seq1.lcs(seq2)
|
|
63
|
-
#
|
|
64
|
-
# A note when using objects: Diff::LCS only works properly when each object
|
|
65
|
-
# can be used as a key in a Hash, which typically means that the objects must
|
|
66
|
-
# implement Object#eql? in a way that two identical values compare
|
|
67
|
-
# identically for key purposes. That is:
|
|
68
|
-
#
|
|
69
|
-
# O.new('a').eql?(O.new('a')) == true
|
|
70
|
-
def lcs(other, &block) # :yields: self[i] if there are matched subsequences
|
|
69
|
+
# Returns an Array containing the longest common subsequence(s) between `self` and
|
|
70
|
+
# `other`. See Diff::LCS.lcs.
|
|
71
|
+
def lcs(other, &block) = # :yields: self[i] if there are matched subsequences
|
|
71
72
|
Diff::LCS.lcs(self, other, &block)
|
|
72
|
-
end
|
|
73
73
|
|
|
74
|
-
# Returns the difference set between
|
|
75
|
-
def diff(other, callbacks = nil, &block)
|
|
76
|
-
Diff::LCS.diff(self, other, callbacks, &block)
|
|
77
|
-
end
|
|
74
|
+
# Returns the difference set between `self` and `other`. See Diff::LCS.diff.
|
|
75
|
+
def diff(other, callbacks = nil, &block) = Diff::LCS.diff(self, other, callbacks, &block)
|
|
78
76
|
|
|
79
|
-
# Returns the balanced ("side-by-side") difference set between
|
|
80
|
-
#
|
|
81
|
-
def sdiff(other, callbacks = nil, &block)
|
|
82
|
-
Diff::LCS.sdiff(self, other, callbacks, &block)
|
|
83
|
-
end
|
|
77
|
+
# Returns the balanced ("side-by-side") difference set between `self` and `other`. See
|
|
78
|
+
# Diff::LCS.sdiff.
|
|
79
|
+
def sdiff(other, callbacks = nil, &block) = Diff::LCS.sdiff(self, other, callbacks, &block)
|
|
84
80
|
|
|
85
|
-
# Traverses the discovered longest common subsequences between
|
|
86
|
-
#
|
|
87
|
-
def traverse_sequences(other, callbacks = nil, &block)
|
|
81
|
+
# Traverses the discovered longest common subsequences between `self` and `other`. See
|
|
82
|
+
# Diff::LCS.traverse_sequences.
|
|
83
|
+
def traverse_sequences(other, callbacks = nil, &block) =
|
|
88
84
|
Diff::LCS.traverse_sequences(self, other, callbacks || Diff::LCS::SequenceCallbacks, &block)
|
|
89
|
-
end
|
|
90
85
|
|
|
91
|
-
# Traverses the discovered longest common subsequences between
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
def traverse_balanced(other, callbacks = nil, &block)
|
|
86
|
+
# Traverses the discovered longest common subsequences between `self` and `other` using
|
|
87
|
+
# the alternate, balanced algorithm. See Diff::LCS.traverse_balanced.
|
|
88
|
+
def traverse_balanced(other, callbacks = nil, &block) =
|
|
95
89
|
Diff::LCS.traverse_balanced(self, other, callbacks || Diff::LCS::BalancedCallbacks, &block)
|
|
96
|
-
end
|
|
97
90
|
|
|
98
|
-
# Attempts to patch
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
def patch(patchset)
|
|
102
|
-
Diff::LCS.patch(self, patchset)
|
|
103
|
-
end
|
|
91
|
+
# Attempts to patch `self` with the provided `patchset`. A new sequence based on `self`
|
|
92
|
+
# and the `patchset` will be created. See Diff::LCS.patch. Attempts to autodiscover the
|
|
93
|
+
# direction of the patch.
|
|
94
|
+
def patch(patchset) = Diff::LCS.patch(self, patchset)
|
|
104
95
|
alias_method :unpatch, :patch
|
|
105
96
|
|
|
106
|
-
# Attempts to patch
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
def patch!(patchset)
|
|
110
|
-
Diff::LCS.patch!(self, patchset)
|
|
111
|
-
end
|
|
97
|
+
# Attempts to patch `self` with the provided `patchset`. A new sequence based on `self`
|
|
98
|
+
# and the `patchset` will be created. See Diff::LCS.patch!. Does no patch direction
|
|
99
|
+
# autodiscovery.
|
|
100
|
+
def patch!(patchset) = Diff::LCS.patch!(self, patchset)
|
|
112
101
|
|
|
113
|
-
# Attempts to unpatch
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
def unpatch!(patchset)
|
|
117
|
-
Diff::LCS.unpatch!(self, patchset)
|
|
118
|
-
end
|
|
102
|
+
# Attempts to unpatch `self` with the provided `patchset`. A new sequence based on
|
|
103
|
+
# `self` and the `patchset` will be created. See Diff::LCS.unpatch!. Does no patch
|
|
104
|
+
# direction autodiscovery.
|
|
105
|
+
def unpatch!(patchset) = Diff::LCS.unpatch!(self, patchset)
|
|
119
106
|
|
|
120
|
-
# Attempts to patch
|
|
121
|
-
#
|
|
122
|
-
#
|
|
107
|
+
# Attempts to patch `self` with the provided `patchset`, using #patch!. If the sequence
|
|
108
|
+
# this is used on supports #replace, the value of `self` will be replaced. See
|
|
109
|
+
# Diff::LCS.patch!. Does no patch direction autodiscovery.
|
|
123
110
|
def patch_me(patchset)
|
|
124
111
|
if respond_to? :replace
|
|
125
112
|
replace(patch!(patchset))
|
|
@@ -128,9 +115,9 @@ module Diff::LCS
|
|
|
128
115
|
end
|
|
129
116
|
end
|
|
130
117
|
|
|
131
|
-
# Attempts to unpatch
|
|
132
|
-
#
|
|
133
|
-
#
|
|
118
|
+
# Attempts to unpatch `self` with the provided `patchset`, using #unpatch!. If the
|
|
119
|
+
# sequence this is used on supports #replace, the value of `self` will be replaced. See
|
|
120
|
+
# Diff::LCS#unpatch. Does no patch direction autodiscovery.
|
|
134
121
|
def unpatch_me(patchset)
|
|
135
122
|
if respond_to? :replace
|
|
136
123
|
replace(unpatch!(patchset))
|
|
@@ -138,440 +125,427 @@ module Diff::LCS
|
|
|
138
125
|
unpatch!(patchset)
|
|
139
126
|
end
|
|
140
127
|
end
|
|
141
|
-
end
|
|
142
128
|
|
|
143
|
-
|
|
144
|
-
|
|
129
|
+
# Returns an Array containing the longest common subsequence(s) between `seq` and
|
|
130
|
+
# `seq2`.
|
|
131
|
+
#
|
|
132
|
+
# > NOTE on comparing objects: Diff::LCS only works properly when each object can be
|
|
133
|
+
# > used as a key in a Hash. This means that those objects must implement the methods
|
|
134
|
+
# > `#hash` and `#eql?` such that two objects containing identical values compare
|
|
135
|
+
# > identically for key purposes. That is:
|
|
136
|
+
# >
|
|
137
|
+
# > ```
|
|
138
|
+
# > O.new('a').eql?(O.new('a')) == true && O.new('a').hash == O.new('a').hash
|
|
139
|
+
# > ```
|
|
140
|
+
def self.lcs(seq1, seq2, &block) # :yields: seq1[i] for each matched
|
|
145
141
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
142
|
+
[].tap { |result|
|
|
143
|
+
matches.each_index do
|
|
144
|
+
next if matches[_1].nil?
|
|
145
|
+
|
|
146
|
+
v = seq1[_1]
|
|
147
|
+
v = block.call(v) if block
|
|
148
|
+
|
|
149
|
+
result << v
|
|
150
|
+
end
|
|
151
|
+
}
|
|
156
152
|
end
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
# Class argument is provided for +callbacks+, #diff will attempt to
|
|
166
|
-
# initialise it. If the +callbacks+ object (possibly initialised) responds to
|
|
167
|
-
# #finish, it will be called.
|
|
168
|
-
def diff(seq1, seq2, callbacks = nil, &block) # :yields: diff changes
|
|
153
|
+
|
|
154
|
+
# `diff` computes the smallest set of additions and deletions necessary to turn the
|
|
155
|
+
# first sequence into the second, and returns a description of these changes.
|
|
156
|
+
#
|
|
157
|
+
# See Diff::LCS::DiffCallbacks for the default behaviour. An alternate behaviour may be
|
|
158
|
+
# implemented with Diff::LCS::ContextDiffCallbacks. If the `callbacks` object responds
|
|
159
|
+
# to #finish, it will be called.
|
|
160
|
+
def self.diff(seq1, seq2, callbacks = nil, &block) = # :yields: diff changes
|
|
169
161
|
diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks, &block)
|
|
170
|
-
end
|
|
171
162
|
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
# #finish, it will be called.
|
|
186
|
-
#
|
|
187
|
-
# Each element of a returned array is a Diff::LCS::ContextChange object,
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
163
|
+
# `sdiff` computes all necessary components to show two sequences and their minimized
|
|
164
|
+
# differences side by side, just like the Unix utility _sdiff_ does:
|
|
165
|
+
#
|
|
166
|
+
#
|
|
167
|
+
# ```
|
|
168
|
+
# old < -
|
|
169
|
+
# same same
|
|
170
|
+
# before | after
|
|
171
|
+
# - > new
|
|
172
|
+
# ```
|
|
173
|
+
#
|
|
174
|
+
# See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate behaviour may be
|
|
175
|
+
# implemented with Diff::LCS::ContextDiffCallbacks. If the `callbacks` object responds
|
|
176
|
+
# to #finish, it will be called.
|
|
177
|
+
#
|
|
178
|
+
# Each element of a returned array is a Diff::LCS::ContextChange object, which can be
|
|
179
|
+
# implicitly converted to an array.
|
|
180
|
+
#
|
|
181
|
+
# ```ruby
|
|
182
|
+
# Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)|
|
|
183
|
+
# case action
|
|
184
|
+
# when '!'
|
|
185
|
+
# # replace
|
|
186
|
+
# when '-'
|
|
187
|
+
# # delete
|
|
188
|
+
# when '+'
|
|
189
|
+
# # insert
|
|
199
190
|
# end
|
|
200
|
-
|
|
191
|
+
# end
|
|
192
|
+
# ```
|
|
193
|
+
def self.sdiff(seq1, seq2, callbacks = nil, &block) = # :yields: diff changes
|
|
201
194
|
diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks, &block)
|
|
202
|
-
end
|
|
203
195
|
|
|
204
|
-
# #traverse_sequences is the most general facility provided by this module;
|
|
205
|
-
# #
|
|
206
|
-
#
|
|
207
|
-
# The arguments to #
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
# callbacks#match
|
|
217
|
-
#
|
|
218
|
-
# callbacks#discard_a
|
|
219
|
-
#
|
|
220
|
-
# callbacks#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
# If there are two arrows (
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
# advance
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
# #
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
# each element of +A+ until the end of the sequence is reached (<tt>A[i]</tt>
|
|
279
|
-
# and <tt>B[-1]</tt>).
|
|
280
|
-
#
|
|
281
|
-
# There is a chance that one additional <tt>callbacks#discard_a</tt> or
|
|
282
|
-
# <tt>callbacks#discard_b</tt> will be called after the end of the sequence
|
|
283
|
-
# is reached, if +a+ has not yet reached the end of +A+ or +b+ has not yet
|
|
284
|
-
# reached the end of +B+.
|
|
285
|
-
def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks) # :yields: change events
|
|
196
|
+
# #traverse_sequences is the most general facility provided by this module; #diff and
|
|
197
|
+
# #lcs are implemented using #traverse_sequences.
|
|
198
|
+
#
|
|
199
|
+
# The arguments to #traverse_sequence are the two sequences to traverse, and a callback
|
|
200
|
+
# object, like this:
|
|
201
|
+
#
|
|
202
|
+
# ```ruby
|
|
203
|
+
# traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
|
|
204
|
+
# ```
|
|
205
|
+
#
|
|
206
|
+
# ### Callback Methods
|
|
207
|
+
#
|
|
208
|
+
# - `callbacks#match`: Called when `a` and `b` are pointing to common elements in `A`
|
|
209
|
+
# and `B`.
|
|
210
|
+
# - `callbacks#discard_a`: Called when `a` is pointing to an element not in `B`.
|
|
211
|
+
# - `callbacks#discard_b`: Called when `b` is pointing to an element not in `A`.
|
|
212
|
+
# - `callbacks#finished_a`: Called when `a` has reached the end of sequence `A`.
|
|
213
|
+
# Optional.
|
|
214
|
+
# - `callbacks#finished_b`: Called when `b` has reached the end of sequence `B`.
|
|
215
|
+
# Optional.
|
|
216
|
+
#
|
|
217
|
+
# ### Algorithm
|
|
218
|
+
#
|
|
219
|
+
# ```
|
|
220
|
+
# a---+
|
|
221
|
+
# v
|
|
222
|
+
# A = a b c e h j l m n p
|
|
223
|
+
# B = b c d e f j k l m r s t
|
|
224
|
+
# ^
|
|
225
|
+
# b---+
|
|
226
|
+
# ```
|
|
227
|
+
#
|
|
228
|
+
# If there are two arrows (`a` and `b`) pointing to elements of sequences `A` and `B`,
|
|
229
|
+
# the arrows will initially point to the first elements of their respective sequences.
|
|
230
|
+
# #traverse_sequences will advance the arrows through the sequences one element at
|
|
231
|
+
# a time, calling a method on the user-specified callback object before each advance. It
|
|
232
|
+
# will advance the arrows in such a way that if there are elements `A[i]` and `B[j]`
|
|
233
|
+
# which are both equal and part of the longest common subsequence, there will be some
|
|
234
|
+
# moment during the execution of #traverse_sequences when arrow `a` is pointing to
|
|
235
|
+
# `A[i]` and arrow `b` is pointing to `B[j]`. When this happens, #traverse_sequences
|
|
236
|
+
# will call `callbacks#match` and then it will advance both arrows.
|
|
237
|
+
#
|
|
238
|
+
# Otherwise, one of the arrows is pointing to an element of its sequence that is not
|
|
239
|
+
# part of the longest common subsequence. #traverse_sequences will advance that arrow
|
|
240
|
+
# and will call `callbacks#discard_a` or `callbacks#discard_b`, depending on which arrow
|
|
241
|
+
# it advanced. If both arrows point to elements that are not part of the longest common
|
|
242
|
+
# subsequence, then #traverse_sequences will advance arrow `a` and call the appropriate
|
|
243
|
+
# callback, then it will advance arrow `b` and call the appropriate callback.
|
|
244
|
+
#
|
|
245
|
+
# The methods for `callbacks#match`, `callbacks#discard_a`, and `callbacks#discard_b`
|
|
246
|
+
# are invoked with an event comprising the action ("=", "+", or "-", respectively), the
|
|
247
|
+
# indexes `i` and `j`, and the elements `A[i]` and `B[j]`. Return values are discarded
|
|
248
|
+
# by #traverse_sequences.
|
|
249
|
+
#
|
|
250
|
+
# #### End of Sequences
|
|
251
|
+
#
|
|
252
|
+
# If arrow `a` reaches the end of its sequence before arrow `b` does, #traverse_sequence
|
|
253
|
+
# will try to call `callbacks#finished_a` with the last index and element of `A`
|
|
254
|
+
# (`A[-1]`) and the current index and element of `B` (`B[j]`). If `callbacks#finished_a`
|
|
255
|
+
# does not exist, then `callbacks#discard_b` will be called on each element of `B` until
|
|
256
|
+
# the end of the sequence is reached (the call will be done with `A[-1]` and `B[j]` for
|
|
257
|
+
# each element).
|
|
258
|
+
#
|
|
259
|
+
# If `b` reaches the end of `B` before `a` reaches the end of `A`,
|
|
260
|
+
# `callbacks#finished_b` will be called with the current index and element of `A`
|
|
261
|
+
# (`A[i]`) and the last index and element of `B` (`A[-1]`). Again, if
|
|
262
|
+
# `callbacks#finished_b` does not exist on the callback object, then
|
|
263
|
+
# `callbacks#discard_a` will be called on each element of `A` until the end of the
|
|
264
|
+
# sequence is reached (`A[i]` and `B[-1]`).
|
|
265
|
+
#
|
|
266
|
+
# There is a chance that one additional `callbacks#discard_a` or `callbacks#discard_b`
|
|
267
|
+
# will be called after the end of the sequence is reached, if `a` has not yet reached
|
|
268
|
+
# the end of `A` or `b` has not yet reached the end of `B`.
|
|
269
|
+
def self.traverse_sequences(seq1, seq2, callbacks = nil) # :yields: change events
|
|
286
270
|
callbacks ||= Diff::LCS::SequenceCallbacks
|
|
287
271
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
|
288
272
|
|
|
289
273
|
run_finished_a = run_finished_b = false
|
|
290
|
-
string = seq1.is_a?(String)
|
|
291
274
|
|
|
292
275
|
a_size = seq1.size
|
|
293
276
|
b_size = seq2.size
|
|
294
|
-
|
|
277
|
+
a_i = b_j = 0
|
|
295
278
|
|
|
296
279
|
matches.each do |b_line|
|
|
297
280
|
if b_line.nil?
|
|
298
|
-
unless seq1[
|
|
299
|
-
|
|
300
|
-
|
|
281
|
+
unless seq1[a_i].nil?
|
|
282
|
+
a_x = seq1[a_i]
|
|
283
|
+
b_x = seq2[b_j]
|
|
301
284
|
|
|
302
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
285
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
303
286
|
event = yield event if block_given?
|
|
304
287
|
callbacks.discard_a(event)
|
|
305
288
|
end
|
|
306
289
|
else
|
|
307
|
-
|
|
290
|
+
a_x = seq1[a_i]
|
|
308
291
|
|
|
309
292
|
loop do
|
|
310
|
-
break unless
|
|
293
|
+
break unless b_j < b_line
|
|
311
294
|
|
|
312
|
-
|
|
313
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
295
|
+
b_x = seq2[b_j]
|
|
296
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
314
297
|
event = yield event if block_given?
|
|
315
298
|
callbacks.discard_b(event)
|
|
316
|
-
|
|
299
|
+
b_j += 1
|
|
317
300
|
end
|
|
318
|
-
|
|
319
|
-
event = Diff::LCS::ContextChange.new("=",
|
|
301
|
+
b_x = seq2[b_j]
|
|
302
|
+
event = Diff::LCS::ContextChange.new("=", a_i, a_x, b_j, b_x)
|
|
320
303
|
event = yield event if block_given?
|
|
321
304
|
callbacks.match(event)
|
|
322
|
-
|
|
305
|
+
b_j += 1
|
|
323
306
|
end
|
|
324
|
-
|
|
307
|
+
|
|
308
|
+
a_i += 1
|
|
325
309
|
end
|
|
326
310
|
|
|
327
|
-
# The last entry (if any) processed was a match.
|
|
328
|
-
#
|
|
329
|
-
while (
|
|
311
|
+
# The last entry (if any) processed was a match. `a_i` and `b_j` point just past the
|
|
312
|
+
# last matching lines in their sequences.
|
|
313
|
+
while (a_i < a_size) || (b_j < b_size)
|
|
330
314
|
# last A?
|
|
331
|
-
if
|
|
315
|
+
if a_i == a_size && b_j < b_size
|
|
332
316
|
if callbacks.respond_to?(:finished_a) && !run_finished_a
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
event = Diff::LCS::ContextChange.new(">",
|
|
317
|
+
a_x = seq1[-1]
|
|
318
|
+
b_x = seq2[b_j]
|
|
319
|
+
event = Diff::LCS::ContextChange.new(">", a_size - 1, a_x, b_j, b_x)
|
|
336
320
|
event = yield event if block_given?
|
|
337
321
|
callbacks.finished_a(event)
|
|
338
322
|
run_finished_a = true
|
|
339
323
|
else
|
|
340
|
-
|
|
324
|
+
a_x = seq1[a_i]
|
|
341
325
|
loop do
|
|
342
|
-
|
|
343
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
326
|
+
b_x = seq2[b_j]
|
|
327
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
344
328
|
event = yield event if block_given?
|
|
345
329
|
callbacks.discard_b(event)
|
|
346
|
-
|
|
347
|
-
break unless
|
|
330
|
+
b_j += 1
|
|
331
|
+
break unless b_j < b_size
|
|
348
332
|
end
|
|
349
333
|
end
|
|
350
334
|
end
|
|
351
335
|
|
|
352
336
|
# last B?
|
|
353
|
-
if
|
|
337
|
+
if b_j == b_size && a_i < a_size
|
|
354
338
|
if callbacks.respond_to?(:finished_b) && !run_finished_b
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
event = Diff::LCS::ContextChange.new("<",
|
|
339
|
+
a_x = seq1[a_i]
|
|
340
|
+
b_x = seq2[-1]
|
|
341
|
+
event = Diff::LCS::ContextChange.new("<", a_i, a_x, b_size - 1, b_x)
|
|
358
342
|
event = yield event if block_given?
|
|
359
343
|
callbacks.finished_b(event)
|
|
360
344
|
run_finished_b = true
|
|
361
345
|
else
|
|
362
|
-
|
|
346
|
+
b_x = seq2[b_j]
|
|
363
347
|
loop do
|
|
364
|
-
|
|
365
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
348
|
+
a_x = seq1[a_i]
|
|
349
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
366
350
|
event = yield event if block_given?
|
|
367
351
|
callbacks.discard_a(event)
|
|
368
|
-
|
|
369
|
-
break unless
|
|
352
|
+
a_i += 1
|
|
353
|
+
break unless b_j < b_size
|
|
370
354
|
end
|
|
371
355
|
end
|
|
372
356
|
end
|
|
373
357
|
|
|
374
|
-
if
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
358
|
+
if a_i < a_size
|
|
359
|
+
a_x = seq1[a_i]
|
|
360
|
+
b_x = seq2[b_j]
|
|
361
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
378
362
|
event = yield event if block_given?
|
|
379
363
|
callbacks.discard_a(event)
|
|
380
|
-
|
|
364
|
+
a_i += 1
|
|
381
365
|
end
|
|
382
366
|
|
|
383
|
-
if
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
367
|
+
if b_j < b_size
|
|
368
|
+
a_x = seq1[a_i]
|
|
369
|
+
b_x = seq2[b_j]
|
|
370
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
387
371
|
event = yield event if block_given?
|
|
388
372
|
callbacks.discard_b(event)
|
|
389
|
-
|
|
373
|
+
b_j += 1
|
|
390
374
|
end
|
|
391
375
|
end
|
|
392
376
|
end
|
|
393
377
|
|
|
394
|
-
# #traverse_balanced is an alternative to #traverse_sequences. It uses a
|
|
395
|
-
#
|
|
396
|
-
#
|
|
397
|
-
#
|
|
398
|
-
#
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
#
|
|
402
|
-
#
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
#
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
# callbacks#
|
|
414
|
-
#
|
|
415
|
-
#
|
|
416
|
-
#
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
#
|
|
423
|
-
#
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
427
|
-
#
|
|
428
|
-
#
|
|
429
|
-
#
|
|
430
|
-
#
|
|
431
|
-
#
|
|
432
|
-
#
|
|
433
|
-
#
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
#
|
|
440
|
-
#
|
|
441
|
-
#
|
|
442
|
-
#
|
|
443
|
-
#
|
|
444
|
-
#
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
#
|
|
448
|
-
#
|
|
449
|
-
#
|
|
450
|
-
#
|
|
451
|
-
#
|
|
452
|
-
#
|
|
453
|
-
#
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
#
|
|
459
|
-
#
|
|
460
|
-
# <tt>callbacks#change</tt> and advance both arrows. If
|
|
461
|
-
# <tt>callbacks#change</tt> is not implemented, then
|
|
462
|
-
# <tt>callbacks#discard_a</tt> and <tt>callbacks#discard_b</tt> will be
|
|
463
|
-
# called in turn.
|
|
464
|
-
#
|
|
465
|
-
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
|
|
466
|
-
# <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are invoked
|
|
467
|
-
# with an event comprising the action ("=", "+", "-", or "!", respectively),
|
|
468
|
-
# the indicies +i+ and +j+, and the elements <tt>A[i]</tt> and <tt>B[j]</tt>.
|
|
378
|
+
# #traverse_balanced is an alternative to #traverse_sequences. It uses a different
|
|
379
|
+
# algorithm to iterate through the entries in the computed longest common subsequence.
|
|
380
|
+
# Instead of viewing the changes as insertions or deletions from one of the sequences,
|
|
381
|
+
# #traverse_balanced will report _changes_ between the sequences.
|
|
382
|
+
#
|
|
383
|
+
# The arguments to #traverse_balanced are the two sequences to traverse and a callback
|
|
384
|
+
# object, like this:
|
|
385
|
+
#
|
|
386
|
+
# ```ruby
|
|
387
|
+
# traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
|
|
388
|
+
# ```
|
|
389
|
+
#
|
|
390
|
+
# #sdiff is implemented using #traverse_balanced.
|
|
391
|
+
#
|
|
392
|
+
# ### Callback Methods
|
|
393
|
+
#
|
|
394
|
+
# - `callbacks#match`: Called when `a` and `b` are pointing to common elements in `A`
|
|
395
|
+
# and `B`.
|
|
396
|
+
# - `callbacks#discard_a`: Called when `a` is pointing to an element not in `B`.
|
|
397
|
+
# - `callbacks#discard_b`: Called when `b` is pointing to an element not in `A`.
|
|
398
|
+
# - `callbacks#change`: Called when `a` and `b` are pointing to the same relative
|
|
399
|
+
# position, but `A[a]` and `B[b]` are not the same; a _change_ has occurred. Optional.
|
|
400
|
+
#
|
|
401
|
+
# #traverse_balanced might be a bit slower than #traverse_sequences, noticeable only
|
|
402
|
+
# while processing large amounts of data.
|
|
403
|
+
#
|
|
404
|
+
# ### Algorithm
|
|
405
|
+
#
|
|
406
|
+
# ```
|
|
407
|
+
# a---+
|
|
408
|
+
# v
|
|
409
|
+
# A = a b c e h j l m n p
|
|
410
|
+
# B = b c d e f j k l m r s t
|
|
411
|
+
# ^
|
|
412
|
+
# b---+
|
|
413
|
+
# ```
|
|
414
|
+
#
|
|
415
|
+
# #### Matches
|
|
416
|
+
#
|
|
417
|
+
# If there are two arrows (`a` and `b`) pointing to elements of sequences `A` and `B`,
|
|
418
|
+
# the arrows will initially point to the first elements of their respective sequences.
|
|
419
|
+
# #traverse_sequences will advance the arrows through the sequences one element at
|
|
420
|
+
# a time, calling a method on the user-specified callback object before each advance. It
|
|
421
|
+
# will advance the arrows in such a way that if there are elements `A[i]` and
|
|
422
|
+
# `B[j]` which are both equal and part of the longest common subsequence, there will be
|
|
423
|
+
# some moment during the execution of #traverse_sequences when arrow `a` is pointing to
|
|
424
|
+
# `A[i]` and arrow `b` is pointing to `B[j]`. When this happens, #traverse_sequences
|
|
425
|
+
# will call `callbacks#match` and then it will advance both arrows.
|
|
426
|
+
#
|
|
427
|
+
# #### Discards
|
|
428
|
+
#
|
|
429
|
+
# Otherwise, one of the arrows is pointing to an element of its sequence that is not
|
|
430
|
+
# part of the longest common subsequence. #traverse_sequences will advance that arrow
|
|
431
|
+
# and will call `callbacks#discard_a` or `callbacks#discard_b`, depending on which arrow
|
|
432
|
+
# it advanced.
|
|
433
|
+
#
|
|
434
|
+
# #### Changes
|
|
435
|
+
#
|
|
436
|
+
# If both `a` and `b` point to elements that are not part of the longest common
|
|
437
|
+
# subsequence, then #traverse_sequences will try to call `callbacks#change` and advance
|
|
438
|
+
# both arrows. If `callbacks#change` is not implemented, then `callbacks#discard_a` and
|
|
439
|
+
# `callbacks#discard_b` will be called in turn.
|
|
440
|
+
#
|
|
441
|
+
# The methods for `callbacks#match`, `callbacks#discard_a`, `callbacks#discard_b`, and
|
|
442
|
+
# `callbacks#change` are invoked with an event comprising the action ("=", "+", "-", or
|
|
443
|
+
# "!", respectively), the indexes `i` and `j`, and the elements `A[i]` and `B[j]`.
|
|
469
444
|
# Return values are discarded by #traverse_balanced.
|
|
470
445
|
#
|
|
471
446
|
# === Context
|
|
472
447
|
#
|
|
473
|
-
# Note that
|
|
474
|
-
#
|
|
475
|
-
def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
|
|
448
|
+
# Note that `i` and `j` may not be the same index position, even if `a` and `b` are
|
|
449
|
+
# considered to be pointing to matching or changed elements.
|
|
450
|
+
def self.traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
|
|
476
451
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
|
477
452
|
a_size = seq1.size
|
|
478
453
|
b_size = seq2.size
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
string = seq1.is_a?(String)
|
|
454
|
+
a_i = b_j = m_b = 0
|
|
455
|
+
m_a = -1
|
|
482
456
|
|
|
483
457
|
# Process all the lines in the match vector.
|
|
484
458
|
loop do
|
|
485
|
-
# Find next match
|
|
459
|
+
# Find next match indexes `m_a` and `m_b`
|
|
486
460
|
loop do
|
|
487
|
-
|
|
488
|
-
break unless
|
|
461
|
+
m_a += 1
|
|
462
|
+
break unless m_a < matches.size && matches[m_a].nil?
|
|
489
463
|
end
|
|
490
464
|
|
|
491
|
-
break if
|
|
465
|
+
break if m_a >= matches.size # end of matches?
|
|
492
466
|
|
|
493
|
-
|
|
467
|
+
m_b = matches[m_a]
|
|
494
468
|
|
|
495
469
|
# Change(seq2)
|
|
496
|
-
while (
|
|
497
|
-
|
|
498
|
-
|
|
470
|
+
while (a_i < m_a) || (b_j < m_b)
|
|
471
|
+
a_x = seq1[a_i]
|
|
472
|
+
b_x = seq2[b_j]
|
|
499
473
|
|
|
500
|
-
case [(
|
|
474
|
+
case [(a_i < m_a), (b_j < m_b)]
|
|
501
475
|
when [true, true]
|
|
502
476
|
if callbacks.respond_to?(:change)
|
|
503
|
-
event = Diff::LCS::ContextChange.new("!",
|
|
477
|
+
event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
|
|
504
478
|
event = yield event if block_given?
|
|
505
479
|
callbacks.change(event)
|
|
506
|
-
|
|
480
|
+
a_i += 1
|
|
507
481
|
else
|
|
508
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
482
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
509
483
|
event = yield event if block_given?
|
|
510
484
|
callbacks.discard_a(event)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
485
|
+
a_i += 1
|
|
486
|
+
a_x = seq1[a_i]
|
|
487
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
514
488
|
event = yield event if block_given?
|
|
515
489
|
callbacks.discard_b(event)
|
|
516
490
|
end
|
|
517
491
|
|
|
518
|
-
|
|
492
|
+
b_j += 1
|
|
519
493
|
when [true, false]
|
|
520
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
494
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
521
495
|
event = yield event if block_given?
|
|
522
496
|
callbacks.discard_a(event)
|
|
523
|
-
|
|
497
|
+
a_i += 1
|
|
524
498
|
when [false, true]
|
|
525
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
499
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
526
500
|
event = yield event if block_given?
|
|
527
501
|
callbacks.discard_b(event)
|
|
528
|
-
|
|
502
|
+
b_j += 1
|
|
529
503
|
end
|
|
530
504
|
end
|
|
531
505
|
|
|
532
506
|
# Match
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
event = Diff::LCS::ContextChange.new("=",
|
|
507
|
+
a_x = seq1[a_i]
|
|
508
|
+
b_x = seq2[b_j]
|
|
509
|
+
event = Diff::LCS::ContextChange.new("=", a_i, a_x, b_j, b_x)
|
|
536
510
|
event = yield event if block_given?
|
|
537
511
|
callbacks.match(event)
|
|
538
|
-
|
|
539
|
-
|
|
512
|
+
a_i += 1
|
|
513
|
+
b_j += 1
|
|
540
514
|
end
|
|
541
515
|
|
|
542
|
-
while (
|
|
543
|
-
|
|
544
|
-
|
|
516
|
+
while (a_i < a_size) || (b_j < b_size)
|
|
517
|
+
a_x = seq1[a_i]
|
|
518
|
+
b_x = seq2[b_j]
|
|
545
519
|
|
|
546
|
-
case [(
|
|
520
|
+
case [(a_i < a_size), (b_j < b_size)]
|
|
547
521
|
when [true, true]
|
|
548
522
|
if callbacks.respond_to?(:change)
|
|
549
|
-
event = Diff::LCS::ContextChange.new("!",
|
|
523
|
+
event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
|
|
550
524
|
event = yield event if block_given?
|
|
551
525
|
callbacks.change(event)
|
|
552
|
-
|
|
526
|
+
a_i += 1
|
|
553
527
|
else
|
|
554
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
528
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
555
529
|
event = yield event if block_given?
|
|
556
530
|
callbacks.discard_a(event)
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
531
|
+
a_i += 1
|
|
532
|
+
a_x = seq1[a_i]
|
|
533
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
560
534
|
event = yield event if block_given?
|
|
561
535
|
callbacks.discard_b(event)
|
|
562
536
|
end
|
|
563
537
|
|
|
564
|
-
|
|
538
|
+
b_j += 1
|
|
565
539
|
when [true, false]
|
|
566
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
540
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
567
541
|
event = yield event if block_given?
|
|
568
542
|
callbacks.discard_a(event)
|
|
569
|
-
|
|
543
|
+
a_i += 1
|
|
570
544
|
when [false, true]
|
|
571
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
545
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
572
546
|
event = yield event if block_given?
|
|
573
547
|
callbacks.discard_b(event)
|
|
574
|
-
|
|
548
|
+
b_j += 1
|
|
575
549
|
end
|
|
576
550
|
end
|
|
577
551
|
end
|
|
@@ -581,61 +555,67 @@ class << Diff::LCS
|
|
|
581
555
|
:patch => {"+" => "+", "-" => "-", "!" => "!", "=" => "="}.freeze,
|
|
582
556
|
:unpatch => {"+" => "-", "-" => "+", "!" => "!", "=" => "="}.freeze
|
|
583
557
|
}.freeze
|
|
558
|
+
private_constant :PATCH_MAP
|
|
584
559
|
# standard:enable Style/HashSyntax
|
|
585
560
|
|
|
586
|
-
# Applies a
|
|
587
|
-
#
|
|
561
|
+
# Applies a `patchset` to the sequence `src` according to the `direction` (`:patch` or
|
|
562
|
+
# `:unpatch`), producing a new sequence.
|
|
588
563
|
#
|
|
589
|
-
# If the
|
|
590
|
-
#
|
|
564
|
+
# If the `direction` is not specified, Diff::LCS::patch will attempt to discover the
|
|
565
|
+
# direction of the `patchset`.
|
|
591
566
|
#
|
|
592
|
-
# A
|
|
593
|
-
#
|
|
567
|
+
# A `patchset` can be considered to apply forward (`:patch`) if the following expression
|
|
568
|
+
# is true:
|
|
594
569
|
#
|
|
595
|
-
#
|
|
570
|
+
# ```ruby
|
|
571
|
+
# patch(s1, diff(s1, s2)) # => s2
|
|
572
|
+
# ```
|
|
596
573
|
#
|
|
597
|
-
# A
|
|
598
|
-
#
|
|
574
|
+
# A `patchset` can be considered to apply backward (`:unpatch`) if the following
|
|
575
|
+
# expression is true:
|
|
599
576
|
#
|
|
600
|
-
#
|
|
577
|
+
# ```ruby
|
|
578
|
+
# patch(s2, diff(s1, s2)) # => s1
|
|
579
|
+
# ```
|
|
601
580
|
#
|
|
602
|
-
# If the
|
|
603
|
-
#
|
|
604
|
-
#
|
|
581
|
+
# If the `patchset` contains no changes, the `src` value will be returned as either
|
|
582
|
+
# `src.dup` or `src`. A `patchset` can be deemed as having no changes if the following
|
|
583
|
+
# predicate returns true:
|
|
605
584
|
#
|
|
606
|
-
#
|
|
607
|
-
#
|
|
585
|
+
# ```ruby
|
|
586
|
+
# patchset.empty? or patchset.flatten(1).all? { |change| change.unchanged? }
|
|
587
|
+
# ```
|
|
608
588
|
#
|
|
609
|
-
#
|
|
589
|
+
# ### Patchsets
|
|
610
590
|
#
|
|
611
|
-
# A
|
|
612
|
-
#
|
|
613
|
-
# changes:
|
|
591
|
+
# A `patchset` is always an enumerable sequence of changes, hunks of changes, or a mix
|
|
592
|
+
# of the two. A hunk of changes is an enumerable sequence of changes:
|
|
614
593
|
#
|
|
615
|
-
#
|
|
616
|
-
#
|
|
617
|
-
#
|
|
618
|
-
#
|
|
619
|
-
#
|
|
620
|
-
#
|
|
594
|
+
# ```
|
|
595
|
+
# [ # patchset
|
|
596
|
+
# # change
|
|
597
|
+
# [ # hunk
|
|
598
|
+
# # change
|
|
599
|
+
# ]
|
|
600
|
+
# ]
|
|
601
|
+
# ```
|
|
621
602
|
#
|
|
622
|
-
# The
|
|
623
|
-
#
|
|
624
|
-
#
|
|
625
|
-
#
|
|
626
|
-
def patch(src, patchset, direction = nil)
|
|
603
|
+
# The `patch` method accepts `patchset`s that are enumerable sequences containing either
|
|
604
|
+
# Diff::LCS::Change objects (or a subclass) or the array representations of those
|
|
605
|
+
# objects. Prior to application, array representations of Diff::LCS::Change objects will
|
|
606
|
+
# be reified.
|
|
607
|
+
def self.patch(src, patchset, direction = nil)
|
|
627
608
|
# Normalize the patchset.
|
|
628
609
|
has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset)
|
|
629
610
|
|
|
630
611
|
return src.respond_to?(:dup) ? src.dup : src unless has_changes
|
|
631
612
|
|
|
632
|
-
string = src.is_a?(String)
|
|
633
613
|
# Start with a new empty type of the source's class
|
|
634
614
|
res = src.class.new
|
|
635
615
|
|
|
636
616
|
direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset)
|
|
637
617
|
|
|
638
|
-
|
|
618
|
+
a_i = b_j = 0
|
|
639
619
|
|
|
640
620
|
patch_map = PATCH_MAP[direction]
|
|
641
621
|
|
|
@@ -658,84 +638,78 @@ class << Diff::LCS
|
|
|
658
638
|
|
|
659
639
|
case action
|
|
660
640
|
when "-" # Remove details from the old string
|
|
661
|
-
while
|
|
662
|
-
res <<
|
|
663
|
-
|
|
664
|
-
|
|
641
|
+
while a_i < op
|
|
642
|
+
res << src[a_i]
|
|
643
|
+
a_i += 1
|
|
644
|
+
b_j += 1
|
|
665
645
|
end
|
|
666
|
-
|
|
646
|
+
a_i += 1
|
|
667
647
|
when "+"
|
|
668
|
-
while
|
|
669
|
-
res <<
|
|
670
|
-
|
|
671
|
-
|
|
648
|
+
while b_j < np
|
|
649
|
+
res << src[a_i]
|
|
650
|
+
a_i += 1
|
|
651
|
+
b_j += 1
|
|
672
652
|
end
|
|
673
653
|
|
|
674
654
|
res << el
|
|
675
|
-
|
|
655
|
+
b_j += 1
|
|
676
656
|
when "="
|
|
677
657
|
# This only appears in sdiff output with the SDiff callback.
|
|
678
658
|
# Therefore, we only need to worry about dealing with a single
|
|
679
659
|
# element.
|
|
680
660
|
res << el
|
|
681
661
|
|
|
682
|
-
|
|
683
|
-
|
|
662
|
+
a_i += 1
|
|
663
|
+
b_j += 1
|
|
684
664
|
when "!"
|
|
685
|
-
while
|
|
686
|
-
res <<
|
|
687
|
-
|
|
688
|
-
|
|
665
|
+
while a_i < op
|
|
666
|
+
res << src[a_i]
|
|
667
|
+
a_i += 1
|
|
668
|
+
b_j += 1
|
|
689
669
|
end
|
|
690
670
|
|
|
691
|
-
|
|
692
|
-
|
|
671
|
+
b_j += 1
|
|
672
|
+
a_i += 1
|
|
693
673
|
|
|
694
674
|
res << el
|
|
695
675
|
end
|
|
696
676
|
when Diff::LCS::Change
|
|
697
677
|
case action
|
|
698
678
|
when "-"
|
|
699
|
-
while
|
|
700
|
-
res <<
|
|
701
|
-
|
|
702
|
-
|
|
679
|
+
while a_i < change.position
|
|
680
|
+
res << src[a_i]
|
|
681
|
+
a_i += 1
|
|
682
|
+
b_j += 1
|
|
703
683
|
end
|
|
704
|
-
|
|
684
|
+
a_i += 1
|
|
705
685
|
when "+"
|
|
706
|
-
while
|
|
707
|
-
res <<
|
|
708
|
-
|
|
709
|
-
|
|
686
|
+
while b_j < change.position
|
|
687
|
+
res << src[a_i]
|
|
688
|
+
a_i += 1
|
|
689
|
+
b_j += 1
|
|
710
690
|
end
|
|
711
691
|
|
|
712
|
-
|
|
692
|
+
b_j += 1
|
|
713
693
|
|
|
714
694
|
res << change.element
|
|
715
695
|
end
|
|
716
696
|
end
|
|
717
697
|
end
|
|
718
698
|
|
|
719
|
-
while
|
|
720
|
-
res <<
|
|
721
|
-
|
|
722
|
-
|
|
699
|
+
while a_i < src.size
|
|
700
|
+
res << src[a_i]
|
|
701
|
+
a_i += 1
|
|
702
|
+
b_j += 1
|
|
723
703
|
end
|
|
724
704
|
|
|
725
705
|
res
|
|
726
706
|
end
|
|
727
707
|
|
|
728
|
-
# Given a
|
|
729
|
-
#
|
|
730
|
-
def unpatch!(src, patchset)
|
|
731
|
-
patch(src, patchset, :unpatch)
|
|
732
|
-
end
|
|
708
|
+
# Given a patchset, convert the current version to the prior version. Does no
|
|
709
|
+
# auto-discovery.
|
|
710
|
+
def self.unpatch!(src, patchset) = patch(src, patchset, :unpatch)
|
|
733
711
|
|
|
734
|
-
# Given a
|
|
735
|
-
#
|
|
736
|
-
def patch!(src, patchset)
|
|
737
|
-
patch(src, patchset, :patch)
|
|
738
|
-
end
|
|
712
|
+
# Given a patchset, convert the current version to the next version. Does no
|
|
713
|
+
# auto-discovery.
|
|
714
|
+
def self.patch!(src, patchset) = patch(src, patchset, :patch)
|
|
739
715
|
end
|
|
740
|
-
|
|
741
|
-
require "diff/lcs/backports"
|