~abentley/bzrtools/bzrtools.dev

399 by Aaron Bentley
Implement cdiff (based on old Fai code)
1
# Copyright (C) 2006 Aaron Bentley
2
# <aaron.bentley@utoronto.ca>
3
#
4
#    This program is free software; you can redistribute it and/or modify
5
#    it under the terms of the GNU General Public License as published by
6
#    the Free Software Foundation; either version 2 of the License, or
7
#    (at your option) any later version.
8
#
9
#    This program is distributed in the hope that it will be useful,
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
#    GNU General Public License for more details.
13
#
14
#    You should have received a copy of the GNU General Public License
15
#    along with this program; if not, write to the Free Software
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
18
import sys
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
19
from os.path import expanduser
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
20
497 by Aaron Bentley
Add optional style checks to colordiff
21
from bzrlib import patiencediff, trace
408.1.1 by Adeodato Simó
Get command objects via get_cmd_object(), instead of directly importing
22
from bzrlib.commands import get_cmd_object
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
23
from bzrlib.patches import (hunk_from_header, InsertLine, RemoveLine,
24
                            ContextLine, Hunk)
25
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
26
import terminal
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
27
28
class LineParser(object):
29
    def parse_line(self, line):
30
        if line.startswith("@"):
31
            return hunk_from_header(line)
32
        elif line.startswith("+"):
33
            return InsertLine(line[1:])
34
        elif line.startswith("-"):
35
            return RemoveLine(line[1:])
36
        elif line.startswith(" "):
37
            return ContextLine(line[1:])
38
        else:
39
            return line
40
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
41
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
42
class DiffWriter(object):
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
43
44
    colors = {
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
45
        'metaline':    'darkyellow',
412 by Aaron Bentley
Make Ellerman's cdiff colours the default
46
        'plain':       'darkwhite',
47
        'newtext':     'darkblue',
48
        'oldtext':     'darkred',
49
        'diffstuff':   'darkgreen'
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
50
    }
51
497 by Aaron Bentley
Add optional style checks to colordiff
52
    def __init__(self, target, check_style):
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
53
        self.target = target
54
        self.lp = LineParser()
400 by Aaron Bentley
Don't just assume we get lines
55
        self.chunks = []
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
56
        self._read_colordiffrc()
497 by Aaron Bentley
Add optional style checks to colordiff
57
        self.added_trailing_whitespace = 0
58
        self.spurious_whitespace = 0
59
        self._new_lines = []
60
        self._old_lines = []
61
        self.check_style = check_style
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
62
63
    def _read_colordiffrc(self):
64
        path = expanduser('~/.colordiffrc')
65
        try:
66
            f = open(path, 'r')
67
        except IOError:
68
            return
69
70
        for line in f.readlines():
71
            try:
72
                key, val = line.split('=')
73
            except ValueError:
74
                continue
75
76
            key = key.strip()
77
            val = val.strip()
78
79
            tmp = val
80
            if val.startswith('dark'):
81
                tmp = val[4:]
82
83
            if tmp not in terminal.colors:
84
                continue
85
86
            self.colors[key] = val
87
88
    def colorstring(self, type, string):
89
        color = self.colors[type]
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
90
        if color is not None:
91
            string = terminal.colorstring(str(string), color)
92
        self.target.write(string)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
93
94
    def write(self, text):
400 by Aaron Bentley
Don't just assume we get lines
95
        newstuff = text.split('\n')
96
        for newchunk in newstuff[:-1]:
97
            self._writeline(''.join(self.chunks + [newchunk, '\n']))
98
            self.chunks = []
99
        self.chunks = [newstuff[-1]]
100
101
    def _writeline(self, line):
102
        item = self.lp.parse_line(line)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
103
        if isinstance(item, Hunk):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
104
            line_class = 'diffstuff'
497 by Aaron Bentley
Add optional style checks to colordiff
105
            self._analyse_old_new()
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
106
        elif isinstance(item, InsertLine):
497 by Aaron Bentley
Add optional style checks to colordiff
107
            if str(line).rstrip('\n').endswith(' '):
108
                self.added_trailing_whitespace += 1
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
109
            line_class = 'newtext'
497 by Aaron Bentley
Add optional style checks to colordiff
110
            self._new_lines.append(line)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
111
        elif isinstance(item, RemoveLine):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
112
            line_class = 'oldtext'
497 by Aaron Bentley
Add optional style checks to colordiff
113
            self._old_lines.append(line)
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
114
        elif isinstance(item, basestring) and item.startswith('==='):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
115
            line_class = 'metaline'
497 by Aaron Bentley
Add optional style checks to colordiff
116
            self._analyse_old_new()
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
117
        else:
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
118
            line_class = 'plain'
497 by Aaron Bentley
Add optional style checks to colordiff
119
            self._analyse_old_new()
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
120
        self.colorstring(line_class, str(item))
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
121
122
    def flush(self):
123
        self.target.flush()
124
497 by Aaron Bentley
Add optional style checks to colordiff
125
    @staticmethod
126
    def _matched_lines(old, new):
127
        matcher = patiencediff.PatienceSequenceMatcher(None, old, new)
128
        matched_lines = sum (n for i, j, n in matcher.get_matching_blocks())
129
        return matched_lines
130
131
    def _analyse_old_new(self):
132
        if (self._old_lines, self._new_lines) == ([], []):
133
            return
134
        if not self.check_style:
135
            return
136
        old = [l[1:] for l in self._old_lines]
137
        new = [l[1:] for l in self._new_lines]
138
        ws_matched = self._matched_lines(self._old_lines, self._new_lines)
139
        old = [l[1:].rstrip() for l in self._old_lines]
140
        new = [l[1:].rstrip() for l in self._new_lines]
141
        no_ws_matched = self._matched_lines(old, new)
142
        assert no_ws_matched >= ws_matched
143
        if no_ws_matched > ws_matched:
144
            self.spurious_whitespace += no_ws_matched - ws_matched
145
            self.target.write('^ Spurious whitespace change above.\n')
146
        self._old_lines, self._new_lines = ([], [])
147
148
149
def colordiff(check_style, *args, **kwargs):
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
150
    real_stdout = sys.stdout
497 by Aaron Bentley
Add optional style checks to colordiff
151
    dw = DiffWriter(real_stdout, check_style)
152
    sys.stdout = dw
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
153
    try:
408.1.1 by Adeodato Simó
Get command objects via get_cmd_object(), instead of directly importing
154
        get_cmd_object('diff').run(*args, **kwargs)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
155
    finally:
156
        sys.stdout = real_stdout
497 by Aaron Bentley
Add optional style checks to colordiff
157
    if check_style:
158
        if dw.added_trailing_whitespace > 0:
499 by Aaron Bentley
Tweak colordiff/shelf changes
159
            trace.warning('%d new line(s) has trailing whitespace.' %
497 by Aaron Bentley
Add optional style checks to colordiff
160
                          dw.added_trailing_whitespace)
161
        if dw.spurious_whitespace > 0:
499 by Aaron Bentley
Tweak colordiff/shelf changes
162
            trace.warning('%d line(s) has spurious whitespace changes' %
497 by Aaron Bentley
Add optional style checks to colordiff
163
                          dw.spurious_whitespace)
164