Module: Diff::LCS

Included in:
Array, String
Defined in:
lib/diff/lcs.rb,
lib/diff/lcs.rb,
lib/diff/lcs/version.rb

Overview

## How Diff Works (by Mark-Jason Dominus)

I once read an article written by the authors of diff; they said that they hard worked very hard on the algorithm until they found the right one.

I think what they ended up using (and I hope someone will correct me, because I am not very confident about this) was the ‘longest common subsequence’ method. In the LCS problem, you have two sequences of items:

“‘ a b c d f g h j q z a b c d e f g i j k r x y z “`

and you want to find the longest sequence of items that is present in both original sequences in the same order. That is, you want to find a new sequence S which can be obtained from the first sequence by deleting some items, and from the second sequence by deleting other items. You also want S to be as long as possible. In this case S is:

“‘ a b c d f g j z “`

From there it’s only a small step to get diff-like output:

“‘ e h i k q r x y + - + + - + + + “`

This module solves the LCS problem. It also includes a canned function to generate diff-like output.

It might seem from the example above that the LCS of two sequences is always pretty obvious, but that’s not always the case, especially when the two sequences have many repeated elements. For example, consider

“‘ a x b y c z p d q a b c a x b y c z “`

A naive approach might start by matching up the a and b that appear at the beginning of each sequence, like this:

“‘ a x b y c z p d q a b c a b y c z “`

This finds the common subsequence ‘a b c z`. But actually, the LCS is `a x b y c z`:

“‘

a x b y c z p d q

a b c a x b y c z “‘

Defined Under Namespace

Modules: Internals Classes: Block, Change, ContextChange, ContextDiffCallbacks, DefaultCallbacks, DiffCallbacks, Hunk, Ldiff, SDiffCallbacks

Constant Summary collapse

VERSION =
"2.0.0"
SequenceCallbacks =

An alias for DefaultCallbacks used in Diff::LCS.traverse_sequences.

“‘ruby Diff::LCS.lcs(seq1, seq2, Diff::LCS::SequenceCallbacks) “`

Diff::LCS::DefaultCallbacks
BalancedCallbacks =

An alias for DefaultCallbacks used in Diff::LCS.traverse_balanced.

“‘ruby Diff::LCS.lcs(seq1, seq2, Diff::LCS::BalancedCallbacks) “`

Diff::LCS::DefaultCallbacks

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.callbacks_for(callbacks) ⇒ Object

:nodoc:



4
5
6
7
8
# File 'lib/diff/lcs/internals.rb', line 4

def callbacks_for(callbacks) # :nodoc:
  callbacks.new
rescue
  callbacks
end

.diff(seq1, seq2, callbacks = nil, &block) ⇒ Object

diff computes the smallest set of additions and deletions necessary to turn the first sequence into the second, and returns a description of these changes.

See Diff::LCS::DiffCallbacks for the default behaviour. An alternate behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If the callbacks object responds to #finish, it will be called.



160
161
# File 'lib/diff/lcs.rb', line 160

def self.diff(seq1, seq2, callbacks = nil, &block) = # :yields: diff changes
diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks, &block)

.diff_traversal(method, seq1, seq2, callbacks, &block) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/diff/lcs/internals.rb', line 11

def diff_traversal(method, seq1, seq2, callbacks, &block)
  callbacks = callbacks_for(callbacks)

  case method
  when :diff
    traverse_sequences(seq1, seq2, callbacks)
  when :sdiff
    traverse_balanced(seq1, seq2, callbacks)
  end

  callbacks.finish if callbacks.respond_to? :finish

  if block
    callbacks.diffs.map do |hunk|
      if hunk.is_a? Array
        hunk.map { block.call(_1) }
      else
        block.call(hunk)
      end
    end
  else
    callbacks.diffs
  end
end

.lcs(seq1, seq2, &block) ⇒ Object

Returns an Array containing the longest common subsequence(s) between seq and seq2.

