diff-lcs 1.1.3 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/Code-of-Conduct.md +74 -0
  4. data/Contributing.md +119 -0
  5. data/History.md +400 -0
  6. data/{License.rdoc → License.md} +6 -5
  7. data/Manifest.txt +36 -4
  8. data/README.rdoc +35 -23
  9. data/Rakefile +106 -11
  10. data/bin/htmldiff +7 -4
  11. data/bin/ldiff +4 -1
  12. data/docs/COPYING.txt +21 -22
  13. data/docs/artistic.txt +127 -0
  14. data/lib/diff/lcs/array.rb +1 -15
  15. data/lib/diff/lcs/backports.rb +9 -0
  16. data/lib/diff/lcs/block.rb +4 -18
  17. data/lib/diff/lcs/callbacks.rb +233 -230
  18. data/lib/diff/lcs/change.rb +114 -109
  19. data/lib/diff/lcs/htmldiff.rb +17 -18
  20. data/lib/diff/lcs/hunk.rb +232 -116
  21. data/lib/diff/lcs/internals.rb +308 -0
  22. data/lib/diff/lcs/ldiff.rb +138 -177
  23. data/lib/diff/lcs/string.rb +1 -15
  24. data/lib/diff/lcs.rb +597 -963
  25. data/lib/diff-lcs.rb +1 -3
  26. data/spec/change_spec.rb +89 -0
  27. data/spec/diff_spec.rb +32 -16
  28. data/spec/fixtures/aX +1 -0
  29. data/spec/fixtures/bXaX +1 -0
  30. data/spec/fixtures/ds1.csv +50 -0
  31. data/spec/fixtures/ds2.csv +51 -0
  32. data/spec/fixtures/ldiff/output.diff +4 -0
  33. data/spec/fixtures/ldiff/output.diff-c +7 -0
  34. data/spec/fixtures/ldiff/output.diff-e +3 -0
  35. data/spec/fixtures/ldiff/output.diff-f +3 -0
  36. data/spec/fixtures/ldiff/output.diff-u +5 -0
  37. data/spec/fixtures/ldiff/output.diff.chef +4 -0
  38. data/spec/fixtures/ldiff/output.diff.chef-c +15 -0
  39. data/spec/fixtures/ldiff/output.diff.chef-e +3 -0
  40. data/spec/fixtures/ldiff/output.diff.chef-f +3 -0
  41. data/spec/fixtures/ldiff/output.diff.chef-u +9 -0
  42. data/spec/fixtures/ldiff/output.diff.chef2 +7 -0
  43. data/spec/fixtures/ldiff/output.diff.chef2-c +20 -0
  44. data/spec/fixtures/ldiff/output.diff.chef2-d +7 -0
  45. data/spec/fixtures/ldiff/output.diff.chef2-e +7 -0
  46. data/spec/fixtures/ldiff/output.diff.chef2-f +7 -0
  47. data/spec/fixtures/ldiff/output.diff.chef2-u +16 -0
  48. data/spec/fixtures/new-chef +4 -0
  49. data/spec/fixtures/new-chef2 +17 -0
  50. data/spec/fixtures/old-chef +4 -0
  51. data/spec/fixtures/old-chef2 +14 -0
  52. data/spec/hunk_spec.rb +83 -0
  53. data/spec/issues_spec.rb +154 -0
  54. data/spec/lcs_spec.rb +36 -16
  55. data/spec/ldiff_spec.rb +87 -0
  56. data/spec/patch_spec.rb +198 -172
  57. data/spec/sdiff_spec.rb +99 -89
  58. data/spec/spec_helper.rb +149 -59
  59. data/spec/traverse_balanced_spec.rb +191 -167
  60. data/spec/traverse_sequences_spec.rb +105 -51
  61. metadata +218 -99
  62. data/.gemtest +0 -0
  63. data/History.rdoc +0 -54
  64. data/diff-lcs.gemspec +0 -51
  65. data/docs/artistic.html +0 -289
