class DocDiff::Diff::EditScript

Attributes

additions[R]
count_a[R]
count_b[R]
deletions[R]

Public Class Methods

new() click to toggle source
# File lib/docdiff/diff/editscript.rb, line 7
def initialize
  @chunk_common = nil
  @chunk_add = []
  @chunk_del = []
  @list = []
  @list << @chunk_del
  @list << @chunk_add

  @cs = Subsequence.new
  @count_a = 0
  @count_b = 0
  @additions = 0
  @deletions = 0
end
parse_rcsdiff(input) click to toggle source
# File lib/docdiff/diff/rcsdiff.rb, line 12
def EditScript.parse_rcsdiff(input)
  ses = EditScript.new
  l = 1
  scan_rcsdiff(input) {|mark, beg, len, lines|
    if mark == :del
      ses.common beg - l if l < beg
      ses.del len
      l = beg + len
    else
      ses.add lines
    end
  }
  return ses
end
scan_rcsdiff(input) { |:del, beg, len, nil| ... } click to toggle source
# File lib/docdiff/diff/rcsdiff.rb, line 27
def EditScript.scan_rcsdiff(input)
  state = :command
  beg = len = nil
  adds = nil
  input.each_line("\n") {|line|
    case state
    when :command
      case line
      when /\Aa(\d+)\s+(\d+)/
        beg = $1.to_i
        len = $2.to_i
        adds = []
        state = :add
      when /\Ad(\d+)\s+(\d+)/
        beg = $1.to_i
        len = $2.to_i
        yield :del, beg, len, nil
        state = :command
      else
        raise InvalidRCSDiffFormat.new(line)
      end
    when :add
      adds << line
      if adds.length == len
        yield :add, beg, len, adds
        adds = nil
        state = :command
      end
    else
      raise StandardError.new("unknown state")
    end
  }
end

Public Instance Methods

add(seq_or_len) click to toggle source
# File lib/docdiff/diff/editscript.rb, line 53
def add(seq_or_len)
  unless @chunk_add
    @chunk_add = []
    @chunk_del = []
    @chunk_common = nil
    @list << @chunk_del
    @list << @chunk_add
  end
  if Array === seq_or_len
    len = seq_or_len.length
    mark = :add_elt
  else
    len = seq_or_len
    mark = :add_num
  end
  if !@chunk_add.empty? && @chunk_add.last[0] == mark
    @chunk_add.last[2] += seq_or_len
  else
    @chunk_add << [mark, nil, seq_or_len]
  end
  @count_b += len
  @additions += len
end
apply(src) click to toggle source
# File lib/docdiff/diff/editscript.rb, line 118
def apply(src)
  l = 0
  dst = []
  each {|mark, del, add|
    case mark
    when :add_elt
      dst.concat add
    when :add_num
      raise ArgumentError.new("additionnal lines are not known.")
    when :common_elt_elt
      dst.concat add
      l += del.length
    when :common_elt_num
      dst.concat src[l, del]
      l += del
    when :common_num_elt
      dst.concat add
      l += add
    when :common_num_num
      dst.concat src[l, del]
      l += del
    when :del_elt
      l += del.length
    when :del_num
      l += del
    end
  }
  dst.concat src[l..-1]
  return dst
end
common(seq_or_len_a, seq_or_len_b=seq_or_len_a) click to toggle source
# File lib/docdiff/diff/editscript.rb, line 77
def common(seq_or_len_a, seq_or_len_b=seq_or_len_a)
  unless @chunk_common
    @list.pop
    @list.pop
    @list << @chunk_del unless @chunk_del.empty?
    @list << @chunk_add unless @chunk_add.empty?
    @chunk_add = nil
    @chunk_del = nil
    @chunk_common = []
    @list << @chunk_common
  end

  len_a = Array === seq_or_len_a ? seq_or_len_a.length : seq_or_len_a
  len_b = Array === seq_or_len_b ? seq_or_len_b.length : seq_or_len_b
  raise ArgumentError.new("length not equal: #{len_a} != #{len_b}") if len_a != len_b
  len = len_a

  mark = ((Array === seq_or_len_a) ?
          (Array === seq_or_len_b ? :common_elt_elt : :common_elt_num) :
          (Array === seq_or_len_b ? :common_num_elt : :common_num_num))

  if !@chunk_common.empty? && @chunk_common.last[0] == mark
    @chunk_common.last[1] += seq_or_len_a
    @chunk_common.last[2] += seq_or_len_b
  else
    @chunk_common << [mark, seq_or_len_a, seq_or_len_b]
  end

  @cs.add @count_a, @count_b, len
  @count_a += len
  @count_b += len
end
commonsubsequence() click to toggle source
# File lib/docdiff/diff/editscript.rb, line 25
def commonsubsequence
  return @cs
end
del(seq_or_len) click to toggle source
# File lib/docdiff/diff/editscript.rb, line 29
def del(seq_or_len)
  unless @chunk_del
    @chunk_add = []
    @chunk_del = []
    @chunk_common = nil
    @list << @chunk_del
    @list << @chunk_add
  end
  if Array === seq_or_len
    len = seq_or_len.length
    mark = :del_elt
  else
    len = seq_or_len
    mark = :del_num
  end
  if !@chunk_del.empty? && @chunk_del.last[0] == mark
    @chunk_del.last[1] += seq_or_len
  else
    @chunk_del << [mark, seq_or_len, nil]
  end
  @count_a += len
  @deletions += len