> NOTE on comparing objects: Diff::LCS only works properly when each object can be > used as a key in a Hash. This means that those objects must implement the methods > #hash and #eql? such that two objects containing identical values compare > identically for key purposes. That is: > > “‘ > O.new(’a’).eql?(O.new(‘a’)) == true && O.new(‘a’).hash == O.new(‘a’).hash > “‘



140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/diff/lcs.rb', line 140

def self.lcs(seq1, seq2, &block) # :yields: seq1[i] for each matched
  matches = Diff::LCS::Internals.lcs(seq1, seq2)
  [].tap { |result|
    matches.each_index do
      next if matches[_1].nil?

      v = seq1[_1]
      v = block.call(v) if block

      result << v
    end
  }
end

.patch(src, patchset, direction = nil) ⇒ Object

Applies a patchset to the sequence src according to the direction (:patch or :unpatch), producing a new sequence.

If the direction is not specified, Diff::LCS::patch will attempt to discover the direction of the patchset.

A patchset can be considered to apply forward (:patch) if the following expression is true:

“‘ruby patch(s1, diff(s1, s2)) # => s2 “`

A patchset can be considered to apply backward (:unpatch) if the following expression is true:

“‘ruby patch(s2, diff(s1, s2)) # => s1 “`

If the patchset contains no changes, the src value will be returned as either src.dup or src. A patchset can be deemed as having no changes if the following predicate returns true:

“‘ruby patchset.empty? or patchset.flatten(1).all? { |change| change.unchanged? } “`

### Patchsets

A patchset is always an enumerable sequence of changes, hunks of changes, or a mix of the two. A hunk of changes is an enumerable sequence of changes:

“‘ [ # patchset

# change
[ # hunk
  # change
]

] “‘

The patch method accepts ‘patchset`s that are enumerable sequences containing either Diff::LCS::Change objects (or a subclass) or the array representations of those objects. Prior to application, array representations of Diff::LCS::Change objects will be reified.



607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
# File 'lib/diff/lcs.rb', line 607

def self.patch(src, patchset, direction = nil)
  # Normalize the patchset.
  has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset)

  return src.respond_to?(:dup) ? src.dup : src unless has_changes

  # Start with a new empty type of the source's class
  res = src.class.new

  direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset)

  a_i = b_j = 0

  patch_map = PATCH_MAP[direction]

  patchset.each do |change|
    # Both Change and ContextChange support #action
    action = patch_map[change.action]

    case change
    when Diff::LCS::ContextChange
      case direction
      when :patch
        el = change.new_element
        op = change.old_position
        np = change.new_position
      when :unpatch
        el = change.old_element
        op = change.new_position
        np = change.old_position
      end

      case action
      when "-" # Remove details from the old string
        while a_i < op
          res << src[a_i]
          a_i += 1
          b_j += 1
        end
        a_i += 1
      when "+"
        while b_j < np
          res << src[a_i]
          a_i += 1
          b_j += 1
        end

        res << el
        b_j += 1
      when "="
        # This only appears in sdiff output with the SDiff callback.
        # Therefore, we only need to worry about dealing with a single
        # element.
        res << el

        a_i += 1
        b_j += 1
      when "!"
        while a_i < op
          res << src[a_i]
          a_i += 1
          b_j += 1
        end

        b_j += 1
        a_i += 1

        res << el
      end
    when Diff::LCS::Change
      case action
      when "-"
        while a_i < change.position
          res << src[a_i]
          a_i += 1
          b_j += 1
        end
        a_i += 1
      when "+"
        while b_j < change.position
          res << src[a_i]
          a_i += 1
          b_j += 1
        end

        b_j += 1

        res << change.element
      end
    end
  end

  while a_i < src.size
    res << src[a_i]
    a_i += 1
    b_j += 1
  end

  res
end

.patch!(src, patchset) ⇒ Object

Given a patchset, convert the current version to the next version. Does no auto-discovery.



714
# File 'lib/diff/lcs.rb', line 714

def self.patch!(src, patchset) = patch(src, patchset, :patch)