data/lib/diff/lcs.rb CHANGED
@@ -1,651 +1,509 @@
1
- # -*- ruby encoding: utf-8 -*-
2
-
3
- module Diff
4
- # = Diff::LCS 1.1.3
5
- # Computes "intelligent" differences between two sequenced Enumerables.
6
- # This is an implementation of the McIlroy-Hunt "diff" algorithm for
7
- # Enumerable objects that include Diffable.
8
- #
9
- # Based on Mario I. Wolczko's Smalltalk version (1.2, 1993) and Ned Konz's
10
- # Perl version (Algorithm::Diff 1.15).
11
- #
12
- # == Synopsis
13
- # require 'diff/lcs'
14
- #
15
- # seq1 = %w(a b c e h j l m n p)
16
- # seq2 = %w(b c d e f j k l m r s t)
17
- #
18
- # lcs = Diff::LCS.LCS(seq1, seq2)
19
- # diffs = Diff::LCS.diff(seq1, seq2)
20
- # sdiff = Diff::LCS.sdiff(seq1, seq2)
21
- # seq = Diff::LCS.traverse_sequences(seq1, seq2, callback_obj)
22
- # bal = Diff::LCS.traverse_balanced(seq1, seq2, callback_obj)
23
- # seq2 == Diff::LCS.patch(seq1, diffs)
24
- # seq2 == Diff::LCS.patch!(seq1, diffs)
25
- # seq1 == Diff::LCS.unpatch(seq2, diffs)
26
- # seq1 == Diff::LCS.unpatch!(seq2, diffs)
27
- # seq2 == Diff::LCS.patch(seq1, sdiff)
28
- # seq2 == Diff::LCS.patch!(seq1, sdiff)
29
- # seq1 == Diff::LCS.unpatch(seq2, sdiff)
30
- # seq1 == Diff::LCS.unpatch!(seq2, sdiff)
31
- #
32
- # Alternatively, objects can be extended with Diff::LCS:
33
- #
34
- # seq1.extend(Diff::LCS)
35
- # lcs = seq1.lcs(seq2)
36
- # diffs = seq1.diff(seq2)
37
- # sdiff = seq1.sdiff(seq2)
38
- # seq = seq1.traverse_sequences(seq2, callback_obj)
39
- # bal = seq1.traverse_balanced(seq2, callback_obj)
40
- # seq2 == seq1.patch(diffs)
41
- # seq2 == seq1.patch!(diffs)
42
- # seq1 == seq2.unpatch(diffs)
43
- # seq1 == seq2.unpatch!(diffs)
44
- # seq2 == seq1.patch(sdiff)
45
- # seq2 == seq1.patch!(sdiff)
46
- # seq1 == seq2.unpatch(sdiff)
47
- # seq1 == seq2.unpatch!(sdiff)
48
- #
49
- # Default extensions are provided for Array and String objects through the
50
- # use of 'diff/lcs/array' and 'diff/lcs/string'.
51
- #
52
- # == Introduction (by Mark-Jason Dominus)
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
- # &copy; 2000&ndash;2002 and the Smalltalk diff version by Mario I.
109
- # Wolczko, copyright &copy; 1993. Documentation includes work by
110
- # Mark-Jason Dominus.
111
- #
112
- # == Licence
113
- # Copyright &copy; 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#LCS.
60
+ # +self+ and +other+. See Diff::LCS#lcs.
141
61
  #
142
62
  # lcs = seq1.lcs(seq2)
143
- def lcs(other, &block) #:yields self[ii] if there are matched subsequences:
144
- Diff::LCS.LCS(self, other, &block)
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::diff(self, other, callbacks, &block)
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::sdiff(self, other, callbacks, &block)
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 a copy of +self+ with the provided +patchset+. See
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::patch(self.dup, patchset)
102
+ Diff::LCS.patch(self, patchset)
178
103
  end
104
+ alias unpatch patch
179
105
 
180
- # Attempts to unpatch a copy of +self+ with the provided +patchset+. See
181
- # Diff::LCS#patch.
182
- def unpatch(patchset)
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::patch!(self, patchset)
110
+ Diff::LCS.patch!(self, patchset)
190
111
  end
191
112
 
192
- # Attempts to unpatch +self+ with the provided +patchset+. See
193
- # Diff::LCS#unpatch. Does no autodiscovery.
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::unpatch!(self, patchset)
117
+ Diff::LCS.unpatch!(self, patchset)
196
118
  end
197
- end
198
119
 