end
each() { |mark_del_add| ... } click to toggle source
# File lib/docdiff/diff/editscript.rb, line 110
def each
  @list.each {|chunk|
    chunk.each {|mark_del_add|
      yield mark_del_add
    }
  }
end
rcsdiff(out='') click to toggle source
# File lib/docdiff/diff/rcsdiff.rb, line 61
def rcsdiff(out='')
  state = :lines
  l = 1
  each {|mark, del, add|
    case mark
    when :add_elt
      out << "a#{l - 1} #{add.length}\n"
      add.each {|line|
        case state
        when :lines
          case line
          when /\A.*\n\z/
          when /\A.*\z/
            state = :after_last_line
          else
            raise ArgumentError.new("additional element is not line")
          end
        when :after_last_line
          raise ArgumentError.new("additional elements after last incomplete line")
        end
        out << line
      }
    when :add_num
      raise ArgumentError.new("additionnal lines are not known.")
    when :common_elt_elt
      l += del.length
    when :common_elt_num
      l += add
    when :common_num_elt
      l += del
    when :common_num_num
      l += del
    when :del_elt
      del = del.length
      out << "d#{l} #{del}\n"
      l += del
    when :del_num
      out << "d#{l} #{del}\n"
      l += del
    end
  }
  return out
end
unidiff(out='', context_lines=3) click to toggle source
# File lib/docdiff/diff/unidiff.rb, line 22
def unidiff(out='', context_lines=3)
  state = :common
  l1 = l2 = 1
  hunk = []
  hunk_l1 = hunk_l2 = 1
  hunk_tail = 0
  each {|mark, del, add|
    case mark
    when :add_elt
      unless hunk
        hunk = []
        hunk_l1 = l1
        hunk_l2 = l2
      end

      add.each {|line| hunk << '+' + line}
      hunk[-1] += "\n\\ No newline at end of file\n" if /\n\z/ !~ hunk[-1]
      l2 += add.length
      hunk_tail = 0
    when :add_num
      raise ArgumentError.new("additionnal lines are not known.")
    when :common_elt_elt
      if hunk
        if hunk_tail + add.length <= context_lines * 2
          add.each {|line| hunk << ' ' + line}
          hunk[-1] += "\n\\ No newline at end of file\n" if /\n\z/ !~ hunk[-1]
          l1 += add.length
          l2 += add.length
          hunk_tail += add.length
        else
          i = 0
          if hunk_tail != hunk.length
            while hunk_tail < context_lines
              hunk << ' ' + add[i]
              l1 += 1
              l2 += 1
              hunk_tail += 1
              i += 1
            end
            hunk[-1] += "\n\\ No newline at end of file\n" if /\n\z/ !~ hunk[-1]

            out << unidiff_hunk_header(hunk_l1, l1 - hunk_l1, hunk_l2, l2 - hunk_l1)
            h = hunk.length - (hunk_tail - context_lines)
            (0...h).each {|j| out << hunk[j]}
            hunk[0, h] = []
          end

          l1 += add.length - i
          l2 += add.length - i

          hunk_l1 = l1 - context_lines
          hunk_l2 = l2 - context_lines
          hunk = add[-context_lines..-1].collect {|line| ' ' + line}
          hunk[-1] += "\n\\ No newline at end of file\n" if /\n\z/ !~ hunk[-1]
          hunk_tail = context_lines
        end
      else
        hunk_l1 = l1
        hunk_l2 = l2
        l1 += add.length
        l2 += add.length
        if context_lines <= add.length
          hunk = add[-context_lines..-1].collect {|line| ' ' + line}
        else
          hunk = add.collect {|line| ' ' + line}
        end
        hunk[-1] += "\n\\ No newline at end of file\n" if /\n\z/ !~ hunk[-1]
        hunk_tail = hunk.length
      end
    when :common_elt_num
      raise ArgumentError.new("deleted lines are not known.")
    when :common_num_elt
      raise ArgumentError.new("additional lines are not known.")
    when :common_num_num
      raise ArgumentError.new("deleted and additional lines are not known.")
    when :del_elt
      if hunk_tail == hunk.length && context_lines < hunk_tail
        i = hunk_tail - context_lines
        hunk[0, i] = []
        hunk_l1 += i
        hunk_l2 += i
      end
      del.each {|line| hunk << '-' + line}
      hunk[-1] += "\n\\ No newline at end of file\n" if /\n\z/ !~ hunk[-1]
      l1 += del.length
      hunk_tail = 0
    when :del_num
      raise ArgumentError.new("deleted lines are not known.")
    end
  }
  if hunk_tail != hunk.length
    if context_lines < hunk_tail
      i = hunk_tail - context_lines
      hunk[-i..-1] = []
      l1 -= i
      l2 -= i
    end
    out << unidiff_hunk_header(hunk_l1, l1 - hunk_l1, hunk_l2, l2 - hunk_l1)
    hunk.each {|line| out << line}
  end
  return out
end
unidiff_hunk_header(l1, ll1, l2, ll2) click to toggle source
# File lib/docdiff/diff/unidiff.rb, line 12
def unidiff_hunk_header(l1, ll1, l2, ll2)
  l1 = 0 if ll1 == 0
  l2 = 0 if ll2 == 0
  result = "@@ -#{l1}"
  result << ",#{ll1}" if ll1 != 1
  result << " +#{l2}"
  result << ",#{ll2}" if ll2 != 1
  result << " @@\n"
end