.sdiff(seq1, seq2, callbacks = nil, &block) ⇒ Object

sdiff computes all necessary components to show two sequences and their minimized differences side by side, just like the Unix utility sdiff does:

“‘ old < - same same before | after

  • > new

“‘

See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If the callbacks object responds to #finish, it will be called.

Each element of a returned array is a Diff::LCS::ContextChange object, which can be implicitly converted to an array.

“‘ruby Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)|

case action
when '!'
  # replace
when '-'
  # delete
when '+'
  # insert
end

end “‘



193
194
# File 'lib/diff/lcs.rb', line 193

def self.sdiff(seq1, seq2, callbacks = nil, &block) = # :yields: diff changes
diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks, &block)

.traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks) ⇒ Object

#traverse_balanced is an alternative to #traverse_sequences. It uses a different algorithm to iterate through the entries in the computed longest common subsequence. Instead of viewing the changes as insertions or deletions from one of the sequences, #traverse_balanced will report changes between the sequences.

The arguments to #traverse_balanced are the two sequences to traverse and a callback object, like this:

“‘ruby traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks) “`

#sdiff is implemented using #traverse_balanced.

### Callback Methods

  • ‘callbacks#match`: Called when a and b are pointing to common elements in A and B.

  • ‘callbacks#discard_a`: Called when a is pointing to an element not in B.

  • ‘callbacks#discard_b`: Called when b is pointing to an element not in A.

  • ‘callbacks#change`: Called when a and b are pointing to the same relative position, but A[a] and B[b] are not the same; a change has occurred. Optional.

#traverse_balanced might be a bit slower than #traverse_sequences, noticeable only while processing large amounts of data.

### Algorithm

“‘ a—+

v

A = a b c e h j l m n p B = b c d e f j k l m r s t

^

b—+ “‘

#### Matches

If there are two arrows (a and b) pointing to elements of sequences A and B, the arrows will initially point to the first elements of their respective sequences. #traverse_sequences will advance the arrows through the sequences one element at a time, calling a method on the user-specified callback object before each advance. It will advance the arrows in such a way that if there are elements A[i] and B[j] which are both equal and part of the longest common subsequence, there will be some moment during the execution of #traverse_sequences when arrow a is pointing to A[i] and arrow b is pointing to B[j]. When this happens, #traverse_sequences will call ‘callbacks#match` and then it will advance both arrows.

#### Discards

Otherwise, one of the arrows is pointing to an element of its sequence that is not part of the longest common subsequence. #traverse_sequences will advance that arrow and will call ‘callbacks#discard_a` or `callbacks#discard_b`, depending on which arrow it advanced.

#### Changes

If both a and b point to elements that are not part of the longest common subsequence, then #traverse_sequences will try to call ‘callbacks#change` and advance both arrows. If `callbacks#change` is not implemented, then `callbacks#discard_a` and `callbacks#discard_b` will be called in turn.

The methods for ‘callbacks#match`, `callbacks#discard_a`, `callbacks#discard_b`, and `callbacks#change` are invoked with an event comprising the action (“=”, “+”, “-”, or “!”, respectively), the indexes i and j, and the elements A[i] and B[j]. Return values are discarded by #traverse_balanced.

Context

Note that i and j may not be the same index position, even if a and b are considered to be pointing to matching or changed elements.



450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
# File 'lib/diff/lcs.rb', line 450

