diff-lcs 1.1.3 → 1.5.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 +7 -0
- data/.rspec +1 -0
- data/Code-of-Conduct.md +74 -0
- data/Contributing.md +119 -0
- data/History.md +400 -0
- data/{License.rdoc → License.md} +6 -5
- data/Manifest.txt +36 -4
- data/README.rdoc +35 -23
- data/Rakefile +106 -11
- data/bin/htmldiff +7 -4
- data/bin/ldiff +4 -1
- data/docs/COPYING.txt +21 -22
- data/docs/artistic.txt +127 -0
- data/lib/diff/lcs/array.rb +1 -15
- data/lib/diff/lcs/backports.rb +9 -0
- data/lib/diff/lcs/block.rb +4 -18
- data/lib/diff/lcs/callbacks.rb +233 -230
- data/lib/diff/lcs/change.rb +114 -109
- data/lib/diff/lcs/htmldiff.rb +17 -18
- data/lib/diff/lcs/hunk.rb +232 -116
- data/lib/diff/lcs/internals.rb +308 -0
- data/lib/diff/lcs/ldiff.rb +138 -177
- data/lib/diff/lcs/string.rb +1 -15
- data/lib/diff/lcs.rb +597 -963
- data/lib/diff-lcs.rb +1 -3
- data/spec/change_spec.rb +89 -0
- data/spec/diff_spec.rb +32 -16
- data/spec/fixtures/aX +1 -0
- data/spec/fixtures/bXaX +1 -0
- data/spec/fixtures/ds1.csv +50 -0
- data/spec/fixtures/ds2.csv +51 -0
- data/spec/fixtures/ldiff/output.diff +4 -0
- data/spec/fixtures/ldiff/output.diff-c +7 -0
- data/spec/fixtures/ldiff/output.diff-e +3 -0
- data/spec/fixtures/ldiff/output.diff-f +3 -0
- data/spec/fixtures/ldiff/output.diff-u +5 -0
- data/spec/fixtures/ldiff/output.diff.chef +4 -0
- data/spec/fixtures/ldiff/output.diff.chef-c +15 -0
- data/spec/fixtures/ldiff/output.diff.chef-e +3 -0
- data/spec/fixtures/ldiff/output.diff.chef-f +3 -0
- data/spec/fixtures/ldiff/output.diff.chef-u +9 -0
- data/spec/fixtures/ldiff/output.diff.chef2 +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-c +20 -0
- data/spec/fixtures/ldiff/output.diff.chef2-d +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-e +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-f +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-u +16 -0
- data/spec/fixtures/new-chef +4 -0
- data/spec/fixtures/new-chef2 +17 -0
- data/spec/fixtures/old-chef +4 -0
- data/spec/fixtures/old-chef2 +14 -0
- data/spec/hunk_spec.rb +83 -0
- data/spec/issues_spec.rb +154 -0
- data/spec/lcs_spec.rb +36 -16
- data/spec/ldiff_spec.rb +87 -0
- data/spec/patch_spec.rb +198 -172
- data/spec/sdiff_spec.rb +99 -89
- data/spec/spec_helper.rb +149 -59
- data/spec/traverse_balanced_spec.rb +191 -167
- data/spec/traverse_sequences_spec.rb +105 -51
- metadata +218 -99
- data/.gemtest +0 -0
- data/History.rdoc +0 -54
- data/diff-lcs.gemspec +0 -51
- data/docs/artistic.html +0 -289
data/lib/diff/lcs.rb
CHANGED
@@ -1,651 +1,509 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
module Diff
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
# <em>The following text is from the Perl documentation. The only changes
|
55
|
-
# have been to make the text appear better in Rdoc</em>.
|
56
|
-
#
|
57
|
-
# I once read an article written by the authors of +diff+; they said that
|
58
|
-
# they hard worked very hard on the algorithm until they found the right
|
59
|
-
# one.
|
60
|
-
#
|
61
|
-
# I think what they ended up using (and I hope someone will correct me,
|
62
|
-
# because I am not very confident about this) was the `longest common
|
63
|
-
# subsequence' method. In the LCS problem, you have two sequences of
|
64
|
-
# items:
|
65
|
-
#
|
66
|
-
# a b c d f g h j q z
|
67
|
-
# a b c d e f g i j k r x y z
|
68
|
-
#
|
69
|
-
# and you want to find the longest sequence of items that is present in
|
70
|
-
# both original sequences in the same order. That is, you want to find a
|
71
|
-
# new sequence *S* which can be obtained from the first sequence by
|
72
|
-
# deleting some items, and from the second sequence by deleting other
|
73
|
-
# items. You also want *S* to be as long as possible. In this case *S* is:
|
74
|
-
#
|
75
|
-
# a b c d f g j z
|
76
|
-
#
|
77
|
-
# From there it's only a small step to get diff-like output:
|
78
|
-
#
|
79
|
-
# e h i k q r x y
|
80
|
-
# + - + + - + + +
|
81
|
-
#
|
82
|
-
# This module solves the LCS problem. It also includes a canned function
|
83
|
-
# to generate +diff+-like output.
|
84
|
-
#
|
85
|
-
# It might seem from the example above that the LCS of two sequences is
|
86
|
-
# always pretty obvious, but that's not always the case, especially when
|
87
|
-
# the two sequences have many repeated elements. For example, consider
|
88
|
-
#
|
89
|
-
# a x b y c z p d q
|
90
|
-
# a b c a x b y c z
|
91
|
-
#
|
92
|
-
# A naive approach might start by matching up the +a+ and +b+ that appear
|
93
|
-
# at the beginning of each sequence, like this:
|
94
|
-
#
|
95
|
-
# a x b y c z p d q
|
96
|
-
# a b c a b y c z
|
97
|
-
#
|
98
|
-
# This finds the common subsequence +a b c z+. But actually, the LCS is
|
99
|
-
# +a x b y c z+:
|
100
|
-
#
|
101
|
-
# a x b y c z p d q
|
102
|
-
# a b c a x b y c z
|
103
|
-
#
|
104
|
-
# == Author
|
105
|
-
# This version is by Austin Ziegler <austin@rubyforge.org>.
|
106
|
-
#
|
107
|
-
# It is based on the Perl Algorithm::Diff (1.15) by Ned Konz , copyright
|
108
|
-
# © 2000–2002 and the Smalltalk diff version by Mario I.
|
109
|
-
# Wolczko, copyright © 1993. Documentation includes work by
|
110
|
-
# Mark-Jason Dominus.
|
111
|
-
#
|
112
|
-
# == Licence
|
113
|
-
# Copyright © 2004 Austin Ziegler
|
114
|
-
# This program is free software; you can redistribute it and/or modify it
|
115
|
-
# under the same terms as Ruby, or alternatively under the Perl Artistic
|
116
|
-
# licence.
|
117
|
-
#
|
118
|
-
# == Credits
|
119
|
-
# Much of the documentation is taken directly from the Perl
|
120
|
-
# Algorithm::Diff implementation and was written originally by Mark-Jason
|
121
|
-
# Dominus and later by Ned Konz. The basic Ruby implementation was
|
122
|
-
# re-ported from the Smalltalk implementation, available at
|
123
|
-
# ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st
|
124
|
-
#
|
125
|
-
# #sdiff and #traverse_balanced were written for the Perl version by Mike
|
126
|
-
# Schilli <m@perlmeister.com>.
|
127
|
-
#
|
128
|
-
# "The algorithm is described in <em>A Fast Algorithm for Computing
|
129
|
-
# Longest Common Subsequences</em>, CACM, vol.20, no.5, pp.350-353, May
|
130
|
-
# 1977, with a few minor improvements to improve the speed."
|
131
|
-
module LCS
|
132
|
-
VERSION = '1.1.3'
|
133
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diff; end unless defined? Diff # rubocop:disable Style/Documentation
|
4
|
+
|
5
|
+
# == How Diff Works (by Mark-Jason Dominus)
|
6
|
+
#
|
7
|
+
# I once read an article written by the authors of +diff+; they said that they
|
8
|
+
# hard worked very hard on the algorithm until they found the right one.
|
9
|
+
#
|
10
|
+
# I think what they ended up using (and I hope someone will correct me, because
|
11
|
+
# I am not very confident about this) was the `longest common subsequence'
|
12
|
+
# method. In the LCS problem, you have two sequences of items:
|
13
|
+
#
|
14
|
+
# a b c d f g h j q z
|
15
|
+
# a b c d e f g i j k r x y z
|
16
|
+
#
|
17
|
+
# and you want to find the longest sequence of items that is present in both
|
18
|
+
# original sequences in the same order. That is, you want to find a new
|
19
|
+
# sequence *S* which can be obtained from the first sequence by deleting some
|
20
|
+
# items, and from the second sequence by deleting other items. You also want
|
21
|
+
# *S* to be as long as possible. In this case *S* is:
|
22
|
+
#
|
23
|
+
# a b c d f g j z
|
24
|
+
#
|
25
|
+
# From there it's only a small step to get diff-like output:
|
26
|
+
#
|
27
|
+
# e h i k q r x y
|
28
|
+
# + - + + - + + +
|
29
|
+
#
|
30
|
+
# This module solves the LCS problem. It also includes a canned function to
|
31
|
+
# generate +diff+-like output.
|
32
|
+
#
|
33
|
+
# It might seem from the example above that the LCS of two sequences is always
|
34
|
+
# pretty obvious, but that's not always the case, especially when the two
|
35
|
+
# sequences have many repeated elements. For example, consider
|
36
|
+
#
|
37
|
+
# a x b y c z p d q
|
38
|
+
# a b c a x b y c z
|
39
|
+
#
|
40
|
+
# A naive approach might start by matching up the +a+ and +b+ that appear at
|
41
|
+
# the beginning of each sequence, like this:
|
42
|
+
#
|
43
|
+
# a x b y c z p d q
|
44
|
+
# a b c a b y c z
|
45
|
+
#
|
46
|
+
# This finds the common subsequence +a b c z+. But actually, the LCS is +a x b
|
47
|
+
# y c z+:
|
48
|
+
#
|
49
|
+
# a x b y c z p d q
|
50
|
+
# a b c a x b y c z
|
51
|
+
module Diff::LCS
|
52
|
+
VERSION = '1.5.0'
|
134
53
|
end
|
135
54
|
|
136
55
|
require 'diff/lcs/callbacks'
|
56
|
+
require 'diff/lcs/internals'
|
137
57
|
|
138
|
-
module Diff::LCS
|
58
|
+
module Diff::LCS # rubocop:disable Style/Documentation
|
139
59
|
# Returns an Array containing the longest common subsequence(s) between
|
140
|
-
# +self+ and +other+. See Diff::LCS#
|
60
|
+
# +self+ and +other+. See Diff::LCS#lcs.
|
141
61
|
#
|
142
62
|
# lcs = seq1.lcs(seq2)
|
143
|
-
|
144
|
-
|
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:
|
71
|
+
Diff::LCS.lcs(self, other, &block)
|
145
72
|
end
|
146
73
|
|
147
|
-
# Returns the difference set between +self+ and +other+. See
|
148
|
-
# Diff::LCS#diff.
|
74
|
+
# Returns the difference set between +self+ and +other+. See Diff::LCS#diff.
|
149
75
|
def diff(other, callbacks = nil, &block)
|
150
|
-
Diff::LCS
|
76
|
+
Diff::LCS.diff(self, other, callbacks, &block)
|
151
77
|
end
|
152
78
|
|
153
79
|
# Returns the balanced ("side-by-side") difference set between +self+ and
|
154
80
|
# +other+. See Diff::LCS#sdiff.
|
155
81
|
def sdiff(other, callbacks = nil, &block)
|
156
|
-
Diff::LCS
|
82
|
+
Diff::LCS.sdiff(self, other, callbacks, &block)
|
157
83
|
end
|
158
84
|
|
159
85
|
# Traverses the discovered longest common subsequences between +self+ and
|
160
86
|
# +other+. See Diff::LCS#traverse_sequences.
|
161
87
|
def traverse_sequences(other, callbacks = nil, &block)
|
162
|
-
traverse_sequences(self, other, callbacks ||
|
163
|
-
Diff::LCS::YieldingCallbacks, &block)
|
88
|
+
Diff::LCS.traverse_sequences(self, other, callbacks || Diff::LCS::SequenceCallbacks, &block)
|
164
89
|
end
|
165
90
|
|
166
91
|
# Traverses the discovered longest common subsequences between +self+ and
|
167
92
|
# +other+ using the alternate, balanced algorithm. See
|
168
93
|
# Diff::LCS#traverse_balanced.
|
169
94
|
def traverse_balanced(other, callbacks = nil, &block)
|
170
|
-
traverse_balanced(self, other, callbacks ||
|
171
|
-
Diff::LCS::YieldingCallbacks, &block)
|
95
|
+
Diff::LCS.traverse_balanced(self, other, callbacks || Diff::LCS::BalancedCallbacks, &block)
|
172
96
|
end
|
173
97
|
|
174
|
-
# Attempts to patch
|
175
|
-
# Diff::LCS#patch.
|
98
|
+
# Attempts to patch +self+ with the provided +patchset+. A new sequence based
|
99
|
+
# on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Attempts
|
100
|
+
# to autodiscover the direction of the patch.
|
176
101
|
def patch(patchset)
|
177
|
-
Diff::LCS
|
102
|
+
Diff::LCS.patch(self, patchset)
|
178
103
|
end
|
104
|
+
alias unpatch patch
|
179
105
|
|
180
|
-
# Attempts to
|
181
|
-
# Diff::LCS#patch.
|
182
|
-
|
183
|
-
Diff::LCS::unpatch(self.dup, patchset)
|
184
|
-
end
|
185
|
-
|
186
|
-
# Attempts to patch +self+ with the provided +patchset+. See
|
187
|
-
# Diff::LCS#patch!. Does no autodiscovery.
|
106
|
+
# Attempts to patch +self+ with the provided +patchset+. A new sequence based
|
107
|
+
# on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Does no
|
108
|
+
# patch direction autodiscovery.
|
188
109
|
def patch!(patchset)
|
189
|
-
Diff::LCS
|
110
|
+
Diff::LCS.patch!(self, patchset)
|
190
111
|
end
|
191
112
|
|
192
|
-
# Attempts to unpatch +self+ with the provided +patchset+.
|
193
|
-
# Diff::LCS#unpatch.
|
113
|
+
# Attempts to unpatch +self+ with the provided +patchset+. A new sequence
|
114
|
+
# based on +self+ and the +patchset+ will be created. See Diff::LCS#unpatch.
|
115
|
+
# Does no patch direction autodiscovery.
|
194
116
|
def unpatch!(patchset)
|
195
|
-
Diff::LCS
|
117
|
+
Diff::LCS.unpatch!(self, patchset)
|
196
118
|
end
|
197
|
-
end
|
198
119
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
#
|
208
|
-
# lcs.each_with_index do |ee, ii|
|
209
|
-
# assert(ee.nil? || (seq1[ii] == seq2[ee]))
|
210
|
-
# end
|
211
|
-
#
|
212
|
-
# If a block is provided, the matching subsequences will be yielded from
|
213
|
-
# +seq1+ in turn and may be modified before they are placed into the
|
214
|
-
# returned Array of subsequences.
|
215
|
-
def LCS(seq1, seq2, &block) #:yields seq1[ii] for each matched:
|
216
|
-
matches = Diff::LCS.__lcs(seq1, seq2)
|
217
|
-
ret = []
|
218
|
-
matches.each_with_index do |ee, ii|
|
219
|
-
unless matches[ii].nil?
|
220
|
-
if block_given?
|
221
|
-
ret << (yield seq1[ii])
|
222
|
-
else
|
223
|
-
ret << seq1[ii]
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
ret
|
120
|
+
# Attempts to patch +self+ with the provided +patchset+, using #patch!. If
|
121
|
+
# the sequence this is used on supports #replace, the value of +self+ will be
|
122
|
+
# replaced. See Diff::LCS#patch. Does no patch direction autodiscovery.
|
123
|
+
def patch_me(patchset)
|
124
|
+
if respond_to? :replace
|
125
|
+
replace(patch!(patchset))
|
126
|
+
else
|
127
|
+
patch!(patchset)
|
228
128
|
end
|
129
|
+
end
|
229
130
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
# responds to #finish, it will be called.
|
239
|
-
def diff(seq1, seq2, callbacks = nil, &block) # :yields diff changes:
|
240
|
-
callbacks ||= Diff::LCS::DiffCallbacks
|
241
|
-
if callbacks.kind_of?(Class)
|
242
|
-
cb = callbacks.new rescue callbacks
|
243
|
-
callbacks = cb
|
244
|
-
end
|
245
|
-
traverse_sequences(seq1, seq2, callbacks)
|
246
|
-
callbacks.finish if callbacks.respond_to?(:finish)
|
247
|
-
|
248
|
-
if block_given?
|
249
|
-
res = callbacks.diffs.map do |hunk|
|
250
|
-
if hunk.kind_of?(Array)
|
251
|
-
hunk = hunk.map { |hunk_block| yield hunk_block }
|
252
|
-
else
|
253
|
-
yield hunk
|
254
|
-
end
|
255
|
-
end
|
256
|
-
res
|
257
|
-
else
|
258
|
-
callbacks.diffs
|
259
|
-
end
|
131
|
+
# Attempts to unpatch +self+ with the provided +patchset+, using #unpatch!.
|
132
|
+
# If the sequence this is used on supports #replace, the value of +self+ will
|
133
|
+
# be replaced. See Diff::LCS#unpatch. Does no patch direction autodiscovery.
|
134
|
+
def unpatch_me(patchset)
|
135
|
+
if respond_to? :replace
|
136
|
+
replace(unpatch!(patchset))
|
137
|
+
else
|
138
|
+
unpatch!(patchset)
|
260
139
|
end
|
140
|
+
end
|
141
|
+
end
|
261
142
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
# a Class argument is provided for +callbacks+, #diff will attempt to
|
274
|
-
# initialise it. If the +callbacks+ object (possibly initialised)
|
275
|
-
# responds to #finish, it will be called.
|
276
|
-
def sdiff(seq1, seq2, callbacks = nil, &block) #:yields diff changes:
|
277
|
-
callbacks ||= Diff::LCS::SDiffCallbacks
|
278
|
-
if callbacks.kind_of?(Class)
|
279
|
-
cb = callbacks.new rescue callbacks
|
280
|
-
callbacks = cb
|
281
|
-
end
|
282
|
-
traverse_balanced(seq1, seq2, callbacks)
|
283
|
-
callbacks.finish if callbacks.respond_to?(:finish)
|
284
|
-
|
285
|
-
if block_given?
|
286
|
-
res = callbacks.diffs.map do |hunk|
|
287
|
-
if hunk.kind_of?(Array)
|
288
|
-
hunk = hunk.map { |hunk_block| yield hunk_block }
|
289
|
-
else
|
290
|
-
yield hunk
|
291
|
-
end
|
292
|
-
end
|
293
|
-
res
|
294
|
-
else
|
295
|
-
callbacks.diffs
|
296
|
-
end
|
143
|
+
class << Diff::LCS
|
144
|
+
def lcs(seq1, seq2, &block) #:yields seq1[i] for each matched:
|
145
|
+
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
146
|
+
ret = []
|
147
|
+
string = seq1.kind_of? String
|
148
|
+
matches.each_with_index do |_e, i|
|
149
|
+
next if matches[i].nil?
|
150
|
+
|
151
|
+
v = string ? seq1[i, 1] : seq1[i]
|
152
|
+
v = block[v] if block
|
153
|
+
ret << v
|
297
154
|
end
|
155
|
+
ret
|
156
|
+
end
|
157
|
+
alias LCS lcs
|
298
158
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
#
|
312
|
-
# callbacks#match:: Called when +a+ and +b+ are pointing
|
313
|
-
# to common elements in +A+ and +B+.
|
314
|
-
# callbacks#discard_a:: Called when +a+ is pointing to an
|
315
|
-
# element not in +B+.
|
316
|
-
# callbacks#discard_b:: Called when +b+ is pointing to an
|
317
|
-
# element not in +A+.
|
318
|
-
# <em>callbacks#finished_a</em>:: Called when +a+ has reached the end of
|
319
|
-
# sequence +A+.
|
320
|
-
# <em>callbacks#finished_b</em>:: Called when +b+ has reached the end of
|
321
|
-
# sequence +B+.
|
322
|
-
#
|
323
|
-
# == Algorithm
|
324
|
-
# a---+
|
325
|
-
# v
|
326
|
-
# A = a b c e h j l m n p
|
327
|
-
# B = b c d e f j k l m r s t
|
328
|
-
# ^
|
329
|
-
# b---+
|
330
|
-
#
|
331
|
-
# If there are two arrows (+a+ and +b+) pointing to elements of
|
332
|
-
# sequences +A+ and +B+, the arrows will initially point to the first
|
333
|
-
# elements of their respective sequences. #traverse_sequences will
|
334
|
-
# advance the arrows through the sequences one element at a time,
|
335
|
-
# calling a method on the user-specified callback object before each
|
336
|
-
# advance. It will advance the arrows in such a way that if there are
|
337
|
-
# elements <tt>A[ii]</tt> and <tt>B[jj]</tt> which are both equal and
|
338
|
-
# part of the longest common subsequence, there will be some moment
|
339
|
-
# during the execution of #traverse_sequences when arrow +a+ is pointing
|
340
|
-
# to <tt>A[ii]</tt> and arrow +b+ is pointing to <tt>B[jj]</tt>. When
|
341
|
-
# this happens, #traverse_sequences will call <tt>callbacks#match</tt>
|
342
|
-
# and then it will advance both arrows.
|
343
|
-
#
|
344
|
-
# Otherwise, one of the arrows is pointing to an element of its sequence
|
345
|
-
# that is not part of the longest common subsequence.
|
346
|
-
# #traverse_sequences will advance that arrow and will call
|
347
|
-
# <tt>callbacks#discard_a</tt> or <tt>callbacks#discard_b</tt>, depending
|
348
|
-
# on which arrow it advanced. If both arrows point to elements that are
|
349
|
-
# not part of the longest common subsequence, then #traverse_sequences
|
350
|
-
# will advance one of them and call the appropriate callback, but it is
|
351
|
-
# not specified which it will call.
|
352
|
-
#
|
353
|
-
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
|
354
|
-
# and <tt>callbacks#discard_b</tt> are invoked with an event comprising
|
355
|
-
# the action ("=", "+", or "-", respectively), the indicies +ii+ and
|
356
|
-
# +jj+, and the elements <tt>A[ii]</tt> and <tt>B[jj]</tt>. Return
|
357
|
-
# values are discarded by #traverse_sequences.
|
358
|
-
#
|
359
|
-
# === End of Sequences
|
360
|
-
# If arrow +a+ reaches the end of its sequence before arrow +b+ does,
|
361
|
-
# #traverse_sequence will try to call <tt>callbacks#finished_a</tt> with
|
362
|
-
# the last index and element of +A+ (<tt>A[-1]</tt>) and the current
|
363
|
-
# index and element of +B+ (<tt>B[jj]</tt>). If
|
364
|
-
# <tt>callbacks#finished_a</tt> does not exist, then
|
365
|
-
# <tt>callbacks#discard_b</tt> will be called on each element of +B+
|
366
|
-
# until the end of the sequence is reached (the call
|
367
|
-
# will be done with <tt>A[-1]</tt> and <tt>B[jj]</tt> for each element).
|
368
|
-
#
|
369
|
-
# If +b+ reaches the end of +B+ before +a+ reaches the end of +A+,
|
370
|
-
# <tt>callbacks#finished_b</tt> will be called with the current index
|
371
|
-
# and element of +A+ (<tt>A[ii]</tt>) and the last index and element of
|
372
|
-
# +B+ (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not
|
373
|
-
# exist on the callback object, then <tt>callbacks#discard_a</tt> will
|
374
|
-
# be called on each element of +A+ until the end of the sequence is
|
375
|
-
# reached (<tt>A[ii]</tt> and <tt>B[-1]</tt>).
|
376
|
-
#
|
377
|
-
# There is a chance that one additional <tt>callbacks#discard_a</tt> or
|
378
|
-
# <tt>callbacks#discard_b</tt> will be called after the end of the
|
379
|
-
# sequence is reached, if +a+ has not yet reached the end of +A+ or +b+
|
380
|
-
# has not yet reached the end of +B+.
|
381
|
-
def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks, &block) #:yields change events:
|
382
|
-
matches = Diff::LCS.__lcs(seq1, seq2)
|
383
|
-
|
384
|
-
run_finished_a = run_finished_b = false
|
385
|
-
string = seq1.kind_of?(String)
|
386
|
-
|
387
|
-
a_size = seq1.size
|
388
|
-
b_size = seq2.size
|
389
|
-
ai = bj = 0
|
390
|
-
|
391
|
-
(0 .. matches.size).each do |ii|
|
392
|
-
b_line = matches[ii]
|
393
|
-
|
394
|
-
ax = string ? seq1[ii, 1] : seq1[ii]
|
395
|
-
bx = string ? seq2[bj, 1] : seq2[bj]
|
396
|
-
|
397
|
-
if b_line.nil?
|
398
|
-
unless ax.nil?
|
399
|
-
event = Diff::LCS::ContextChange.new('-', ii, ax, bj, bx)
|
400
|
-
event = yield event if block_given?
|
401
|
-
callbacks.discard_a(event)
|
402
|
-
end
|
403
|
-
else
|
404
|
-
loop do
|
405
|
-
break unless bj < b_line
|
406
|
-
bx = string ? seq2[bj, 1] : seq2[bj]
|
407
|
-
event = Diff::LCS::ContextChange.new('+', ii, ax, bj, bx)
|
408
|
-
event = yield event if block_given?
|
409
|
-
callbacks.discard_b(event)
|
410
|
-
bj += 1
|
411
|
-
end
|
412
|
-
bx = string ? seq2[bj, 1] : seq2[bj]
|
413
|
-
event = Diff::LCS::ContextChange.new('=', ii, ax, bj, bx)
|
414
|
-
event = yield event if block_given?
|
415
|
-
callbacks.match(event)
|
416
|
-
bj += 1
|
417
|
-
end
|
418
|
-
ai = ii
|
419
|
-
end
|
420
|
-
ai += 1
|
421
|
-
|
422
|
-
# The last entry (if any) processed was a match. +ai+ and +bj+ point
|
423
|
-
# just past the last matching lines in their sequences.
|
424
|
-
while (ai < a_size) or (bj < b_size)
|
425
|
-
# last A?
|
426
|
-
if ai == a_size and bj < b_size
|
427
|
-
if callbacks.respond_to?(:finished_a) and not run_finished_a
|
428
|
-
ax = string ? seq1[-1, 1] : seq1[-1]
|
429
|
-
bx = string ? seq2[bj, 1] : seq2[bj]
|
430
|
-
event = Diff::LCS::ContextChange.new('>', (a_size - 1), ax, bj, bx)
|
431
|
-
event = yield event if block_given?
|
432
|
-
callbacks.finished_a(event)
|
433
|
-
run_finished_a = true
|
434
|
-
else
|
435
|
-
ax = string ? seq1[ai, 1] : seq1[ai]
|
436
|
-
loop do
|
437
|
-
bx = string ? seq2[bj, 1] : seq2[bj]
|
438
|
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
439
|
-
event = yield event if block_given?
|
440
|
-
callbacks.discard_b(event)
|
441
|
-
bj += 1
|
442
|
-
break unless bj < b_size
|
443
|
-
end
|
444
|
-
end
|
445
|
-
end
|
159
|
+
# #diff computes the smallest set of additions and deletions necessary to
|
160
|
+
# turn the first sequence into the second, and returns a description of these
|
161
|
+
# changes.
|
162
|
+
#
|
163
|
+
# See Diff::LCS::DiffCallbacks for the default behaviour. An alternate
|
164
|
+
# behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
|
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:
|
169
|
+
diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks, &block)
|
170
|
+
end
|
446
171
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
172
|
+
# #sdiff computes all necessary components to show two sequences and their
|
173
|
+
# minimized differences side by side, just like the Unix utility
|
174
|
+
# <em>sdiff</em> does:
|
175
|
+
#
|
176
|
+
# old < -
|
177
|
+
# same same
|
178
|
+
# before | after
|
179
|
+
# - > new
|
180
|
+
#
|
181
|
+
# See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate
|
182
|
+
# behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
|
183
|
+
# Class argument is provided for +callbacks+, #diff will attempt to
|
184
|
+
# initialise it. If the +callbacks+ object (possibly initialised) responds to
|
185
|
+
# #finish, it will be called.
|
186
|
+
#
|
187
|
+
# Each element of a returned array is a Diff::LCS::ContextChange object,
|
188
|
+
# which can be implicitly converted to an array.
|
189
|
+
#
|
190
|
+
# Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)|
|
191
|
+
# case action
|
192
|
+
# when '!'
|
193
|
+
# # replace
|
194
|
+
# when '-'
|
195
|
+
# # delete
|
196
|
+
# when '+'
|
197
|
+
# # insert
|
198
|
+
# end
|
199
|
+
# end
|
200
|
+
def sdiff(seq1, seq2, callbacks = nil, &block) #:yields diff changes:
|
201
|
+
diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks, &block)
|
202
|
+
end
|
468
203
|
|
469
|
-
|
204
|
+
# #traverse_sequences is the most general facility provided by this module;
|
205
|
+
# #diff and #lcs are implemented as calls to it.
|
206
|
+
#
|
207
|
+
# The arguments to #traverse_sequences are the two sequences to traverse, and
|
208
|
+
# a callback object, like this:
|
209
|
+
#
|
210
|
+
# traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
|
211
|
+
#
|
212
|
+
# == Callback Methods
|
213
|
+
#
|
214
|
+
# Optional callback methods are <em>emphasized</em>.
|
215
|
+
#
|
216
|
+
# callbacks#match:: Called when +a+ and +b+ are pointing to
|
217
|
+
# common elements in +A+ and +B+.
|
218
|
+
# callbacks#discard_a:: Called when +a+ is pointing to an
|
219
|
+
# element not in +B+.
|
220
|
+
# callbacks#discard_b:: Called when +b+ is pointing to an
|
221
|
+
# element not in +A+.
|
222
|
+
# <em>callbacks#finished_a</em>:: Called when +a+ has reached the end of
|
223
|
+
# sequence +A+.
|
224
|
+
# <em>callbacks#finished_b</em>:: Called when +b+ has reached the end of
|
225
|
+
# sequence +B+.
|
226
|
+
#
|
227
|
+
# == Algorithm
|
228
|
+
#
|
229
|
+
# a---+
|
230
|
+
# v
|
231
|
+
# A = a b c e h j l m n p
|
232
|
+
# B = b c d e f j k l m r s t
|
233
|
+
# ^
|
234
|
+
# b---+
|
235
|
+
#
|
236
|
+
# If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+
|
237
|
+
# and +B+, the arrows will initially point to the first elements of their
|
238
|
+
# respective sequences. #traverse_sequences will advance the arrows through
|
239
|
+
# the sequences one element at a time, calling a method on the user-specified
|
240
|
+
# callback object before each advance. It will advance the arrows in such a
|
241
|
+
# way that if there are elements <tt>A[i]</tt> and <tt>B[j]</tt> which are
|
242
|
+
# both equal and part of the longest common subsequence, there will be some
|
243
|
+
# moment during the execution of #traverse_sequences when arrow +a+ is
|
244
|
+
# pointing to <tt>A[i]</tt> and arrow +b+ is pointing to <tt>B[j]</tt>. When
|
245
|
+
# this happens, #traverse_sequences will call <tt>callbacks#match</tt> and
|
246
|
+
# then it will advance both arrows.
|
247
|
+
#
|
248
|
+
# Otherwise, one of the arrows is pointing to an element of its sequence that
|
249
|
+
# is not part of the longest common subsequence. #traverse_sequences will
|
250
|
+
# advance that arrow and will call <tt>callbacks#discard_a</tt> or
|
251
|
+
# <tt>callbacks#discard_b</tt>, depending on which arrow it advanced. If both
|
252
|
+
# arrows point to elements that are not part of the longest common
|
253
|
+
# subsequence, then #traverse_sequences will advance arrow +a+ and call the
|
254
|
+
# appropriate callback, then it will advance arrow +b+ and call the appropriate
|
255
|
+
# callback.
|
256
|
+
#
|
257
|
+
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>, and
|
258
|
+
# <tt>callbacks#discard_b</tt> are invoked with an event comprising the
|
259
|
+
# action ("=", "+", or "-", respectively), the indicies +i+ and +j+, and the
|
260
|
+
# elements <tt>A[i]</tt> and <tt>B[j]</tt>. Return values are discarded by
|
261
|
+
# #traverse_sequences.
|
262
|
+
#
|
263
|
+
# === End of Sequences
|
264
|
+
#
|
265
|
+
# If arrow +a+ reaches the end of its sequence before arrow +b+ does,
|
266
|
+
# #traverse_sequence will try to call <tt>callbacks#finished_a</tt> with the
|
267
|
+
# last index and element of +A+ (<tt>A[-1]</tt>) and the current index and
|
268
|
+
# element of +B+ (<tt>B[j]</tt>). If <tt>callbacks#finished_a</tt> does not
|
269
|
+
# exist, then <tt>callbacks#discard_b</tt> will be called on each element of
|
270
|
+
# +B+ until the end of the sequence is reached (the call will be done with
|
271
|
+
# <tt>A[-1]</tt> and <tt>B[j]</tt> for each element).
|
272
|
+
#
|
273
|
+
# If +b+ reaches the end of +B+ before +a+ reaches the end of +A+,
|
274
|
+
# <tt>callbacks#finished_b</tt> will be called with the current index and
|
275
|
+
# element of +A+ (<tt>A[i]</tt>) and the last index and element of +B+
|
276
|
+
# (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not exist on
|
277
|
+
# the callback object, then <tt>callbacks#discard_a</tt> will be called on
|
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:
|
286
|
+
callbacks ||= Diff::LCS::SequenceCallbacks
|
287
|
+
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
288
|
+
|
289
|
+
run_finished_a = run_finished_b = false
|
290
|
+
string = seq1.kind_of?(String)
|
291
|
+
|
292
|
+
a_size = seq1.size
|
293
|
+
b_size = seq2.size
|
294
|
+
ai = bj = 0
|
295
|
+
|
296
|
+
matches.each do |b_line|
|
297
|
+
if b_line.nil?
|
298
|
+
unless seq1[ai].nil?
|
470
299
|
ax = string ? seq1[ai, 1] : seq1[ai]
|
471
300
|
bx = string ? seq2[bj, 1] : seq2[bj]
|
301
|
+
|
472
302
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
473
303
|
event = yield event if block_given?
|
474
304
|
callbacks.discard_a(event)
|
475
|
-
ai += 1
|
476
305
|
end
|
306
|
+
else
|
307
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
308
|
+
|
309
|
+
loop do
|
310
|
+
break unless bj < b_line
|
477
311
|
|
478
|
-
if bj < b_size
|
479
|
-
ax = string ? seq1[ai, 1] : seq1[ai]
|
480
312
|
bx = string ? seq2[bj, 1] : seq2[bj]
|
481
313
|
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
482
314
|
event = yield event if block_given?
|
483
315
|
callbacks.discard_b(event)
|
484
316
|
bj += 1
|
485
317
|
end
|
318
|
+
bx = string ? seq2[bj, 1] : seq2[bj]
|
319
|
+
event = Diff::LCS::ContextChange.new('=', ai, ax, bj, bx)
|
320
|
+
event = yield event if block_given?
|
321
|
+
callbacks.match(event)
|
322
|
+
bj += 1
|
486
323
|
end
|
324
|
+
ai += 1
|
487
325
|
end
|
488
326
|
|
489
|
-
#
|
490
|
-
#
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
# <em>callbacks#change</em>:: Called when +a+ and +b+ are pointing
|
512
|
-
# to the same relative position, but
|
513
|
-
# <tt>A[a]</tt> and <tt>B[b]</tt> are
|
514
|
-
# not the same; a <em>change</em> has
|
515
|
-
# occurred.
|
516
|
-
#
|
517
|
-
# #traverse_balanced might be a bit slower than #traverse_sequences,
|
518
|
-
# noticable only while processing huge amounts of data.
|
519
|
-
#
|
520
|
-
# The +sdiff+ function of this module is implemented as call to
|
521
|
-
# #traverse_balanced.
|
522
|
-
#
|
523
|
-
# == Algorithm
|
524
|
-
# a---+
|
525
|
-
# v
|
526
|
-
# A = a b c e h j l m n p
|
527
|
-
# B = b c d e f j k l m r s t
|
528
|
-
# ^
|
529
|
-
# b---+
|
530
|
-
#
|
531
|
-
# === Matches
|
532
|
-
# If there are two arrows (+a+ and +b+) pointing to elements of
|
533
|
-
# sequences +A+ and +B+, the arrows will initially point to the first
|
534
|
-
# elements of their respective sequences. #traverse_sequences will
|
535
|
-
# advance the arrows through the sequences one element at a time,
|
536
|
-
# calling a method on the user-specified callback object before each
|
537
|
-
# advance. It will advance the arrows in such a way that if there are
|
538
|
-
# elements <tt>A[ii]</tt> and <tt>B[jj]</tt> which are both equal and
|
539
|
-
# part of the longest common subsequence, there will be some moment
|
540
|
-
# during the execution of #traverse_sequences when arrow +a+ is pointing
|
541
|
-
# to <tt>A[ii]</tt> and arrow +b+ is pointing to <tt>B[jj]</tt>. When
|
542
|
-
# this happens, #traverse_sequences will call <tt>callbacks#match</tt>
|
543
|
-
# and then it will advance both arrows.
|
544
|
-
#
|
545
|
-
# === Discards
|
546
|
-
# Otherwise, one of the arrows is pointing to an element of its sequence
|
547
|
-
# that is not part of the longest common subsequence.
|
548
|
-
# #traverse_sequences will advance that arrow and will call
|
549
|
-
# <tt>callbacks#discard_a</tt> or <tt>callbacks#discard_b</tt>,
|
550
|
-
# depending on which arrow it advanced.
|
551
|
-
#
|
552
|
-
# === Changes
|
553
|
-
# If both +a+ and +b+ point to elements that are not part of the longest
|
554
|
-
# common subsequence, then #traverse_sequences will try to call
|
555
|
-
# <tt>callbacks#change</tt> and advance both arrows. If
|
556
|
-
# <tt>callbacks#change</tt> is not implemented, then
|
557
|
-
# <tt>callbacks#discard_a</tt> and <tt>callbacks#discard_b</tt> will be
|
558
|
-
# called in turn.
|
559
|
-
#
|
560
|
-
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
|
561
|
-
# <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are
|
562
|
-
# invoked with an event comprising the action ("=", "+", "-", or "!",
|
563
|
-
# respectively), the indicies +ii+ and +jj+, and the elements
|
564
|
-
# <tt>A[ii]</tt> and <tt>B[jj]</tt>. Return values are discarded by
|
565
|
-
# #traverse_balanced.
|
566
|
-
#
|
567
|
-
# === Context
|
568
|
-
# Note that +ii+ and +jj+ may not be the same index position, even if
|
569
|
-
# +a+ and +b+ are considered to be pointing to matching or changed
|
570
|
-
# elements.
|
571
|
-
def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
|
572
|
-
matches = Diff::LCS.__lcs(seq1, seq2)
|
573
|
-
a_size = seq1.size
|
574
|
-
b_size = seq2.size
|
575
|
-
ai = bj = mb = 0
|
576
|
-
ma = -1
|
577
|
-
string = seq1.kind_of?(String)
|
578
|
-
|
579
|
-
# Process all the lines in the match vector.
|
580
|
-
loop do
|
581
|
-
# Find next match indices +ma+ and +mb+
|
582
|
-
loop do
|
583
|
-
ma += 1
|
584
|
-
break unless ma < matches.size and matches[ma].nil?
|
327
|
+
# The last entry (if any) processed was a match. +ai+ and +bj+ point just
|
328
|
+
# past the last matching lines in their sequences.
|
329
|
+
while (ai < a_size) or (bj < b_size)
|
330
|
+
# last A?
|
331
|
+
if ai == a_size and bj < b_size
|
332
|
+
if callbacks.respond_to?(:finished_a) and !run_finished_a
|
333
|
+
ax = string ? seq1[-1, 1] : seq1[-1]
|
334
|
+
bx = string ? seq2[bj, 1] : seq2[bj]
|
335
|
+
event = Diff::LCS::ContextChange.new('>', (a_size - 1), ax, bj, bx)
|
336
|
+
event = yield event if block_given?
|
337
|
+
callbacks.finished_a(event)
|
338
|
+
run_finished_a = true
|
339
|
+
else
|
340
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
341
|
+
loop do
|
342
|
+
bx = string ? seq2[bj, 1] : seq2[bj]
|
343
|
+
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
344
|
+
event = yield event if block_given?
|
345
|
+
callbacks.discard_b(event)
|
346
|
+
bj += 1
|
347
|
+
break unless bj < b_size
|
348
|
+
end
|
585
349
|
end
|
350
|
+
end
|
586
351
|
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
# Change(seq2)
|
591
|
-
while (ai < ma) or (bj < mb)
|
352
|
+
# last B?
|
353
|
+
if bj == b_size and ai < a_size
|
354
|
+
if callbacks.respond_to?(:finished_b) and !run_finished_b
|
592
355
|
ax = string ? seq1[ai, 1] : seq1[ai]
|
356
|
+
bx = string ? seq2[-1, 1] : seq2[-1]
|
357
|
+
event = Diff::LCS::ContextChange.new('<', ai, ax, (b_size - 1), bx)
|
358
|
+
event = yield event if block_given?
|
359
|
+
callbacks.finished_b(event)
|
360
|
+
run_finished_b = true
|
361
|
+
else
|
593
362
|
bx = string ? seq2[bj, 1] : seq2[bj]
|
594
|
-
|
595
|
-
|
596
|
-
when [true, true]
|
597
|
-
if callbacks.respond_to?(:change)
|
598
|
-
event = Diff::LCS::ContextChange.new('!', ai, ax, bj, bx)
|
599
|
-
event = yield event if block_given?
|
600
|
-
callbacks.change(event)
|
601
|
-
ai += 1
|
602
|
-
bj += 1
|
603
|
-
else
|
604
|
-
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
605
|
-
event = yield event if block_given?
|
606
|
-
callbacks.discard_a(event)
|
607
|
-
ai += 1
|
608
|
-
ax = string ? seq1[ai, 1] : seq1[ai]
|
609
|
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
610
|
-
event = yield event if block_given?
|
611
|
-
callbacks.discard_b(event)
|
612
|
-
bj += 1
|
613
|
-
end
|
614
|
-
when [true, false]
|
363
|
+
loop do
|
364
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
615
365
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
616
366
|
event = yield event if block_given?
|
617
367
|
callbacks.discard_a(event)
|
618
368
|
ai += 1
|
619
|
-
|
620
|
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
621
|
-
event = yield event if block_given?
|
622
|
-
callbacks.discard_b(event)
|
623
|
-
bj += 1
|
369
|
+
break unless bj < b_size
|
624
370
|
end
|
625
371
|
end
|
372
|
+
end
|
626
373
|
|
627
|
-
|
374
|
+
if ai < a_size
|
628
375
|
ax = string ? seq1[ai, 1] : seq1[ai]
|
629
376
|
bx = string ? seq2[bj, 1] : seq2[bj]
|
630
|
-
event = Diff::LCS::ContextChange.new('
|
377
|
+
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
631
378
|
event = yield event if block_given?
|
632
|
-
callbacks.
|
379
|
+
callbacks.discard_a(event)
|
633
380
|
ai += 1
|
381
|
+
end
|
382
|
+
|
383
|
+
if bj < b_size
|
384
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
385
|
+
bx = string ? seq2[bj, 1] : seq2[bj]
|
386
|
+
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
387
|
+
event = yield event if block_given?
|
388
|
+
callbacks.discard_b(event)
|
634
389
|
bj += 1
|
635
390
|
end
|
391
|
+
end
|
392
|
+
end
|
636
393
|
|
637
|
-
|
394
|
+
# #traverse_balanced is an alternative to #traverse_sequences. It uses a
|
395
|
+
# different algorithm to iterate through the entries in the computed longest
|
396
|
+
# common subsequence. Instead of viewing the changes as insertions or
|
397
|
+
# deletions from one of the sequences, #traverse_balanced will report
|
398
|
+
# <em>changes</em> between the sequences.
|
399
|
+
#
|
400
|
+
# The arguments to #traverse_balanced are the two sequences to traverse and a
|
401
|
+
# callback object, like this:
|
402
|
+
#
|
403
|
+
# traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
|
404
|
+
#
|
405
|
+
# #sdiff is implemented with #traverse_balanced.
|
406
|
+
#
|
407
|
+
# == Callback Methods
|
408
|
+
#
|
409
|
+
# Optional callback methods are <em>emphasized</em>.
|
410
|
+
#
|
411
|
+
# callbacks#match:: Called when +a+ and +b+ are pointing to
|
412
|
+
# common elements in +A+ and +B+.
|
413
|
+
# callbacks#discard_a:: Called when +a+ is pointing to an
|
414
|
+
# element not in +B+.
|
415
|
+
# callbacks#discard_b:: Called when +b+ is pointing to an
|
416
|
+
# element not in +A+.
|
417
|
+
# <em>callbacks#change</em>:: Called when +a+ and +b+ are pointing to
|
418
|
+
# the same relative position, but
|
419
|
+
# <tt>A[a]</tt> and <tt>B[b]</tt> are not
|
420
|
+
# the same; a <em>change</em> has
|
421
|
+
# occurred.
|
422
|
+
#
|
423
|
+
# #traverse_balanced might be a bit slower than #traverse_sequences,
|
424
|
+
# noticable only while processing huge amounts of data.
|
425
|
+
#
|
426
|
+
# == Algorithm
|
427
|
+
#
|
428
|
+
# a---+
|
429
|
+
# v
|
430
|
+
# A = a b c e h j l m n p
|
431
|
+
# B = b c d e f j k l m r s t
|
432
|
+
# ^
|
433
|
+
# b---+
|
434
|
+
#
|
435
|
+
# === Matches
|
436
|
+
#
|
437
|
+
# If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+
|
438
|
+
# and +B+, the arrows will initially point to the first elements of their
|
439
|
+
# respective sequences. #traverse_sequences will advance the arrows through
|
440
|
+
# the sequences one element at a time, calling a method on the user-specified
|
441
|
+
# callback object before each advance. It will advance the arrows in such a
|
442
|
+
# way that if there are elements <tt>A[i]</tt> and <tt>B[j]</tt> which are
|
443
|
+
# both equal and part of the longest common subsequence, there will be some
|
444
|
+
# moment during the execution of #traverse_sequences when arrow +a+ is
|
445
|
+
# pointing to <tt>A[i]</tt> and arrow +b+ is pointing to <tt>B[j]</tt>. When
|
446
|
+
# this happens, #traverse_sequences will call <tt>callbacks#match</tt> and
|
447
|
+
# then it will advance both arrows.
|
448
|
+
#
|
449
|
+
# === Discards
|
450
|
+
#
|
451
|
+
# Otherwise, one of the arrows is pointing to an element of its sequence that
|
452
|
+
# is not part of the longest common subsequence. #traverse_sequences will
|
453
|
+
# advance that arrow and will call <tt>callbacks#discard_a</tt> or
|
454
|
+
# <tt>callbacks#discard_b</tt>, depending on which arrow it advanced.
|
455
|
+
#
|
456
|
+
# === Changes
|
457
|
+
#
|
458
|
+
# If both +a+ and +b+ point to elements that are not part of the longest
|
459
|
+
# common subsequence, then #traverse_sequences will try to call
|
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>.
|
469
|
+
# Return values are discarded by #traverse_balanced.
|
470
|
+
#
|
471
|
+
# === Context
|
472
|
+
#
|
473
|
+
# Note that +i+ and +j+ may not be the same index position, even if +a+ and
|
474
|
+
# +b+ are considered to be pointing to matching or changed elements.
|
475
|
+
def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
|
476
|
+
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
477
|
+
a_size = seq1.size
|
478
|
+
b_size = seq2.size
|
479
|
+
ai = bj = mb = 0
|
480
|
+
ma = -1
|
481
|
+
string = seq1.kind_of?(String)
|
482
|
+
|
483
|
+
# Process all the lines in the match vector.
|
484
|
+
loop do
|
485
|
+
# Find next match indices +ma+ and +mb+
|
486
|
+
loop do
|
487
|
+
ma += 1
|
488
|
+
break unless ma < matches.size and matches[ma].nil?
|
489
|
+
end
|
490
|
+
|
491
|
+
break if ma >= matches.size # end of matches?
|
492
|
+
|
493
|
+
mb = matches[ma]
|
494
|
+
|
495
|
+
# Change(seq2)
|
496
|
+
while (ai < ma) or (bj < mb)
|
638
497
|
ax = string ? seq1[ai, 1] : seq1[ai]
|
639
498
|
bx = string ? seq2[bj, 1] : seq2[bj]
|
640
499
|
|
641
|
-
case [(ai <
|
500
|
+
case [(ai < ma), (bj < mb)]
|
642
501
|
when [true, true]
|
643
502
|
if callbacks.respond_to?(:change)
|
644
503
|
event = Diff::LCS::ContextChange.new('!', ai, ax, bj, bx)
|
645
504
|
event = yield event if block_given?
|
646
505
|
callbacks.change(event)
|
647
506
|
ai += 1
|
648
|
-
bj += 1
|
649
507
|
else
|
650
508
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
651
509
|
event = yield event if block_given?
|
@@ -655,8 +513,9 @@ module Diff::LCS
|
|
655
513
|
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
656
514
|
event = yield event if block_given?
|
657
515
|
callbacks.discard_b(event)
|
658
|
-
bj += 1
|
659
516
|
end
|
517
|
+
|
518
|
+
bj += 1
|
660
519
|
when [true, false]
|
661
520
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
662
521
|
event = yield event if block_given?
|
@@ -669,437 +528,212 @@ module Diff::LCS
|
|
669
528
|
bj += 1
|
670
529
|
end
|
671
530
|
end
|
672
|
-
end
|
673
531
|
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
def patch(src, patchset, direction = nil)
|
684
|
-
string = src.kind_of?(String)
|
685
|
-
# Start with a new empty type of the source's class
|
686
|
-
res = src.class.new
|
532
|
+
# Match
|
533
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
534
|
+
bx = string ? seq2[bj, 1] : seq2[bj]
|
535
|
+
event = Diff::LCS::ContextChange.new('=', ai, ax, bj, bx)
|
536
|
+
event = yield event if block_given?
|
537
|
+
callbacks.match(event)
|
538
|
+
ai += 1
|
539
|
+
bj += 1
|
540
|
+
end
|
687
541
|
|
688
|
-
|
689
|
-
|
542
|
+
while (ai < a_size) or (bj < b_size)
|
543
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
544
|
+
bx = string ? seq2[bj, 1] : seq2[bj]
|
690
545
|
|
691
|
-
|
692
|
-
|
546
|
+
case [(ai < a_size), (bj < b_size)]
|
547
|
+
when [true, true]
|
548
|
+
if callbacks.respond_to?(:change)
|
549
|
+
event = Diff::LCS::ContextChange.new('!', ai, ax, bj, bx)
|
550
|
+
event = yield event if block_given?
|
551
|
+
callbacks.change(event)
|
552
|
+
ai += 1
|
553
|
+
else
|
554
|
+
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
555
|
+
event = yield event if block_given?
|
556
|
+
callbacks.discard_a(event)
|
557
|
+
ai += 1
|
558
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
559
|
+
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
560
|
+
event = yield event if block_given?
|
561
|
+
callbacks.discard_b(event)
|
562
|
+
end
|
693
563
|
|
694
|
-
|
564
|
+
bj += 1
|
565
|
+
when [true, false]
|
566
|
+
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
567
|
+
event = yield event if block_given?
|
568
|
+
callbacks.discard_a(event)
|
569
|
+
ai += 1
|
570
|
+
when [false, true]
|
571
|
+
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
572
|
+
event = yield event if block_given?
|
573
|
+
callbacks.discard_b(event)
|
574
|
+
bj += 1
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
695
578
|
|
696
|
-
|
697
|
-
|
698
|
-
|
579
|
+
PATCH_MAP = { #:nodoc:
|
580
|
+
:patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' }.freeze,
|
581
|
+
:unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }.freeze
|
582
|
+
}.freeze
|
699
583
|
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
584
|
+
# Applies a +patchset+ to the sequence +src+ according to the +direction+
|
585
|
+
# (<tt>:patch</tt> or <tt>:unpatch</tt>), producing a new sequence.
|
586
|
+
#
|
587
|
+
# If the +direction+ is not specified, Diff::LCS::patch will attempt to
|
588
|
+
# discover the direction of the +patchset+.
|
589
|
+
#
|
590
|
+
# A +patchset+ can be considered to apply forward (<tt>:patch</tt>) if the
|
591
|
+
# following expression is true:
|
592
|
+
#
|
593
|
+
# patch(s1, diff(s1, s2)) -> s2
|
594
|
+
#
|
595
|
+
# A +patchset+ can be considered to apply backward (<tt>:unpatch</tt>) if the
|
596
|
+
# following expression is true:
|
597
|
+
#
|
598
|
+
# patch(s2, diff(s1, s2)) -> s1
|
599
|
+
#
|
600
|
+
# If the +patchset+ contains no changes, the +src+ value will be returned as
|
601
|
+
# either <tt>src.dup</tt> or +src+. A +patchset+ can be deemed as having no
|
602
|
+
# changes if the following predicate returns true:
|
603
|
+
#
|
604
|
+
# patchset.empty? or
|
605
|
+
# patchset.flatten(1).all? { |change| change.unchanged? }
|
606
|
+
#
|
607
|
+
# === Patchsets
|
608
|
+
#
|
609
|
+
# A +patchset+ is always an enumerable sequence of changes, hunks of changes,
|
610
|
+
# or a mix of the two. A hunk of changes is an enumerable sequence of
|
611
|
+
# changes:
|
612
|
+
#
|
613
|
+
# [ # patchset
|
614
|
+
# # change
|
615
|
+
# [ # hunk
|
616
|
+
# # change
|
617
|
+
# ]
|
618
|
+
# ]
|
619
|
+
#
|
620
|
+
# The +patch+ method accepts <tt>patchset</tt>s that are enumerable sequences
|
621
|
+
# containing either Diff::LCS::Change objects (or a subclass) or the array
|
622
|
+
# representations of those objects. Prior to application, array
|
623
|
+
# representations of Diff::LCS::Change objects will be reified.
|
624
|
+
def patch(src, patchset, direction = nil)
|
625
|
+
# Normalize the patchset.
|
626
|
+
has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset)
|
627
|
+
|
628
|
+
return src.respond_to?(:dup) ? src.dup : src unless has_changes
|
629
|
+
|
630
|
+
string = src.kind_of?(String)
|
631
|
+
# Start with a new empty type of the source's class
|
632
|
+
res = src.class.new
|
633
|
+
|
634
|
+
direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset)
|
635
|
+
|
636
|
+
ai = bj = 0
|
637
|
+
|
638
|
+
patch_map = PATCH_MAP[direction]
|
639
|
+
|
640
|
+
patchset.each do |change|
|
641
|
+
# Both Change and ContextChange support #action
|
642
|
+
action = patch_map[change.action]
|
643
|
+
|
644
|
+
case change
|
645
|
+
when Diff::LCS::ContextChange
|
646
|
+
case direction
|
647
|
+
when :patch
|
648
|
+
el = change.new_element
|
649
|
+
op = change.old_position
|
650
|
+
np = change.new_position
|
651
|
+
when :unpatch
|
652
|
+
el = change.old_element
|
653
|
+
op = change.new_position
|
654
|
+
np = change.old_position
|
655
|
+
end
|
712
656
|
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
ai += 1
|
718
|
-
bj += 1
|
719
|
-
end
|
657
|
+
case action
|
658
|
+
when '-' # Remove details from the old string
|
659
|
+
while ai < op
|
660
|
+
res << (string ? src[ai, 1] : src[ai])
|
720
661
|
ai += 1
|
721
|
-
when '+'
|
722
|
-
while bj < np
|
723
|
-
res << (string ? src[ai, 1] : src[ai])
|
724
|
-
ai += 1
|
725
|
-
bj += 1
|
726
|
-
end
|
727
|
-
|
728
|
-
res << el
|
729
662
|
bj += 1
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
res <<
|
735
|
-
|
663
|
+
end
|
664
|
+
ai += 1
|
665
|
+
when '+'
|
666
|
+
while bj < np
|
667
|
+
res << (string ? src[ai, 1] : src[ai])
|
736
668
|
ai += 1
|
737
669
|
bj += 1
|
738
|
-
|
739
|
-
while ai < op
|
740
|
-
res << (string ? src[ai, 1] : src[ai])
|
741
|
-
ai += 1
|
742
|
-
bj += 1
|
743
|
-
end
|
670
|
+
end
|
744
671
|
|
745
|
-
|
746
|
-
|
672
|
+
res << el
|
673
|
+
bj += 1
|
674
|
+
when '='
|
675
|
+
# This only appears in sdiff output with the SDiff callback.
|
676
|
+
# Therefore, we only need to worry about dealing with a single
|
677
|
+
# element.
|
678
|
+
res << el
|
747
679
|
|
748
|
-
|
749
|
-
|
750
|
-
when
|
751
|
-
|
752
|
-
|
753
|
-
while ai < change.position
|
754
|
-
res << (string ? src[ai, 1] : src[ai])
|
755
|
-
ai += 1
|
756
|
-
bj += 1
|
757
|
-
end
|
680
|
+
ai += 1
|
681
|
+
bj += 1
|
682
|
+
when '!'
|
683
|
+
while ai < op
|
684
|
+
res << (string ? src[ai, 1] : src[ai])
|
758
685
|
ai += 1
|
759
|
-
when '+'
|
760
|
-
while bj < change.position
|
761
|
-
res << (string ? src[ai, 1] : src[ai])
|
762
|
-
ai += 1
|
763
|
-
bj += 1
|
764
|
-
end
|
765
|
-
|
766
686
|
bj += 1
|
767
|
-
|
768
|
-
res << change.element
|
769
687
|
end
|
770
|
-
end
|
771
|
-
end
|
772
|
-
|
773
|
-
while ai < src.size
|
774
|
-
res << (string ? src[ai, 1] : src[ai])
|
775
|
-
ai += 1
|
776
|
-
bj += 1
|
777
|
-
end
|
778
688
|
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
# Given a set of patchset, convert the current version to the prior
|
783
|
-
# version. Does no auto-discovery.
|
784
|
-
def unpatch!(src, patchset)
|
785
|
-
Diff::LCS.patch(src, patchset, :unpatch)
|
786
|
-
end
|
787
|
-
|
788
|
-
# Given a set of patchset, convert the current version to the next
|
789
|
-
# version. Does no auto-discovery.
|
790
|
-
def patch!(src, patchset)
|
791
|
-
Diff::LCS.patch(src, patchset, :patch)
|
792
|
-
end
|
793
|
-
|
794
|
-
# private
|
795
|
-
# Compute the longest common subsequence between the sequenced
|
796
|
-
# Enumerables +a+ and +b+. The result is an array whose contents is such
|
797
|
-
# that
|
798
|
-
#
|
799
|
-
# result = Diff::LCS.__lcs(a, b)
|
800
|
-
# result.each_with_index do |e, ii|
|
801
|
-
# assert_equal(a[ii], b[e]) unless e.nil?
|
802
|
-
# end
|
803
|
-
#
|
804
|
-
# Note: This will be deprecated as a public function in a future release.
|
805
|
-
def __lcs(a, b)
|
806
|
-
a_start = b_start = 0
|
807
|
-
a_finish = a.size - 1
|
808
|
-
b_finish = b.size - 1
|
809
|
-
vector = []
|
810
|
-
|
811
|
-
# Prune off any common elements at the beginning...
|
812
|
-
while (a_start <= a_finish) and
|
813
|
-
(b_start <= b_finish) and
|
814
|
-
(a[a_start] == b[b_start])
|
815
|
-
vector[a_start] = b_start
|
816
|
-
a_start += 1
|
817
|
-
b_start += 1
|
818
|
-
end
|
819
|
-
|
820
|
-
# Now the end...
|
821
|
-
while (a_start <= a_finish) and
|
822
|
-
(b_start <= b_finish) and
|
823
|
-
(a[a_finish] == b[b_finish])
|
824
|
-
vector[a_finish] = b_finish
|
825
|
-
a_finish -= 1
|
826
|
-
b_finish -= 1
|
827
|
-
end
|
828
|
-
|
829
|
-
# Now, compute the equivalence classes of positions of elements.
|
830
|
-
b_matches = Diff::LCS.__position_hash(b, b_start .. b_finish)
|
831
|
-
|
832
|
-
thresh = []
|
833
|
-
links = []
|
834
|
-
|
835
|
-
(a_start .. a_finish).each do |ii|
|
836
|
-
ai = a.kind_of?(String) ? a[ii, 1] : a[ii]
|
837
|
-
bm = b_matches[ai]
|
838
|
-
kk = nil
|
839
|
-
bm.reverse_each do |jj|
|
840
|
-
if kk and (thresh[kk] > jj) and (thresh[kk - 1] < jj)
|
841
|
-
thresh[kk] = jj
|
842
|
-
else
|
843
|
-
kk = Diff::LCS.__replace_next_larger(thresh, jj, kk)
|
844
|
-
end
|
845
|
-
links[kk] = [ (kk > 0) ? links[kk - 1] : nil, ii, jj ] unless kk.nil?
|
846
|
-
end
|
847
|
-
end
|
689
|
+
bj += 1
|
690
|
+
ai += 1
|
848
691
|
|
849
|
-
|
850
|
-
link = links[thresh.size - 1]
|
851
|
-
while not link.nil?
|
852
|
-
vector[link[1]] = link[2]
|
853
|
-
link = link[0]
|
692
|
+
res << el
|
854
693
|
end
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
def __replace_next_larger(enum, value, last_index = nil)
|
871
|
-
# Off the end?
|
872
|
-
if enum.empty? or (value > enum[-1])
|
873
|
-
enum << value
|
874
|
-
return enum.size - 1
|
875
|
-
end
|
876
|
-
|
877
|
-
# Binary search for the insertion point
|
878
|
-
last_index ||= enum.size
|
879
|
-
first_index = 0
|
880
|
-
while (first_index <= last_index)
|
881
|
-
ii = (first_index + last_index) >> 1
|
694
|
+
when Diff::LCS::Change
|
695
|
+
case action
|
696
|
+
when '-'
|
697
|
+
while ai < change.position
|
698
|
+
res << (string ? src[ai, 1] : src[ai])
|
699
|
+
ai += 1
|
700
|
+
bj += 1
|
701
|
+
end
|
702
|
+
ai += 1
|
703
|
+
when '+'
|
704
|
+
while bj < change.position
|
705
|
+
res << (string ? src[ai, 1] : src[ai])
|
706
|
+
ai += 1
|
707
|
+
bj += 1
|
708
|
+
end
|
882
709
|
|
883
|
-
|
710
|
+
bj += 1
|
884
711
|
|
885
|
-
|
886
|
-
return nil
|
887
|
-
elsif value > found
|
888
|
-
first_index = ii + 1
|
889
|
-
else
|
890
|
-
last_index = ii - 1
|
712
|
+
res << change.element
|
891
713
|
end
|
892
714
|
end
|
893
|
-
|
894
|
-
# The insertion point is in first_index; overwrite the next larger
|
895
|
-
# value.
|
896
|
-
enum[first_index] = value
|
897
|
-
return first_index
|
898
715
|
end
|
899
716
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
# Note: This will be deprecated as a public function in a future release.
|
905
|
-
def __inverse_vector(a, vector)
|
906
|
-
inverse = a.dup
|
907
|
-
(0 ... vector.size).each do |ii|
|
908
|
-
inverse[vector[ii]] = ii unless vector[ii].nil?
|
909
|
-
end
|
910
|
-
inverse
|
911
|
-
end
|
912
|
-
|
913
|
-
# Returns a hash mapping each element of an Enumerable to the set of
|
914
|
-
# positions it occupies in the Enumerable, optionally restricted to the
|
915
|
-
# elements specified in the range of indexes specified by +interval+.
|
916
|
-
#
|
917
|
-
# Note: This will be deprecated as a public function in a future release.
|
918
|
-
def __position_hash(enum, interval = 0 .. -1)
|
919
|
-
hash = Hash.new { |hh, kk| hh[kk] = [] }
|
920
|
-
interval.each do |ii|
|
921
|
-
kk = enum.kind_of?(String) ? enum[ii, 1] : enum[ii]
|
922
|
-
hash[kk] << ii
|
923
|
-
end
|
924
|
-
hash
|
717
|
+
while ai < src.size
|
718
|
+
res << (string ? src[ai, 1] : src[ai])
|
719
|
+
ai += 1
|
720
|
+
bj += 1
|
925
721
|
end
|
926
722
|
|
927
|
-
|
928
|
-
|
929
|
-
#
|
930
|
-
# WARNING: By default, this examines the whole patch, so this could take
|
931
|
-
# some time. This also works better with Diff::LCS::ContextChange or
|
932
|
-
# Diff::LCS::Change as its source, as an array will cause the creation
|
933
|
-
# of one of the above.
|
934
|
-
#
|
935
|
-
# Note: This will be deprecated as a public function in a future release.
|
936
|
-
def __diff_direction(src, patchset, limit = nil)
|
937
|
-
count = left = left_miss = right = right_miss = 0
|
938
|
-
string = src.kind_of?(String)
|
939
|
-
|
940
|
-
patchset.each do |change|
|
941
|
-
count += 1
|
942
|
-
|
943
|
-
case change
|
944
|
-
when Diff::LCS::Change
|
945
|
-
# With a simplistic change, we can't tell the difference between
|
946
|
-
# the left and right on '!' actions, so we ignore those. On '='
|
947
|
-
# actions, if there's a miss, we miss both left and right.
|
948
|
-
element = string ? src[change.position, 1] : src[change.position]
|
949
|
-
|
950
|
-
case change.action
|
951
|
-
when '-'
|
952
|
-
if element == change.element
|
953
|
-
left += 1
|
954
|
-
else
|
955
|
-
left_miss += 1
|
956
|
-
end
|
957
|
-
when '+'
|
958
|
-
if element == change.element
|
959
|
-
right += 1
|
960
|
-
else
|
961
|
-
right_miss += 1
|
962
|
-
end
|
963
|
-
when '='
|
964
|
-
if element != change.element
|
965
|
-
left_miss += 1
|
966
|
-
right_miss += 1
|
967
|
-
end
|
968
|
-
end
|
969
|
-
when Diff::LCS::ContextChange
|
970
|
-
case change.action
|
971
|
-
when '-' # Remove details from the old string
|
972
|
-
element = string ? src[change.old_position, 1] : src[change.old_position]
|
973
|
-
if element == change.old_element
|
974
|
-
left += 1
|
975
|
-
else
|
976
|
-
left_miss += 1
|
977
|
-
end
|
978
|
-
when '+'
|
979
|
-
element = string ? src[change.new_position, 1] : src[change.new_position]
|
980
|
-
if element == change.new_element
|
981
|
-
right += 1
|
982
|
-
else
|
983
|
-
right_miss += 1
|
984
|
-
end
|
985
|
-
when '='
|
986
|
-
le = string ? src[change.old_position, 1] : src[change.old_position]
|
987
|
-
re = string ? src[change.new_position, 1] : src[change.new_position]
|
988
|
-
|
989
|
-
left_miss += 1 if le != change.old_element
|
990
|
-
right_miss += 1 if re != change.new_element
|
991
|
-
when '!'
|
992
|
-
element = string ? src[change.old_position, 1] : src[change.old_position]
|
993
|
-
if element == change.old_element
|
994
|
-
left += 1
|
995
|
-
else
|
996
|
-
element = string ? src[change.new_position, 1] : src[change.new_position]
|
997
|
-
if element == change.new_element
|
998
|
-
right += 1
|
999
|
-
else
|
1000
|
-
left_miss += 1
|
1001
|
-
right_miss += 1
|
1002
|
-
end
|
1003
|
-
end
|
1004
|
-
end
|
1005
|
-
end
|
1006
|
-
|
1007
|
-
break if (not limit.nil?) && (count > limit)
|
1008
|
-
end
|
1009
|
-
|
1010
|
-
no_left = (left == 0) and (left_miss >= 0)
|
1011
|
-
no_right = (right == 0) and (right_miss >= 0)
|
723
|
+
res
|
724
|
+
end
|
1012
725
|
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
else
|
1019
|
-
raise "The provided patchset does not appear to apply to the provided value as either source or destination value."
|
1020
|
-
end
|
1021
|
-
end
|
726
|
+
# Given a set of patchset, convert the current version to the prior version.
|
727
|
+
# Does no auto-discovery.
|
728
|
+
def unpatch!(src, patchset)
|
729
|
+
patch(src, patchset, :unpatch)
|
730
|
+
end
|
1022
731
|
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
#
|
1028
|
-
# [ # patchset <- Diff::LCS.diff(a, b)
|
1029
|
-
# [ # one or more hunks
|
1030
|
-
# Diff::LCS::Change # one or more changes
|
1031
|
-
# ] ]
|
1032
|
-
#
|
1033
|
-
# [ # patchset, equivalent to the above
|
1034
|
-
# [ # one or more hunks
|
1035
|
-
# [ action, line, value ] # one or more changes
|
1036
|
-
# ] ]
|
1037
|
-
#
|
1038
|
-
# [ # patchset <- Diff::LCS.diff(a, b, Diff::LCS::ContextDiffCallbacks)
|
1039
|
-
# # OR <- Diff::LCS.sdiff(a, b, Diff::LCS::ContextDiffCallbacks)
|
1040
|
-
# [ # one or more hunks
|
1041
|
-
# Diff::LCS::ContextChange # one or more changes
|
1042
|
-
# ] ]
|
1043
|
-
#
|
1044
|
-
# [ # patchset, equivalent to the above
|
1045
|
-
# [ # one or more hunks
|
1046
|
-
# [ action, [ old line, old value ], [ new line, new value ] ]
|
1047
|
-
# # one or more changes
|
1048
|
-
# ] ]
|
1049
|
-
#
|
1050
|
-
# [ # patchset <- Diff::LCS.sdiff(a, b)
|
1051
|
-
# # OR <- Diff::LCS.diff(a, b, Diff::LCS::SDiffCallbacks)
|
1052
|
-
# Diff::LCS::ContextChange # one or more changes
|
1053
|
-
# ]
|
1054
|
-
#
|
1055
|
-
# [ # patchset, equivalent to the above
|
1056
|
-
# [ action, [ old line, old value ], [ new line, new value ] ]
|
1057
|
-
# # one or more changes
|
1058
|
-
# ]
|
1059
|
-
#
|
1060
|
-
# The result of this will be either of the following.
|
1061
|
-
#
|
1062
|
-
# [ # patchset
|
1063
|
-
# Diff::LCS::ContextChange # one or more changes
|
1064
|
-
# ]
|
1065
|
-
#
|
1066
|
-
# [ # patchset
|
1067
|
-
# Diff::LCS::Change # one or more changes
|
1068
|
-
# ]
|
1069
|
-
#
|
1070
|
-
# If either of the above is provided, it will be returned as such.
|
1071
|
-
#
|
1072
|
-
# Note: This will be deprecated as a public function in a future release.
|
1073
|
-
def __normalize_patchset(patchset)
|
1074
|
-
patchset.map do |hunk|
|
1075
|
-
case hunk
|
1076
|
-
when Diff::LCS::ContextChange, Diff::LCS::Change
|
1077
|
-
hunk
|
1078
|
-
when Array
|
1079
|
-
if (not hunk[0].kind_of?(Array)) and hunk[1].kind_of?(Array) and hunk[2].kind_of?(Array)
|
1080
|
-
Diff::LCS::ContextChange.from_a(hunk)
|
1081
|
-
else
|
1082
|
-
hunk.map do |change|
|
1083
|
-
case change
|
1084
|
-
when Diff::LCS::ContextChange, Diff::LCS::Change
|
1085
|
-
change
|
1086
|
-
when Array
|
1087
|
-
# change[1] will ONLY be an array in a ContextChange#to_a call.
|
1088
|
-
# In Change#to_a, it represents the line (singular).
|
1089
|
-
if change[1].kind_of?(Array)
|
1090
|
-
Diff::LCS::ContextChange.from_a(change)
|
1091
|
-
else
|
1092
|
-
Diff::LCS::Change.from_a(change)
|
1093
|
-
end
|
1094
|
-
end
|
1095
|
-
end
|
1096
|
-
end
|
1097
|
-
else
|
1098
|
-
raise ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
|
1099
|
-
end
|
1100
|
-
end.flatten
|
1101
|
-
end
|
732
|
+
# Given a set of patchset, convert the current version to the next version.
|
733
|
+
# Does no auto-discovery.
|
734
|
+
def patch!(src, patchset)
|
735
|
+
patch(src, patchset, :patch)
|
1102
736
|
end
|
1103
737
|
end
|
1104
738
|
|
1105
|
-
|
739
|
+
require 'diff/lcs/backports'
|