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.
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'