def self.traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
  matches = Diff::LCS::Internals.lcs(seq1, seq2)
  a_size = seq1.size
  b_size = seq2.size
  a_i = b_j = m_b = 0
  m_a = -1

  # Process all the lines in the match vector.
  loop do
    # Find next match indexes `m_a` and `m_b`
    loop do
      m_a += 1
      break unless m_a < matches.size && matches[m_a].nil?
    end

    break if m_a >= matches.size # end of matches?

    m_b = matches[m_a]

    # Change(seq2)
    while (a_i < m_a) || (b_j < m_b)
      a_x = seq1[a_i]
      b_x = seq2[b_j]

      case [(a_i < m_a), (b_j < m_b)]
      when [true, true]
        if callbacks.respond_to?(:change)
          event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
          event = yield event if block_given?
          callbacks.change(event)
          a_i += 1
        else
          event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
          event = yield event if block_given?
          callbacks.discard_a(event)
          a_i += 1
          a_x = seq1[a_i]
          event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
          event = yield event if block_given?
          callbacks.discard_b(event)
        end

        b_j += 1
      when [true, false]
        event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.discard_a(event)
        a_i += 1
      when [false, true]
        event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.discard_b(event)
        b_j += 1
      end
    end

    # Match
    a_x = seq1[a_i]
    b_x = seq2[b_j]
    event = Diff::LCS::ContextChange.new("=", a_i, a_x, b_j, b_x)
    event = yield event if block_given?
    callbacks.match(event)
    a_i += 1
    b_j += 1
  end

  while (a_i < a_size) || (b_j < b_size)
    a_x = seq1[a_i]
    b_x = seq2[b_j]

    case [(a_i < a_size), (b_j < b_size)]
    when [true, true]
      if callbacks.respond_to?(:change)
        event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.change(event)
        a_i += 1
      else
        event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.discard_a(event)
        a_i += 1
        a_x = seq1[a_i]
        event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.discard_b(event)
      end

      b_j += 1
    when [true, false]
      event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
      event = yield event if block_given?
      callbacks.discard_a(event)
      a_i += 1
    when [false, true]
      event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
      event = yield event if block_given?
      callbacks.discard_b(event)
      b_j += 1
    end
  end
end

.traverse_sequences(seq1, seq2, callbacks = nil) ⇒ Object

#traverse_sequences is the most general facility provided by this module; #diff and #lcs are implemented using #traverse_sequences.

The arguments to #traverse_sequence are the two sequences to traverse, and a callback object, like this:

“‘ruby traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks) “`

### Callback Methods

  • ‘callbacks#match`: Called when a and b are pointing to common elements in A and B.

  • ‘callbacks#discard_a`: Called when a is pointing to an element not in B.

  • ‘callbacks#discard_b`: Called when b is pointing to an element not in A.

  • ‘callbacks#finished_a`: Called when a has reached the end of sequence A. Optional.

  • ‘callbacks#finished_b`: Called when b has reached the end of sequence B. Optional.

### Algorithm

“‘ a—+

v

A = a b c e h j l m n p B = b c d e f j k l m r s t

^

b—+ “‘

If there are two arrows (a and b) pointing to elements of sequences A and B, the arrows will initially point to the first elements of their respective sequences. #traverse_sequences will advance the arrows through the sequences one element at a time, calling a method on the user-specified callback object before each advance. It will advance the arrows in such a way that if there are elements A[i] and B[j] which are both equal and part of the longest common subsequence, there will be some moment during the execution of #traverse_sequences when arrow a is pointing to A[i] and arrow b is pointing to B[j]. When this happens, #traverse_sequences will call ‘callbacks#match` and then it will advance both arrows.

Otherwise, one of the arrows is pointing to an element of its sequence that is not part of the longest common subsequence. #traverse_sequences will advance that arrow and will call ‘callbacks#discard_a` or `callbacks#discard_b`, depending on which arrow it advanced. If both arrows point to elements that are not part of the longest common subsequence, then #traverse_sequences will advance arrow a and call the appropriate callback, then it will advance arrow b and call the appropriate callback.

The methods for ‘callbacks#match`, `callbacks#discard_a`, and `callbacks#discard_b` are invoked with an event comprising the action (“=”, “+”, or “-”, respectively), the indexes i and j, and the elements A[i] and B[j]. Return values are discarded by #traverse_sequences.

#### End of Sequences

