16fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org#!/usr/bin/env python
26fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##  Copyright (c) 2012 The WebM project authors. All Rights Reserved.
36fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##
46fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##  Use of this source code is governed by a BSD-style license
56fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##  that can be found in the LICENSE file in the root of the source
66fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##  tree. An additional intellectual property rights grant can be found
76fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##  in the file PATENTS.  All contributing project authors may
86fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##  be found in the AUTHORS file in the root of the source tree.
96fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org##
106fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org"""Classes for representing diff pieces."""
116fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
126fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org__author__ = "jkoleszar@google.com"
136fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
146fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.orgimport re
156fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
166fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
176fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.orgclass DiffLines(object):
186fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    """A container for one half of a diff."""
196fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
206fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def __init__(self, filename, offset, length):
216fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.filename = filename
226fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.offset = offset
236fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.length = length
246fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.lines = []
256fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.delta_line_nums = []
266fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
276fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def Append(self, line):
286fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        l = len(self.lines)
296fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        if line[0] != " ":
306fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            self.delta_line_nums.append(self.offset + l)
316fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.lines.append(line[1:])
326fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        assert l+1 <= self.length
336fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
346fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def Complete(self):
356fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        return len(self.lines) == self.length
366fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
376fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def __contains__(self, item):
386fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        return item >= self.offset and item <= self.offset + self.length - 1
396fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
406fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
416fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.orgclass DiffHunk(object):
426fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    """A container for one diff hunk, consisting of two DiffLines."""
436fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
446fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def __init__(self, header, file_a, file_b, start_a, len_a, start_b, len_b):
456fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.header = header
466fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.left = DiffLines(file_a, start_a, len_a)
476fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.right = DiffLines(file_b, start_b, len_b)
486fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.lines = []
496fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
506fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def Append(self, line):
516fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        """Adds a line to the DiffHunk and its DiffLines children."""
526fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        if line[0] == "-":
536fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            self.left.Append(line)
546fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        elif line[0] == "+":
556fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            self.right.Append(line)
566fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        elif line[0] == " ":
576fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            self.left.Append(line)
586fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            self.right.Append(line)
5976e516e2154f353aa02c504bac88afb0f95fefa7johannkoenig@chromium.org        elif line[0] == "\\":
6076e516e2154f353aa02c504bac88afb0f95fefa7johannkoenig@chromium.org            # Ignore newline messages from git diff.
6176e516e2154f353aa02c504bac88afb0f95fefa7johannkoenig@chromium.org            pass
626fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        else:
636fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            assert False, ("Unrecognized character at start of diff line "
646fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                           "%r" % line[0])
656fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        self.lines.append(line)
666fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
676fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def Complete(self):
686fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        return self.left.Complete() and self.right.Complete()
696fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
706fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    def __repr__(self):
716fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        return "DiffHunk(%s, %s, len %d)" % (
726fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            self.left.filename, self.right.filename,
736fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            max(self.left.length, self.right.length))
746fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
756fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
766fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.orgdef ParseDiffHunks(stream):
776fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    """Walk a file-like object, yielding DiffHunks as they're parsed."""
786fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
796fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    file_regex = re.compile(r"(\+\+\+|---) (\S+)")
806fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    range_regex = re.compile(r"@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?")
816fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    hunk = None
826fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    while True:
836fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        line = stream.readline()
846fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        if not line:
856fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            break
866fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
876fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        if hunk is None:
886fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            # Parse file names
896fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            diff_file = file_regex.match(line)
906fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            if diff_file:
916fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org              if line.startswith("---"):
926fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                  a_line = line
936fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                  a = diff_file.group(2)
946fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                  continue
956fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org              if line.startswith("+++"):
966fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                  b_line = line
976fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                  b = diff_file.group(2)
986fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                  continue
996fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
1006fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            # Parse offset/lengths
1016fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            diffrange = range_regex.match(line)
1026fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            if diffrange:
1036fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                if diffrange.group(2):
1046fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    start_a = int(diffrange.group(1))
1056fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    len_a = int(diffrange.group(3))
1066fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                else:
1076fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    start_a = 1
1086fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    len_a = int(diffrange.group(1))
1096fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
1106fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                if diffrange.group(5):
1116fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    start_b = int(diffrange.group(4))
1126fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    len_b = int(diffrange.group(6))
1136fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                else:
1146fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    start_b = 1
1156fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                    len_b = int(diffrange.group(4))
1166fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
1176fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                header = [a_line, b_line, line]
1186fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                hunk = DiffHunk(header, a, b, start_a, len_a, start_b, len_b)
1196fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org        else:
1206fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            # Add the current line to the hunk
1216fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            hunk.Append(line)
1226fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
1236fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            # See if the whole hunk has been parsed. If so, yield it and prepare
1246fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            # for the next hunk.
1256fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org            if hunk.Complete():
1266fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                yield hunk
1276fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org                hunk = None
1286fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org
1296fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    # Partial hunks are a parse error
1306fefe538d859300e7febe78271828198c10f1b52fgalligan@chromium.org    assert hunk is None
131