199
- module Diff::LCS
200
- class << self
201
- # Given two sequenced Enumerables, LCS returns an Array containing their
202
- # longest common subsequences.
203
- #
204
- # lcs = Diff::LCS.LCS(seq1, seq2)
205
- #
206
- # This array whose contents is such that:
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
- # Diff::LCS.diff computes the smallest set of additions and deletions
231
- # necessary to turn the first sequence into the second, and returns a
232
- # description of these changes.
233
- #
234
- # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate
235
- # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If
236
- # a Class argument is provided for +callbacks+, #diff will attempt to
237
- # initialise it. If the +callbacks+ object (possibly initialised)
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
- # Diff::LCS.sdiff computes all necessary components to show two sequences
263
- # and their minimized differences side by side, just like the Unix
264
- # utility <em>sdiff</em> does:
265
- #
266
- # old < -
267
- # same same
268
- # before | after
269
- # - > new
270
- #
271
- # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate
272
- # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If
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
- # Diff::LCS.traverse_sequences is the most general facility provided by this
300
- # module; +diff+ and +LCS+ are implemented as calls to it.
301
- #
302
- # The arguments to #traverse_sequences are the two sequences to
303
- # traverse, and a callback object, like this:
304
- #
305
- # traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
306
- #
307
- # #diff is implemented with #traverse_sequences.
308
- #
309
- # == Callback Methods
310
- # Optional callback methods are <em>emphasized</em>.
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
- # last B?
448
- if bj == b_size and ai < a_size
449
- if callbacks.respond_to?(:finished_b) and not run_finished_b
450
- ax = string ? seq1[ai, 1] : seq1[ai]
451
- bx = string ? seq2[-1, 1] : seq2[-1]
452
- event = Diff::LCS::ContextChange.new('<', ai, ax, (b_size - 1), bx)
453
- event = yield event if block_given?
454
- callbacks.finished_b(event)
455
- run_finished_b = true
456
- else
457
- bx = string ? seq2[bj, 1] : seq2[bj]
458
- loop do
459
- ax = string ? seq1[ai, 1] : seq1[ai]
460
- event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
461
- event = yield event if block_given?
462
- callbacks.discard_a(event)
463
- ai += 1
464
- break unless bj < b_size
465
- end
466
- end
467
- end
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
- if ai < a_size
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
- # #traverse_balanced is an alternative to #traverse_sequences. It
490
- # uses a different algorithm to iterate through the entries in the
491
- # computed longest common subsequence. Instead of viewing the changes as
492
- # insertions or deletions from one of the sequences, #traverse_balanced
493
- # will report <em>changes</em> between the sequences. To represent a
494
- #
495
- # The arguments to #traverse_balanced are the two sequences to traverse
496
- # and a callback object, like this:
497
- #
498
- # traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
499
- #
500
- # #sdiff is implemented with #traverse_balanced.
501
- #
502
- # == Callback Methods
503
- # Optional callback methods are <em>emphasized</em>.
504
- #
505
- # callbacks#match:: Called when +a+ and +b+ are pointing
506
- # to common elements in +A+ and +B+.
507
- # callbacks#discard_a:: Called when +a+ is pointing to an
508
- # element not in +B+.
509
- # callbacks#discard_b:: Called when +b+ is pointing to an
510
- # element not in +A+.
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
- break if ma >= matches.size # end of matches?
588
- mb = matches[ma]
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
- case [(ai < ma), (bj < mb)]
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
- when [false, true]
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
- # Match
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('=', ai, ax, bj, bx)
377
+ event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
631
378
  event = yield event if block_given?
632
- callbacks.match(event)
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
- while (ai < a_size) or (bj < b_size)
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 < a_size), (bj < b_size)]
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
- PATCH_MAP = { #:nodoc:
675
- :patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' },
676
- :unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }
677
- }
678
-
679
- # Given a patchset, convert the current version to the new
680
- # version. If +direction+ is not specified (must be
681
- # <tt>:patch</tt> or <tt>:unpatch</tt>), then discovery of the
682
- # direction of the patch will be attempted.
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
- # Normalize the patchset.
689
- patchset = __normalize_patchset(patchset)
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
- direction ||= Diff::LCS.__diff_direction(src, patchset)
692
- direction ||= :patch
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
- ai = bj = 0
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
- patchset.each do |change|
697
- # Both Change and ContextChange support #action
698
- action = PATCH_MAP[direction][change.action]
579
+ PATCH_MAP = { #:nodoc:
580
+ :patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' }.freeze,
581
+ :unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }.freeze
582
+ }.freeze
699
583
 
700
- case change
701
- when Diff::LCS::ContextChange
702
- case direction
703
- when :patch
704
- el = change.new_element
705
- op = change.old_position
706
- np = change.new_position
707
- when :unpatch
708
- el = change.old_element
709
- op = change.new_position
710
- np = change.old_position
711
- end
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
- case action
714
- when '-' # Remove details from the old string
715
- while ai < op
716
- res << (string ? src[ai, 1] : src[ai])
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
- when '='
731
- # This only appears in sdiff output with the SDiff callback.
732
- # Therefore, we only need to worry about dealing with a single
733
- # element.
734
- res << el
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
- when '!'
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
- bj += 1
746
- ai += 1
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
- res << el
749
- end
750
- when Diff::LCS::Change
751
- case action
752
- when '-'
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
- res
780
- end
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
- unless thresh.empty?
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
- end
856
-
857
- vector
858
- end
859
-
860
- # Find the place at which +value+ would normally be inserted into the
861
- # Enumerable. If that place is already occupied by +value+, do nothing
862
- # and return +nil+. If the place does not exist (i.e., it is off the end
863
- # of the Enumerable), add it to the end. Otherwise, replace the element
864
- # at that point with +value+. It is assumed that the Enumerable's values
865
- # are numeric.
866
- #
867
- # This operation preserves the sort order.
868
- #
869
- # Note: This will be deprecated as a public function in a future release.
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
- found = enum[ii]
710
+ bj += 1
884
711
 
885
- if value == found
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
- # If +vector+ maps the matching elements of another collection onto this
901
- # Enumerable, compute the inverse +vector+ that maps this Enumerable
902
- # onto the collection. (Currently unused.)
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
- # Examine the patchset and the source to see in which direction the
928
- # patch should be applied.
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
- case [no_left, no_right]
1014
- when [false, true]
1015
- return :patch
1016
- when [true, false]
1017
- return :unpatch
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
- # Normalize the patchset. A patchset is always a sequence of changes, but
1024
- # how those changes are represented may vary, depending on how they were
1025
- # generated. In all cases we support, we also support the array
1026
- # representation of the changes. The formats are:
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
- # vim: ft=ruby
739
+ require 'diff/lcs/backports'