If arrow a reaches the end of its sequence before arrow b does, #traverse_sequence will try to call ‘callbacks#finished_a` with the last index and element of A (A[-1]) and the current index and element of B (B[j]). If `callbacks#finished_a` does not exist, then `callbacks#discard_b` will be called on each element of B until the end of the sequence is reached (the call will be done with A[-1] and B[j] for each element).

If b reaches the end of B before a reaches the end of A, ‘callbacks#finished_b` will be called with the current index and element of A (A[i]) and the last index and element of B (A[-1]). Again, if `callbacks#finished_b` does not exist on the callback object, then `callbacks#discard_a` will be called on each element of A until the end of the sequence is reached (A[i] and B[-1]).

There is a chance that one additional ‘callbacks#discard_a` or `callbacks#discard_b` will be called after the end of the sequence is reached, if a has not yet reached the end of A or b has not yet reached the end of B.



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/diff/lcs.rb', line 269

def self.traverse_sequences(seq1, seq2, callbacks = nil) # :yields: change events
  callbacks ||= Diff::LCS::SequenceCallbacks
  matches = Diff::LCS::Internals.lcs(seq1, seq2)

  run_finished_a = run_finished_b = false

  a_size = seq1.size
  b_size = seq2.size
  a_i = b_j = 0

  matches.each do |b_line|
    if b_line.nil?
      unless seq1[a_i].nil?
        a_x = seq1[a_i]
        b_x = seq2[b_j]

        event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.discard_a(event)
      end
    else
      a_x = seq1[a_i]

      loop do
        break unless b_j < b_line

        b_x = seq2[b_j]
        event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.discard_b(event)
        b_j += 1
      end
      b_x = seq2[b_j]
      event = Diff::LCS::ContextChange.new("=", a_i, a_x, b_j, b_x)
      event = yield event if block_given?
      callbacks.match(event)
      b_j += 1
    end

    a_i += 1
  end

  # The last entry (if any) processed was a match. `a_i` and `b_j` point just past the
  # last matching lines in their sequences.
  while (a_i < a_size) || (b_j < b_size)
    # last A?
    if a_i == a_size && b_j < b_size
      if callbacks.respond_to?(:finished_a) && !run_finished_a
        a_x = seq1[-1]
        b_x = seq2[b_j]
        event = Diff::LCS::ContextChange.new(">", a_size - 1, a_x, b_j, b_x)
        event = yield event if block_given?
        callbacks.finished_a(event)
        run_finished_a = true
      else
        a_x = seq1[a_i]
        loop do
          b_x = seq2[b_j]
          event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
          event = yield event if block_given?
          callbacks.discard_b(event)
          b_j += 1
          break unless b_j < b_size
        end
      end
    end

    # last B?
    if b_j == b_size && a_i < a_size
      if callbacks.respond_to?(:finished_b) && !run_finished_b
        a_x = seq1[a_i]
        b_x = seq2[-1]
        event = Diff::LCS::ContextChange.new("<", a_i, a_x, b_size - 1, b_x)
        event = yield event if block_given?
        callbacks.finished_b(event)
        run_finished_b = true
      else
        b_x = seq2[b_j]
        loop do
          a_x = seq1[a_i]
          event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
          event = yield event if block_given?
          callbacks.discard_a(event)
          a_i += 1
          break unless b_j < b_size
        end
      end
    end

    if a_i < a_size
      a_x = seq1[a_i]
      b_x = seq2[b_j]
      event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
      event = yield event if block_given?
      callbacks.discard_a(event)
      a_i += 1
    end

    if b_j < b_size
      a_x = seq1[a_i]
      b_x = seq2[b_j]
      event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
      event = yield event if block_given?
      callbacks.discard_b(event)
      b_j += 1
    end
  end
end

.unpatch!(src, patchset) ⇒ Object

Given a patchset, convert the current version to the prior version. Does no auto-discovery.



710
# File 'lib/diff/lcs.rb', line 710

def self.unpatch!(src, patchset) = patch(src, patchset, :unpatch)

Instance Method Details

#diff(other, callbacks = nil, &block) ⇒ Object

Returns the difference set between self and other. See Diff::LCS.diff.



