diff-lcs 1.6.2 → 2.0.0.beta.2
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 +65 -4
- data/CONTRIBUTING.md +91 -35
- data/CONTRIBUTORS.md +19 -9
- data/LICENCE.md +39 -11
- data/Manifest.txt +91 -83
- data/README.md +13 -9
- data/Rakefile +99 -73
- data/SECURITY.md +22 -27
- 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 +92 -155
- data/lib/diff/lcs/internals.rb +92 -96
- data/lib/diff/lcs/ldiff.rb +30 -38
- data/lib/diff/lcs/version.rb +1 -1
- data/lib/diff/lcs.rb +439 -466
- data/licenses/dco.txt +34 -0
- data/spec/hunk_spec.rb +32 -45
- data/spec/lcs_spec.rb +6 -6
- data/spec/ldiff_spec.rb +8 -8
- data/spec/spec_helper.rb +17 -27
- data/test/fixtures/ldiff/output.diff-c +7 -0
- data/test/fixtures/ldiff/output.diff-u +5 -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-c +9 -0
- data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u +7 -0
- data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c +9 -0
- data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u +7 -0
- data/test/fixtures/ldiff/output.diff.issue95_trailing_context-c +9 -0
- data/test/fixtures/ldiff/output.diff.issue95_trailing_context-u +6 -0
- data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-c +2 -2
- data/test/fixtures/ldiff/output.diff.missing_new_line1-u +9 -0
- data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-c +2 -2
- 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 +199 -110
- data/.rspec +0 -1
- data/bin/htmldiff +0 -35
- data/lib/diff/lcs/backports.rb +0 -13
- data/lib/diff/lcs/htmldiff.rb +0 -160
- data/mise.toml +0 -5
- data/spec/fixtures/ldiff/output.diff-c +0 -7
- data/spec/fixtures/ldiff/output.diff-e +0 -3
- data/spec/fixtures/ldiff/output.diff-f +0 -3
- data/spec/fixtures/ldiff/output.diff-u +0 -5
- data/spec/fixtures/ldiff/output.diff.bin2 +0 -1
- data/spec/fixtures/ldiff/output.diff.bin2-c +0 -1
- data/spec/fixtures/ldiff/output.diff.bin2-e +0 -1
- data/spec/fixtures/ldiff/output.diff.bin2-f +0 -1
- data/spec/fixtures/ldiff/output.diff.bin2-u +0 -1
- 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/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c +0 -9
- data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u +0 -7
- data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c +0 -9
- data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u +0 -7
- data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c +0 -9
- data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u +0 -6
- data/spec/fixtures/ldiff/output.diff.missing_new_line1-u +0 -9
- data/spec/fixtures/ldiff/output.diff.missing_new_line2-u +0 -9
- /data/{docs → licenses}/COPYING.txt +0 -0
- /data/{docs → licenses}/artistic.txt +0 -0
- /data/{spec → test}/fixtures/123_x +0 -0
- /data/{spec → test}/fixtures/456_x +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/empty +0 -0
- /data/{spec → test}/fixtures/file1.bin +0 -0
- /data/{spec → test}/fixtures/file2.bin +0 -0
- /data/{spec → test}/fixtures/four_lines +0 -0
- /data/{spec → test}/fixtures/four_lines_with_missing_new_line +0 -0
- /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-e +0 -0
- /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-f +0 -0
- /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-e +0 -0
- /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-f +0 -0
- /data/{spec → test}/fixtures/ldiff/error.diff.chef-e +0 -0
- /data/{spec → test}/fixtures/ldiff/error.diff.chef-f +0 -0
- /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-e +0 -0
- /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-f +0 -0
- /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-e +0 -0
- /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-f +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.bin1 +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.bin1-c +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.bin1-e +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.bin1-f +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.bin1-u +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/ldiff/output.diff.empty.vs.four_lines +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-e +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-f +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-e +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-f +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-e +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-f +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1 +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-e +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-f +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2 +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-e +0 -0
- /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-f +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,52 +2,62 @@
|
|
|
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
62
|
end
|
|
53
63
|
|
|
@@ -56,71 +66,47 @@ 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. This means that those objects must implement
|
|
66
|
-
# the methods +#hash+ and +#eql?+ such that two objects containing identical values
|
|
67
|
-
# compare identically for key purposes. That is:
|
|
68
|
-
#
|
|
69
|
-
# O.new('a').eql?(O.new('a')) == true &&
|
|
70
|
-
# O.new('a').hash == O.new('a').hash
|
|
71
|
-
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
|
|
72
72
|
Diff::LCS.lcs(self, other, &block)
|
|
73
|
-
end
|
|
74
73
|
|
|
75
|
-
# Returns the difference set between
|
|
76
|
-
def diff(other, callbacks = nil, &block)
|
|
77
|
-
Diff::LCS.diff(self, other, callbacks, &block)
|
|
78
|
-
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)
|
|
79
76
|
|
|
80
|
-
# Returns the balanced ("side-by-side") difference set between
|
|
81
|
-
#
|
|
82
|
-
def sdiff(other, callbacks = nil, &block)
|
|
83
|
-
Diff::LCS.sdiff(self, other, callbacks, &block)
|
|
84
|
-
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)
|
|
85
80
|
|
|
86
|
-
# Traverses the discovered longest common subsequences between
|
|
87
|
-
#
|
|
88
|
-
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) =
|
|
89
84
|
Diff::LCS.traverse_sequences(self, other, callbacks || Diff::LCS::SequenceCallbacks, &block)
|
|
90
|
-
end
|
|
91
85
|
|
|
92
|
-
# Traverses the discovered longest common subsequences between
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
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) =
|
|
96
89
|
Diff::LCS.traverse_balanced(self, other, callbacks || Diff::LCS::BalancedCallbacks, &block)
|
|
97
|
-
end
|
|
98
90
|
|
|
99
|
-
# Attempts to patch
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
def patch(patchset)
|
|
103
|
-
Diff::LCS.patch(self, patchset)
|
|
104
|
-
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)
|
|
105
95
|
alias_method :unpatch, :patch
|
|
106
96
|
|
|
107
|
-
# Attempts to patch
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
def patch!(patchset)
|
|
111
|
-
Diff::LCS.patch!(self, patchset)
|
|
112
|
-
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)
|
|
113
101
|
|
|
114
|
-
# Attempts to unpatch
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
def unpatch!(patchset)
|
|
118
|
-
Diff::LCS.unpatch!(self, patchset)
|
|
119
|
-
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)
|
|
120
106
|
|
|
121
|
-
# Attempts to patch
|
|
122
|
-
#
|
|
123
|
-
#
|
|
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.
|
|
124
110
|
def patch_me(patchset)
|
|
125
111
|
if respond_to? :replace
|
|
126
112
|
replace(patch!(patchset))
|
|
@@ -129,9 +115,9 @@ module Diff::LCS
|
|
|
129
115
|
end
|
|
130
116
|
end
|
|
131
117
|
|
|
132
|
-
# Attempts to unpatch
|
|
133
|
-
#
|
|
134
|
-
#
|
|
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.
|
|
135
121
|
def unpatch_me(patchset)
|
|
136
122
|
if respond_to? :replace
|
|
137
123
|
replace(unpatch!(patchset))
|
|
@@ -139,440 +125,427 @@ module Diff::LCS
|
|
|
139
125
|
unpatch!(patchset)
|
|
140
126
|
end
|
|
141
127
|
end
|
|
142
|
-
end
|
|
143
128
|
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
146
141
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
}
|
|
157
152
|
end
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
# Class argument is provided for +callbacks+, #diff will attempt to
|
|
167
|
-
# initialise it. If the +callbacks+ object (possibly initialised) responds to
|
|
168
|
-
# #finish, it will be called.
|
|
169
|
-
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
|
|
170
161
|
diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks, &block)
|
|
171
|
-
end
|
|
172
162
|
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
# #finish, it will be called.
|
|
187
|
-
#
|
|
188
|
-
# Each element of a returned array is a Diff::LCS::ContextChange object,
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
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
|
|
200
190
|
# end
|
|
201
|
-
|
|
191
|
+
# end
|
|
192
|
+
# ```
|
|
193
|
+
def self.sdiff(seq1, seq2, callbacks = nil, &block) = # :yields: diff changes
|
|
202
194
|
diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks, &block)
|
|
203
|
-
end
|
|
204
195
|
|
|
205
|
-
# #traverse_sequences is the most general facility provided by this module;
|
|
206
|
-
# #
|
|
207
|
-
#
|
|
208
|
-
# The arguments to #
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
# callbacks#match
|
|
218
|
-
#
|
|
219
|
-
# callbacks#discard_a
|
|
220
|
-
#
|
|
221
|
-
# callbacks#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
# If there are two arrows (
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
# advance
|
|
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
|
-
|
|
279
|
-
# each element of +A+ until the end of the sequence is reached (<tt>A[i]</tt>
|
|
280
|
-
# and <tt>B[-1]</tt>).
|
|
281
|
-
#
|
|
282
|
-
# There is a chance that one additional <tt>callbacks#discard_a</tt> or
|
|
283
|
-
# <tt>callbacks#discard_b</tt> will be called after the end of the sequence
|
|
284
|
-
# is reached, if +a+ has not yet reached the end of +A+ or +b+ has not yet
|
|
285
|
-
# reached the end of +B+.
|
|
286
|
-
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
|
|
287
270
|
callbacks ||= Diff::LCS::SequenceCallbacks
|
|
288
271
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
|
289
272
|
|
|
290
273
|
run_finished_a = run_finished_b = false
|
|
291
|
-
string = seq1.is_a?(String)
|
|
292
274
|
|
|
293
275
|
a_size = seq1.size
|
|
294
276
|
b_size = seq2.size
|
|
295
|
-
|
|
277
|
+
a_i = b_j = 0
|
|
296
278
|
|
|
297
279
|
matches.each do |b_line|
|
|
298
280
|
if b_line.nil?
|
|
299
|
-
unless seq1[
|
|
300
|
-
|
|
301
|
-
|
|
281
|
+
unless seq1[a_i].nil?
|
|
282
|
+
a_x = seq1[a_i]
|
|
283
|
+
b_x = seq2[b_j]
|
|
302
284
|
|
|
303
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
285
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
304
286
|
event = yield event if block_given?
|
|
305
287
|
callbacks.discard_a(event)
|
|
306
288
|
end
|
|
307
289
|
else
|
|
308
|
-
|
|
290
|
+
a_x = seq1[a_i]
|
|
309
291
|
|
|
310
292
|
loop do
|
|
311
|
-
break unless
|
|
293
|
+
break unless b_j < b_line
|
|
312
294
|
|
|
313
|
-
|
|
314
|
-
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)
|
|
315
297
|
event = yield event if block_given?
|
|
316
298
|
callbacks.discard_b(event)
|
|
317
|
-
|
|
299
|
+
b_j += 1
|
|
318
300
|
end
|
|
319
|
-
|
|
320
|
-
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)
|
|
321
303
|
event = yield event if block_given?
|
|
322
304
|
callbacks.match(event)
|
|
323
|
-
|
|
305
|
+
b_j += 1
|
|
324
306
|
end
|
|
325
|
-
|
|
307
|
+
|
|
308
|
+
a_i += 1
|
|
326
309
|
end
|
|
327
310
|
|
|
328
|
-
# The last entry (if any) processed was a match.
|
|
329
|
-
#
|
|
330
|
-
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)
|
|
331
314
|
# last A?
|
|
332
|
-
if
|
|
315
|
+
if a_i == a_size && b_j < b_size
|
|
333
316
|
if callbacks.respond_to?(:finished_a) && !run_finished_a
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
event = Diff::LCS::ContextChange.new(">", a_size - 1,
|
|
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)
|
|
337
320
|
event = yield event if block_given?
|
|
338
321
|
callbacks.finished_a(event)
|
|
339
322
|
run_finished_a = true
|
|
340
323
|
else
|
|
341
|
-
|
|
324
|
+
a_x = seq1[a_i]
|
|
342
325
|
loop do
|
|
343
|
-
|
|
344
|
-
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)
|
|
345
328
|
event = yield event if block_given?
|
|
346
329
|
callbacks.discard_b(event)
|
|
347
|
-
|
|
348
|
-
break unless
|
|
330
|
+
b_j += 1
|
|
331
|
+
break unless b_j < b_size
|
|
349
332
|
end
|
|
350
333
|
end
|
|
351
334
|
end
|
|
352
335
|
|
|
353
336
|
# last B?
|
|
354
|
-
if
|
|
337
|
+
if b_j == b_size && a_i < a_size
|
|
355
338
|
if callbacks.respond_to?(:finished_b) && !run_finished_b
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
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)
|
|
359
342
|
event = yield event if block_given?
|
|
360
343
|
callbacks.finished_b(event)
|
|
361
344
|
run_finished_b = true
|
|
362
345
|
else
|
|
363
|
-
|
|
346
|
+
b_x = seq2[b_j]
|
|
364
347
|
loop do
|
|
365
|
-
|
|
366
|
-
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)
|
|
367
350
|
event = yield event if block_given?
|
|
368
351
|
callbacks.discard_a(event)
|
|
369
|
-
|
|
370
|
-
break unless
|
|
352
|
+
a_i += 1
|
|
353
|
+
break unless b_j < b_size
|
|
371
354
|
end
|
|
372
355
|
end
|
|
373
356
|
end
|
|
374
357
|
|
|
375
|
-
if
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
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)
|
|
379
362
|
event = yield event if block_given?
|
|
380
363
|
callbacks.discard_a(event)
|
|
381
|
-
|
|
364
|
+
a_i += 1
|
|
382
365
|
end
|
|
383
366
|
|
|
384
|
-
if
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
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)
|
|
388
371
|
event = yield event if block_given?
|
|
389
372
|
callbacks.discard_b(event)
|
|
390
|
-
|
|
373
|
+
b_j += 1
|
|
391
374
|
end
|
|
392
375
|
end
|
|
393
376
|
end
|
|
394
377
|
|
|
395
|
-
# #traverse_balanced is an alternative to #traverse_sequences. It uses a
|
|
396
|
-
#
|
|
397
|
-
#
|
|
398
|
-
#
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
#
|
|
402
|
-
#
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
#
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
#
|
|
414
|
-
# callbacks#
|
|
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
|
-
#
|
|
461
|
-
# <tt>callbacks#change</tt> and advance both arrows. If
|
|
462
|
-
# <tt>callbacks#change</tt> is not implemented, then
|
|
463
|
-
# <tt>callbacks#discard_a</tt> and <tt>callbacks#discard_b</tt> will be
|
|
464
|
-
# called in turn.
|
|
465
|
-
#
|
|
466
|
-
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
|
|
467
|
-
# <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are invoked
|
|
468
|
-
# with an event comprising the action ("=", "+", "-", or "!", respectively),
|
|
469
|
-
# the indexes +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]`.
|
|
470
444
|
# Return values are discarded by #traverse_balanced.
|
|
471
445
|
#
|
|
472
446
|
# === Context
|
|
473
447
|
#
|
|
474
|
-
# Note that
|
|
475
|
-
#
|
|
476
|
-
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)
|
|
477
451
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
|
478
452
|
a_size = seq1.size
|
|
479
453
|
b_size = seq2.size
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
string = seq1.is_a?(String)
|
|
454
|
+
a_i = b_j = m_b = 0
|
|
455
|
+
m_a = -1
|
|
483
456
|
|
|
484
457
|
# Process all the lines in the match vector.
|
|
485
458
|
loop do
|
|
486
|
-
# Find next match indexes
|
|
459
|
+
# Find next match indexes `m_a` and `m_b`
|
|
487
460
|
loop do
|
|
488
|
-
|
|
489
|
-
break unless
|
|
461
|
+
m_a += 1
|
|
462
|
+
break unless m_a < matches.size && matches[m_a].nil?
|
|
490
463
|
end
|
|
491
464
|
|
|
492
|
-
break if
|
|
465
|
+
break if m_a >= matches.size # end of matches?
|
|
493
466
|
|
|
494
|
-
|
|
467
|
+
m_b = matches[m_a]
|
|
495
468
|
|
|
496
469
|
# Change(seq2)
|
|
497
|
-
while (
|
|
498
|
-
|
|
499
|
-
|
|
470
|
+
while (a_i < m_a) || (b_j < m_b)
|
|
471
|
+
a_x = seq1[a_i]
|
|
472
|
+
b_x = seq2[b_j]
|
|
500
473
|
|
|
501
|
-
case [(
|
|
474
|
+
case [(a_i < m_a), (b_j < m_b)]
|
|
502
475
|
when [true, true]
|
|
503
476
|
if callbacks.respond_to?(:change)
|
|
504
|
-
event = Diff::LCS::ContextChange.new("!",
|
|
477
|
+
event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
|
|
505
478
|
event = yield event if block_given?
|
|
506
479
|
callbacks.change(event)
|
|
507
|
-
|
|
480
|
+
a_i += 1
|
|
508
481
|
else
|
|
509
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
482
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
510
483
|
event = yield event if block_given?
|
|
511
484
|
callbacks.discard_a(event)
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
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)
|
|
515
488
|
event = yield event if block_given?
|
|
516
489
|
callbacks.discard_b(event)
|
|
517
490
|
end
|
|
518
491
|
|
|
519
|
-
|
|
492
|
+
b_j += 1
|
|
520
493
|
when [true, false]
|
|
521
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
494
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
522
495
|
event = yield event if block_given?
|
|
523
496
|
callbacks.discard_a(event)
|
|
524
|
-
|
|
497
|
+
a_i += 1
|
|
525
498
|
when [false, true]
|
|
526
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
499
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
527
500
|
event = yield event if block_given?
|
|
528
501
|
callbacks.discard_b(event)
|
|
529
|
-
|
|
502
|
+
b_j += 1
|
|
530
503
|
end
|
|
531
504
|
end
|
|
532
505
|
|
|
533
506
|
# Match
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
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)
|
|
537
510
|
event = yield event if block_given?
|
|
538
511
|
callbacks.match(event)
|
|
539
|
-
|
|
540
|
-
|
|
512
|
+
a_i += 1
|
|
513
|
+
b_j += 1
|
|
541
514
|
end
|
|
542
515
|
|
|
543
|
-
while (
|
|
544
|
-
|
|
545
|
-
|
|
516
|
+
while (a_i < a_size) || (b_j < b_size)
|
|
517
|
+
a_x = seq1[a_i]
|
|
518
|
+
b_x = seq2[b_j]
|
|
546
519
|
|
|
547
|
-
case [(
|
|
520
|
+
case [(a_i < a_size), (b_j < b_size)]
|
|
548
521
|
when [true, true]
|
|
549
522
|
if callbacks.respond_to?(:change)
|
|
550
|
-
event = Diff::LCS::ContextChange.new("!",
|
|
523
|
+
event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
|
|
551
524
|
event = yield event if block_given?
|
|
552
525
|
callbacks.change(event)
|
|
553
|
-
|
|
526
|
+
a_i += 1
|
|
554
527
|
else
|
|
555
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
528
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
556
529
|
event = yield event if block_given?
|
|
557
530
|
callbacks.discard_a(event)
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
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)
|
|
561
534
|
event = yield event if block_given?
|
|
562
535
|
callbacks.discard_b(event)
|
|
563
536
|
end
|
|
564
537
|
|
|
565
|
-
|
|
538
|
+
b_j += 1
|
|
566
539
|
when [true, false]
|
|
567
|
-
event = Diff::LCS::ContextChange.new("-",
|
|
540
|
+
event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
|
|
568
541
|
event = yield event if block_given?
|
|
569
542
|
callbacks.discard_a(event)
|
|
570
|
-
|
|
543
|
+
a_i += 1
|
|
571
544
|
when [false, true]
|
|
572
|
-
event = Diff::LCS::ContextChange.new("+",
|
|
545
|
+
event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
|
|
573
546
|
event = yield event if block_given?
|
|
574
547
|
callbacks.discard_b(event)
|
|
575
|
-
|
|
548
|
+
b_j += 1
|
|
576
549
|
end
|
|
577
550
|
end
|
|
578
551
|
end
|
|
@@ -582,61 +555,67 @@ class << Diff::LCS
|
|
|
582
555
|
:patch => {"+" => "+", "-" => "-", "!" => "!", "=" => "="}.freeze,
|
|
583
556
|
:unpatch => {"+" => "-", "-" => "+", "!" => "!", "=" => "="}.freeze
|
|
584
557
|
}.freeze
|
|
558
|
+
private_constant :PATCH_MAP
|
|
585
559
|
# standard:enable Style/HashSyntax
|
|
586
560
|
|
|
587
|
-
# Applies a
|
|
588
|
-
#
|
|
561
|
+
# Applies a `patchset` to the sequence `src` according to the `direction` (`:patch` or
|
|
562
|
+
# `:unpatch`), producing a new sequence.
|
|
589
563
|
#
|
|
590
|
-
# If the
|
|
591
|
-
#
|
|
564
|
+
# If the `direction` is not specified, Diff::LCS::patch will attempt to discover the
|
|
565
|
+
# direction of the `patchset`.
|
|
592
566
|
#
|
|
593
|
-
# A
|
|
594
|
-
#
|
|
567
|
+
# A `patchset` can be considered to apply forward (`:patch`) if the following expression
|
|
568
|
+
# is true:
|
|
595
569
|
#
|
|
596
|
-
#
|
|
570
|
+
# ```ruby
|
|
571
|
+
# patch(s1, diff(s1, s2)) # => s2
|
|
572
|
+
# ```
|
|
597
573
|
#
|
|
598
|
-
# A
|
|
599
|
-
#
|
|
574
|
+
# A `patchset` can be considered to apply backward (`:unpatch`) if the following
|
|
575
|
+
# expression is true:
|
|
600
576
|
#
|
|
601
|
-
#
|
|
577
|
+
# ```ruby
|
|
578
|
+
# patch(s2, diff(s1, s2)) # => s1
|
|
579
|
+
# ```
|
|
602
580
|
#
|
|
603
|
-
# If the
|
|
604
|
-
#
|
|
605
|
-
#
|
|
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:
|
|
606
584
|
#
|
|
607
|
-
#
|
|
608
|
-
#
|
|
585
|
+
# ```ruby
|
|
586
|
+
# patchset.empty? or patchset.flatten(1).all? { |change| change.unchanged? }
|
|
587
|
+
# ```
|
|
609
588
|
#
|
|
610
|
-
#
|
|
589
|
+
# ### Patchsets
|
|
611
590
|
#
|
|
612
|
-
# A
|
|
613
|
-
#
|
|
614
|
-
# 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:
|
|
615
593
|
#
|
|
616
|
-
#
|
|
617
|
-
#
|
|
618
|
-
#
|
|
619
|
-
#
|
|
620
|
-
#
|
|
621
|
-
#
|
|
594
|
+
# ```
|
|
595
|
+
# [ # patchset
|
|
596
|
+
# # change
|
|
597
|
+
# [ # hunk
|
|
598
|
+
# # change
|
|
599
|
+
# ]
|
|
600
|
+
# ]
|
|
601
|
+
# ```
|
|
622
602
|
#
|
|
623
|
-
# The
|
|
624
|
-
#
|
|
625
|
-
#
|
|
626
|
-
#
|
|
627
|
-
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)
|
|
628
608
|
# Normalize the patchset.
|
|
629
609
|
has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset)
|
|
630
610
|
|
|
631
611
|
return src.respond_to?(:dup) ? src.dup : src unless has_changes
|
|
632
612
|
|
|
633
|
-
string = src.is_a?(String)
|
|
634
613
|
# Start with a new empty type of the source's class
|
|
635
614
|
res = src.class.new
|
|
636
615
|
|
|
637
616
|
direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset)
|
|
638
617
|
|
|
639
|
-
|
|
618
|
+
a_i = b_j = 0
|
|
640
619
|
|
|
641
620
|
patch_map = PATCH_MAP[direction]
|
|
642
621
|
|
|
@@ -659,84 +638,78 @@ class << Diff::LCS
|
|
|
659
638
|
|
|
660
639
|
case action
|
|
661
640
|
when "-" # Remove details from the old string
|
|
662
|
-
while
|
|
663
|
-
res <<
|
|
664
|
-
|
|
665
|
-
|
|
641
|
+
while a_i < op
|
|
642
|
+
res << src[a_i]
|
|
643
|
+
a_i += 1
|
|
644
|
+
b_j += 1
|
|
666
645
|
end
|
|
667
|
-
|
|
646
|
+
a_i += 1
|
|
668
647
|
when "+"
|
|
669
|
-
while
|
|
670
|
-
res <<
|
|
671
|
-
|
|
672
|
-
|
|
648
|
+
while b_j < np
|
|
649
|
+
res << src[a_i]
|
|
650
|
+
a_i += 1
|
|
651
|
+
b_j += 1
|
|
673
652
|
end
|
|
674
653
|
|
|
675
654
|
res << el
|
|
676
|
-
|
|
655
|
+
b_j += 1
|
|
677
656
|
when "="
|
|
678
657
|
# This only appears in sdiff output with the SDiff callback.
|
|
679
658
|
# Therefore, we only need to worry about dealing with a single
|
|
680
659
|
# element.
|
|
681
660
|
res << el
|
|
682
661
|
|
|
683
|
-
|
|
684
|
-
|
|
662
|
+
a_i += 1
|
|
663
|
+
b_j += 1
|
|
685
664
|
when "!"
|
|
686
|
-
while
|
|
687
|
-
res <<
|
|
688
|
-
|
|
689
|
-
|
|
665
|
+
while a_i < op
|
|
666
|
+
res << src[a_i]
|
|
667
|
+
a_i += 1
|
|
668
|
+
b_j += 1
|
|
690
669
|
end
|
|
691
670
|
|
|
692
|
-
|
|
693
|
-
|
|
671
|
+
b_j += 1
|
|
672
|
+
a_i += 1
|
|
694
673
|
|
|
695
674
|
res << el
|
|
696
675
|
end
|
|
697
676
|
when Diff::LCS::Change
|
|
698
677
|
case action
|
|
699
678
|
when "-"
|
|
700
|
-
while
|
|
701
|
-
res <<
|
|
702
|
-
|
|
703
|
-
|
|
679
|
+
while a_i < change.position
|
|
680
|
+
res << src[a_i]
|
|
681
|
+
a_i += 1
|
|
682
|
+
b_j += 1
|
|
704
683
|
end
|
|
705
|
-
|
|
684
|
+
a_i += 1
|
|
706
685
|
when "+"
|
|
707
|
-
while
|
|
708
|
-
res <<
|
|
709
|
-
|
|
710
|
-
|
|
686
|
+
while b_j < change.position
|
|
687
|
+
res << src[a_i]
|
|
688
|
+
a_i += 1
|
|
689
|
+
b_j += 1
|
|
711
690
|
end
|
|
712
691
|
|
|
713
|
-
|
|
692
|
+
b_j += 1
|
|
714
693
|
|
|
715
694
|
res << change.element
|
|
716
695
|
end
|
|
717
696
|
end
|
|
718
697
|
end
|
|
719
698
|
|
|
720
|
-
while
|
|
721
|
-
res <<
|
|
722
|
-
|
|
723
|
-
|
|
699
|
+
while a_i < src.size
|
|
700
|
+
res << src[a_i]
|
|
701
|
+
a_i += 1
|
|
702
|
+
b_j += 1
|
|
724
703
|
end
|
|
725
704
|
|
|
726
705
|
res
|
|
727
706
|
end
|
|
728
707
|
|
|
729
|
-
# Given a
|
|
730
|
-
#
|
|
731
|
-
def unpatch!(src, patchset)
|
|
732
|
-
patch(src, patchset, :unpatch)
|
|
733
|
-
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)
|
|
734
711
|
|
|
735
|
-
# Given a
|
|
736
|
-
#
|
|
737
|
-
def patch!(src, patchset)
|
|
738
|
-
patch(src, patchset, :patch)
|
|
739
|
-
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)
|
|
740
715
|
end
|
|
741
|
-
|
|
742
|
-
require "diff/lcs/backports"
|