opf/openproject

View on GitHub
lib_static/redmine/diff/array_string_diff.rb

Summary

Maintainability
C
1 day
Test Coverage
#   = Diff
#   (({diff.rb})) - computes the differences between two arrays or
#   strings. Copyright (C) 2001 Lars Christensen
#
#   == Synopsis
#
#       diff = Diff.new(a, b)
#       b = a.patch(diff)
#
#   == Class Diff
#   === Class Methods
#   --- Diff.new(a, b)
#   --- a.diff(b)
#         Creates a Diff object which represent the differences between
#         ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays
#         of any objects, strings, or object of any class that include
#         module ((|Diffable|))
#
#   == Module Diffable
#   The module ((|Diffable|)) is intended to be included in any class for
#   which differences are to be computed. Diffable is included into String
#   and Array when (({diff.rb})) is (({require}))'d.
#
#   Classes including Diffable should implement (({[]})) to get element at
#   integer indices, (({<<})) to append elements to the object and
#   (({ClassName#new})) should accept 0 arguments to create a new empty
#   object.
#
#   === Instance Methods
#   --- Diffable#patch(diff)
#         Applies the differences from ((|diff|)) to the object ((|obj|))
#         and return the result. ((|obj|)) is not changed. ((|obj|)) and
#         can be either an array or a string, but must match the object
#         from which the ((|diff|)) was created.

class Redmine::Diff::ArrayStringDiff
  VERSION = 0.3

  def self.lcs(a, b)
    astart = 0
    bstart = 0
    afinish = a.length - 1
    bfinish = b.length - 1
    mvector = []

    # First we prune off any common elements at the beginning
    while astart <= afinish && bstart <= afinish && a[astart] == b[bstart]
      mvector[astart] = bstart
      astart += 1
      bstart += 1
    end

    # now the end
    while astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish]
      mvector[afinish] = bfinish
      afinish -= 1
      bfinish -= 1
    end

    bmatches = b.reverse_hash(bstart..bfinish)
    thresh = []
    links = []

    (astart..afinish).each do |aindex|
      aelem = a[aindex]
      next unless bmatches.has_key? aelem

      k = nil
      bmatches[aelem].reverse_each do |bindex|
        if k && (thresh[k] > bindex) && (thresh[k - 1] < bindex)
          thresh[k] = bindex
        else
          k = thresh.replacenextlarger(bindex, k)
        end
        links[k] = [k == 0 ? nil : links[k - 1], aindex, bindex] if k
      end
    end

    if !thresh.empty?
      link = links[thresh.length - 1]
      while link
        mvector[link[1]] = link[2]
        link = link[0]
      end
    end

    mvector
  end

  def makediff(a, b)
    mvector = self.class.lcs(a, b)
    ai = bi = 0
    while ai < mvector.length
      bline = mvector[ai]
      if bline
        while bi < bline
          discardb(bi, b[bi])
          bi += 1
        end
        match(ai, bi)
        bi += 1
      else
        discarda(ai, a[ai])
      end
      ai += 1
    end
    while ai < a.length
      discarda(ai, a[ai])
      ai += 1
    end
    while bi < b.length
      discardb(bi, b[bi])
      bi += 1
    end
    match(ai, bi)
    1
  end

  def compactdiffs
    diffs = []
    @diffs.each do |df|
      i = 0
      curdiff = []
      while i < df.length
        whot = df[i][0]
        s = @isstring ? df[i][2].chr : [df[i][2]]
        p = df[i][1]
        last = df[i][1]
        i += 1
        while df[i] && df[i][0] == whot && df[i][1] == last + 1
          s << df[i][2]
          last = df[i][1]
          i += 1
        end
        curdiff.push [whot, p, s]
      end
      diffs.push curdiff
    end
    diffs
  end

  attr_reader :diffs, :difftype

  def initialize(diffs_or_a, b = nil, isstring = nil)
    if b.nil?
      @diffs = diffs_or_a
      @isstring = isstring
    else
      @diffs = []
      @curdiffs = []
      makediff(diffs_or_a, b)
      @difftype = diffs_or_a.class
    end
  end

  def match(_ai, _bi)
    @diffs.push @curdiffs unless @curdiffs.empty?
    @curdiffs = []
  end

  def discarda(i, elem)
    @curdiffs.push ['-', i, elem]
  end

  def discardb(i, elem)
    @curdiffs.push ['+', i, elem]
  end

  def compact
    Diff.new(compactdiffs)
  end

  def compact!
    @diffs = compactdiffs
  end

  def inspect
    @diffs.inspect
  end
end