75
# File 'lib/diff/lcs.rb', line 75

def diff(other, callbacks = nil, &block) = Diff::LCS.diff(self, other, callbacks, &block)

#lcs(other, &block) ⇒ Object

Returns an Array containing the longest common subsequence(s) between self and other. See Diff::LCS.lcs.



71
72
# File 'lib/diff/lcs.rb', line 71

def lcs(other, &block) = # :yields: self[i] if there are matched subsequences
Diff::LCS.lcs(self, other, &block)

#patch(patchset) ⇒ Object

Attempts to patch self with the provided patchset. A new sequence based on self and the patchset will be created. See Diff::LCS.patch. Attempts to autodiscover the direction of the patch.



94
# File 'lib/diff/lcs.rb', line 94

def patch(patchset) = Diff::LCS.patch(self, patchset)

#patch!(patchset) ⇒ Object

Attempts to patch self with the provided patchset. A new sequence based on self and the patchset will be created. See Diff::LCS.patch!. Does no patch direction autodiscovery.



100
# File 'lib/diff/lcs.rb', line 100

def patch!(patchset) = Diff::LCS.patch!(self, patchset)

#patch_me(patchset) ⇒ Object

Attempts to patch self with the provided patchset, using #patch!. If the sequence this is used on supports #replace, the value of self will be replaced. See Diff::LCS.patch!. Does no patch direction autodiscovery.



110
111
112
113
114
115
116
# File 'lib/diff/lcs.rb', line 110

def patch_me(patchset)
  if respond_to? :replace
    replace(patch!(patchset))
  else
    patch!(patchset)
  end
end

#sdiff(other, callbacks = nil, &block) ⇒ Object

Returns the balanced (“side-by-side”) difference set between self and other. See Diff::LCS.sdiff.



79
# File 'lib/diff/lcs.rb', line 79

def sdiff(other, callbacks = nil, &block) = Diff::LCS.sdiff(self, other, callbacks, &block)

#traverse_balanced(other, callbacks = nil, &block) ⇒ Object

Traverses the discovered longest common subsequences between self and other using the alternate, balanced algorithm. See Diff::LCS.traverse_balanced.



88
89
# File 'lib/diff/lcs.rb', line 88

def traverse_balanced(other, callbacks = nil, &block) =
Diff::LCS.traverse_balanced(self, other, callbacks || Diff::LCS::BalancedCallbacks, &block)

#traverse_sequences(other, callbacks = nil, &block) ⇒ Object

Traverses the discovered longest common subsequences between self and other. See Diff::LCS.traverse_sequences.



83
84
# File 'lib/diff/lcs.rb', line 83

def traverse_sequences(other, callbacks = nil, &block) =
Diff::LCS.traverse_sequences(self, other, callbacks || Diff::LCS::SequenceCallbacks, &block)

#unpatchObject

Attempts to patch self with the provided patchset. A new sequence based on self and the patchset will be created. See Diff::LCS.patch. Attempts to autodiscover the direction of the patch.



95
# File 'lib/diff/lcs.rb', line 95

def patch(patchset) = Diff::LCS.patch(self, patchset)

#unpatch!(patchset) ⇒ Object

Attempts to unpatch self with the provided patchset. A new sequence based on self and the patchset will be created. See Diff::LCS.unpatch!. Does no patch direction autodiscovery.



105
# File 'lib/diff/lcs.rb', line 105

def unpatch!(patchset) = Diff::LCS.unpatch!(self, patchset)

#unpatch_me(patchset) ⇒ Object

Attempts to unpatch self with the provided patchset, using #unpatch!. If the sequence this is used on supports #replace, the value of self will be replaced. See Diff::LCS#unpatch. Does no patch direction autodiscovery.



121
122
123
124
125
126
127
# File 'lib/diff/lcs.rb', line 121

def unpatch_me(patchset)
  if respond_to? :replace
    replace(unpatch!(patchset))
  else
    unpatch!(patchset)
  end
end