1233d2500723e5594f3e7c70896ffeeef32b9c950ywan#!/usr/bin/env python
2233d2500723e5594f3e7c70896ffeeef32b9c950ywan##  Copyright (c) 2012 The WebM project authors. All Rights Reserved.
3233d2500723e5594f3e7c70896ffeeef32b9c950ywan##
4233d2500723e5594f3e7c70896ffeeef32b9c950ywan##  Use of this source code is governed by a BSD-style license
5233d2500723e5594f3e7c70896ffeeef32b9c950ywan##  that can be found in the LICENSE file in the root of the source
6233d2500723e5594f3e7c70896ffeeef32b9c950ywan##  tree. An additional intellectual property rights grant can be found
7233d2500723e5594f3e7c70896ffeeef32b9c950ywan##  in the file PATENTS.  All contributing project authors may
8233d2500723e5594f3e7c70896ffeeef32b9c950ywan##  be found in the AUTHORS file in the root of the source tree.
9233d2500723e5594f3e7c70896ffeeef32b9c950ywan##
10233d2500723e5594f3e7c70896ffeeef32b9c950ywan"""Classes for representing diff pieces."""
11233d2500723e5594f3e7c70896ffeeef32b9c950ywan
12233d2500723e5594f3e7c70896ffeeef32b9c950ywan__author__ = "jkoleszar@google.com"
13233d2500723e5594f3e7c70896ffeeef32b9c950ywan
14233d2500723e5594f3e7c70896ffeeef32b9c950ywanimport re
15233d2500723e5594f3e7c70896ffeeef32b9c950ywan
16233d2500723e5594f3e7c70896ffeeef32b9c950ywan
17233d2500723e5594f3e7c70896ffeeef32b9c950ywanclass DiffLines(object):
18233d2500723e5594f3e7c70896ffeeef32b9c950ywan    """A container for one half of a diff."""
19233d2500723e5594f3e7c70896ffeeef32b9c950ywan
20233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def __init__(self, filename, offset, length):
21233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.filename = filename
22233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.offset = offset
23233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.length = length
24233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.lines = []
25233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.delta_line_nums = []
26233d2500723e5594f3e7c70896ffeeef32b9c950ywan
27233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def Append(self, line):
28233d2500723e5594f3e7c70896ffeeef32b9c950ywan        l = len(self.lines)
29233d2500723e5594f3e7c70896ffeeef32b9c950ywan        if line[0] != " ":
30233d2500723e5594f3e7c70896ffeeef32b9c950ywan            self.delta_line_nums.append(self.offset + l)
31233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.lines.append(line[1:])
32233d2500723e5594f3e7c70896ffeeef32b9c950ywan        assert l+1 <= self.length
33233d2500723e5594f3e7c70896ffeeef32b9c950ywan
34233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def Complete(self):
35233d2500723e5594f3e7c70896ffeeef32b9c950ywan        return len(self.lines) == self.length
36233d2500723e5594f3e7c70896ffeeef32b9c950ywan
37233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def __contains__(self, item):
38233d2500723e5594f3e7c70896ffeeef32b9c950ywan        return item >= self.offset and item <= self.offset + self.length - 1
39233d2500723e5594f3e7c70896ffeeef32b9c950ywan
40233d2500723e5594f3e7c70896ffeeef32b9c950ywan
41233d2500723e5594f3e7c70896ffeeef32b9c950ywanclass DiffHunk(object):
42233d2500723e5594f3e7c70896ffeeef32b9c950ywan    """A container for one diff hunk, consisting of two DiffLines."""
43233d2500723e5594f3e7c70896ffeeef32b9c950ywan
44233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def __init__(self, header, file_a, file_b, start_a, len_a, start_b, len_b):
45233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.header = header
46233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.left = DiffLines(file_a, start_a, len_a)
47233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.right = DiffLines(file_b, start_b, len_b)
48233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.lines = []
49233d2500723e5594f3e7c70896ffeeef32b9c950ywan
50233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def Append(self, line):
51233d2500723e5594f3e7c70896ffeeef32b9c950ywan        """Adds a line to the DiffHunk and its DiffLines children."""
52233d2500723e5594f3e7c70896ffeeef32b9c950ywan        if line[0] == "-":
53233d2500723e5594f3e7c70896ffeeef32b9c950ywan            self.left.Append(line)
54233d2500723e5594f3e7c70896ffeeef32b9c950ywan        elif line[0] == "+":
55233d2500723e5594f3e7c70896ffeeef32b9c950ywan            self.right.Append(line)
56233d2500723e5594f3e7c70896ffeeef32b9c950ywan        elif line[0] == " ":
57233d2500723e5594f3e7c70896ffeeef32b9c950ywan            self.left.Append(line)
58233d2500723e5594f3e7c70896ffeeef32b9c950ywan            self.right.Append(line)
59233d2500723e5594f3e7c70896ffeeef32b9c950ywan        elif line[0] == "\\":
60233d2500723e5594f3e7c70896ffeeef32b9c950ywan            # Ignore newline messages from git diff.
61233d2500723e5594f3e7c70896ffeeef32b9c950ywan            pass
62233d2500723e5594f3e7c70896ffeeef32b9c950ywan        else:
63233d2500723e5594f3e7c70896ffeeef32b9c950ywan            assert False, ("Unrecognized character at start of diff line "
64233d2500723e5594f3e7c70896ffeeef32b9c950ywan                           "%r" % line[0])
65233d2500723e5594f3e7c70896ffeeef32b9c950ywan        self.lines.append(line)
66233d2500723e5594f3e7c70896ffeeef32b9c950ywan
67233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def Complete(self):
68233d2500723e5594f3e7c70896ffeeef32b9c950ywan        return self.left.Complete() and self.right.Complete()
69233d2500723e5594f3e7c70896ffeeef32b9c950ywan
70233d2500723e5594f3e7c70896ffeeef32b9c950ywan    def __repr__(self):
71233d2500723e5594f3e7c70896ffeeef32b9c950ywan        return "DiffHunk(%s, %s, len %d)" % (
72233d2500723e5594f3e7c70896ffeeef32b9c950ywan            self.left.filename, self.right.filename,
73233d2500723e5594f3e7c70896ffeeef32b9c950ywan            max(self.left.length, self.right.length))
74233d2500723e5594f3e7c70896ffeeef32b9c950ywan
75233d2500723e5594f3e7c70896ffeeef32b9c950ywan
76233d2500723e5594f3e7c70896ffeeef32b9c950ywandef ParseDiffHunks(stream):
77233d2500723e5594f3e7c70896ffeeef32b9c950ywan    """Walk a file-like object, yielding DiffHunks as they're parsed."""
78233d2500723e5594f3e7c70896ffeeef32b9c950ywan
79233d2500723e5594f3e7c70896ffeeef32b9c950ywan    file_regex = re.compile(r"(\+\+\+|---) (\S+)")
80233d2500723e5594f3e7c70896ffeeef32b9c950ywan    range_regex = re.compile(r"@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?")
81233d2500723e5594f3e7c70896ffeeef32b9c950ywan    hunk = None
82233d2500723e5594f3e7c70896ffeeef32b9c950ywan    while True:
83233d2500723e5594f3e7c70896ffeeef32b9c950ywan        line = stream.readline()
84233d2500723e5594f3e7c70896ffeeef32b9c950ywan        if not line:
85233d2500723e5594f3e7c70896ffeeef32b9c950ywan            break
86233d2500723e5594f3e7c70896ffeeef32b9c950ywan
87233d2500723e5594f3e7c70896ffeeef32b9c950ywan        if hunk is None:
88233d2500723e5594f3e7c70896ffeeef32b9c950ywan            # Parse file names
89233d2500723e5594f3e7c70896ffeeef32b9c950ywan            diff_file = file_regex.match(line)
90233d2500723e5594f3e7c70896ffeeef32b9c950ywan            if diff_file:
91233d2500723e5594f3e7c70896ffeeef32b9c950ywan              if line.startswith("---"):
92233d2500723e5594f3e7c70896ffeeef32b9c950ywan                  a_line = line
93233d2500723e5594f3e7c70896ffeeef32b9c950ywan                  a = diff_file.group(2)
94233d2500723e5594f3e7c70896ffeeef32b9c950ywan                  continue
95233d2500723e5594f3e7c70896ffeeef32b9c950ywan              if line.startswith("+++"):
96233d2500723e5594f3e7c70896ffeeef32b9c950ywan                  b_line = line
97233d2500723e5594f3e7c70896ffeeef32b9c950ywan                  b = diff_file.group(2)
98233d2500723e5594f3e7c70896ffeeef32b9c950ywan                  continue
99233d2500723e5594f3e7c70896ffeeef32b9c950ywan
100233d2500723e5594f3e7c70896ffeeef32b9c950ywan            # Parse offset/lengths
101233d2500723e5594f3e7c70896ffeeef32b9c950ywan            diffrange = range_regex.match(line)
102233d2500723e5594f3e7c70896ffeeef32b9c950ywan            if diffrange:
103233d2500723e5594f3e7c70896ffeeef32b9c950ywan                if diffrange.group(2):
104233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    start_a = int(diffrange.group(1))
105233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    len_a = int(diffrange.group(3))
106233d2500723e5594f3e7c70896ffeeef32b9c950ywan                else:
107233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    start_a = 1
108233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    len_a = int(diffrange.group(1))
109233d2500723e5594f3e7c70896ffeeef32b9c950ywan
110233d2500723e5594f3e7c70896ffeeef32b9c950ywan                if diffrange.group(5):
111233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    start_b = int(diffrange.group(4))
112233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    len_b = int(diffrange.group(6))
113233d2500723e5594f3e7c70896ffeeef32b9c950ywan                else:
114233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    start_b = 1
115233d2500723e5594f3e7c70896ffeeef32b9c950ywan                    len_b = int(diffrange.group(4))
116233d2500723e5594f3e7c70896ffeeef32b9c950ywan
117233d2500723e5594f3e7c70896ffeeef32b9c950ywan                header = [a_line, b_line, line]
118233d2500723e5594f3e7c70896ffeeef32b9c950ywan                hunk = DiffHunk(header, a, b, start_a, len_a, start_b, len_b)
119233d2500723e5594f3e7c70896ffeeef32b9c950ywan        else:
120233d2500723e5594f3e7c70896ffeeef32b9c950ywan            # Add the current line to the hunk
121233d2500723e5594f3e7c70896ffeeef32b9c950ywan            hunk.Append(line)
122233d2500723e5594f3e7c70896ffeeef32b9c950ywan
123233d2500723e5594f3e7c70896ffeeef32b9c950ywan            # See if the whole hunk has been parsed. If so, yield it and prepare
124233d2500723e5594f3e7c70896ffeeef32b9c950ywan            # for the next hunk.
125233d2500723e5594f3e7c70896ffeeef32b9c950ywan            if hunk.Complete():
126233d2500723e5594f3e7c70896ffeeef32b9c950ywan                yield hunk
127233d2500723e5594f3e7c70896ffeeef32b9c950ywan                hunk = None
128233d2500723e5594f3e7c70896ffeeef32b9c950ywan
129233d2500723e5594f3e7c70896ffeeef32b9c950ywan    # Partial hunks are a parse error
130233d2500723e5594f3e7c70896ffeeef32b9c950ywan    assert hunk is None
131