~abentley/bzrtools/bzrtools.dev

399 by Aaron Bentley
Implement cdiff (based on old Fai code)
1
# Copyright (C) 2006 Aaron Bentley
612 by Aaron Bentley
Update email address
2
# <aaron@aaronbentley.com>
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
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
501.1.1 by Marius Kruger
Highlight trailing white space
18
import re
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
19
import sys
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
20
from os.path import expanduser
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
21
497 by Aaron Bentley
Add optional style checks to colordiff
22
from bzrlib import patiencediff, trace
408.1.1 by Adeodato Simó
Get command objects via get_cmd_object(), instead of directly importing
23
from bzrlib.commands import get_cmd_object
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
24
from bzrlib.patches import (hunk_from_header, InsertLine, RemoveLine,
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
25
                            ContextLine, Hunk, HunkLine)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
26
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
27
import terminal
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
28
29
class LineParser(object):
30
    def parse_line(self, line):
31
        if line.startswith("@"):
32
            return hunk_from_header(line)
33
        elif line.startswith("+"):
34
            return InsertLine(line[1:])
35
        elif line.startswith("-"):
36
            return RemoveLine(line[1:])
37
        elif line.startswith(" "):
38
            return ContextLine(line[1:])
39
        else:
40
            return line
41
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
42
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
43
class DiffWriter(object):
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
44
684 by Aaron Bentley
Fix cdiff color switch by deferring to DiffWriter
45
    def __init__(self, target, check_style=False, color='always'):
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
46
        self.target = target
47
        self.lp = LineParser()
400 by Aaron Bentley
Don't just assume we get lines
48
        self.chunks = []
685 by Aaron Bentley
Fix has_ansi_colors
49
        from terminal import has_ansi_colors
684 by Aaron Bentley
Fix cdiff color switch by deferring to DiffWriter
50
        if 'always' == color or ('auto' == color and has_ansi_colors()):
51
            self.colors = {
52
                'metaline':      'darkyellow',
53
                'plain':         'darkwhite',
54
                'newtext':       'darkblue',
55
                'oldtext':       'darkred',
56
                'diffstuff':     'darkgreen',
57
                'trailingspace': 'yellow',
58
                'leadingtabs':   'magenta',
59
                'longline':      'cyan',
60
            }
61
            self._read_colordiffrc('/etc/colordiffrc')
62
            self._read_colordiffrc(expanduser('~/.colordiffrc'))
63
        else:
64
            self.colors = {
65
                'metaline':      None,
66
                'plain':         None,
67
                'newtext':       None,
68
                'oldtext':       None,
69
                'diffstuff':     None,
70
                'trailingspace': None,
71
                'leadingtabs':   None,
72
                'longline':      None,
73
            }
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
74
        self.added_leading_tabs = 0
497 by Aaron Bentley
Add optional style checks to colordiff
75
        self.added_trailing_whitespace = 0
76
        self.spurious_whitespace = 0
506 by Aaron Bentley
Colordiff warns on long lines
77
        self.long_lines = 0
531.2.2 by Charlie Shepherd
Remove all trailing whitespace
78
        self.max_line_len = 79
497 by Aaron Bentley
Add optional style checks to colordiff
79
        self._new_lines = []
80
        self._old_lines = []
81
        self.check_style = check_style
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
82
656.1.1 by Christophe Troestler
[Bug #220545] Read '/etc/colordiffrc' before '~/.colordiffrc'.
83
    def _read_colordiffrc(self, path):
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
84
        try:
85
            f = open(path, 'r')
86
        except IOError:
87
            return
88
89
        for line in f.readlines():
90
            try:
91
                key, val = line.split('=')
92
            except ValueError:
93
                continue
94
95
            key = key.strip()
96
            val = val.strip()
97
98
            tmp = val
99
            if val.startswith('dark'):
100
                tmp = val[4:]
101
102
            if tmp not in terminal.colors:
103
                continue
104
105
            self.colors[key] = val
106
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
107
    def colorstring(self, type, item, bad_ws_match):
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
108
        color = self.colors[type]
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
109
        if color is not None:
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
110
            if self.check_style and bad_ws_match:
501.1.4 by Marius Kruger
in colorstring() call terminal.colorstring from one place,
111
                #highlight were needed
112
                item.contents = ''.join(terminal.colorstring(txt, color, bcol)
113
                    for txt, bcol in (
114
                        (bad_ws_match.group(1).expandtabs(),
115
                             self.colors['leadingtabs']),
116
                        (bad_ws_match.group(2)[0:self.max_line_len], None),
117
                        (bad_ws_match.group(2)[self.max_line_len:],
118
                             self.colors['longline']),
119
                        (bad_ws_match.group(3), self.colors['trailingspace'])
120
                    )) + bad_ws_match.group(4)
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
121
            string = terminal.colorstring(str(item), color)
122
        else:
123
            string = str(item)
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
124
        self.target.write(string)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
125
126
    def write(self, text):
400 by Aaron Bentley
Don't just assume we get lines
127
        newstuff = text.split('\n')
128
        for newchunk in newstuff[:-1]:
129
            self._writeline(''.join(self.chunks + [newchunk, '\n']))
130
            self.chunks = []
131
        self.chunks = [newstuff[-1]]
132
744.1.1 by Jelmer Vernooij
Add DiffWriter.writelines.
133
    def writelines(self, lines):
134
        for line in lines:
135
            self.write(line)
136
400 by Aaron Bentley
Don't just assume we get lines
137
    def _writeline(self, line):
138
        item = self.lp.parse_line(line)
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
139
        bad_ws_match = None
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
140
        if isinstance(item, Hunk):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
141
            line_class = 'diffstuff'
497 by Aaron Bentley
Add optional style checks to colordiff
142
            self._analyse_old_new()
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
143
        elif isinstance(item, HunkLine):
144
            bad_ws_match = re.match(r'^([\t]*)(.*?)([\t ]*)(\r?\n)$',
145
                                    item.contents)
146
            has_leading_tabs = bool(bad_ws_match.group(1))
147
            has_trailing_whitespace = bool(bad_ws_match.group(3))
148
            if isinstance(item, InsertLine):
149
                if has_leading_tabs:
150
                    self.added_leading_tabs += 1
151
                if has_trailing_whitespace:
152
                    self.added_trailing_whitespace += 1
153
                if (len(bad_ws_match.group(2)) > self.max_line_len and
154
                    not item.contents.startswith('++ ')):
155
                    self.long_lines += 1
156
                line_class = 'newtext'
157
                self._new_lines.append(item)
158
            elif isinstance(item, RemoveLine):
159
                line_class = 'oldtext'
160
                self._old_lines.append(item)
161
            else:
162
                line_class = 'plain'
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
163
        elif isinstance(item, basestring) and item.startswith('==='):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
164
            line_class = 'metaline'
497 by Aaron Bentley
Add optional style checks to colordiff
165
            self._analyse_old_new()
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
166
        else:
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
167
            line_class = 'plain'
497 by Aaron Bentley
Add optional style checks to colordiff
168
            self._analyse_old_new()
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
169
        self.colorstring(line_class, item, bad_ws_match)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
170
171
    def flush(self):
172
        self.target.flush()
173
497 by Aaron Bentley
Add optional style checks to colordiff
174
    @staticmethod
175
    def _matched_lines(old, new):
176
        matcher = patiencediff.PatienceSequenceMatcher(None, old, new)
177
        matched_lines = sum (n for i, j, n in matcher.get_matching_blocks())
178
        return matched_lines
179
180
    def _analyse_old_new(self):
181
        if (self._old_lines, self._new_lines) == ([], []):
182
            return
183
        if not self.check_style:
184
            return
503 by Aaron Bentley
Update from review comments
185
        old = [l.contents for l in self._old_lines]
186
        new = [l.contents for l in self._new_lines]
187
        ws_matched = self._matched_lines(old, new)
188
        old = [l.rstrip() for l in old]
189
        new = [l.rstrip() for l in new]
497 by Aaron Bentley
Add optional style checks to colordiff
190
        no_ws_matched = self._matched_lines(old, new)
191
        assert no_ws_matched >= ws_matched
192
        if no_ws_matched > ws_matched:
193
            self.spurious_whitespace += no_ws_matched - ws_matched
194
            self.target.write('^ Spurious whitespace change above.\n')
195
        self._old_lines, self._new_lines = ([], [])
196
197
685 by Aaron Bentley
Fix has_ansi_colors
198
def auto_diff_writer(output):
199
    return DiffWriter(output, color='auto')
200
201
680.1.3 by Benoît Pierre
Add --color=MODE option to cdiff; MODE can be:
202
def colordiff(color, check_style, *args, **kwargs):
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
203
    real_stdout = sys.stdout
684 by Aaron Bentley
Fix cdiff color switch by deferring to DiffWriter
204
    dw = DiffWriter(real_stdout, check_style, color)
497 by Aaron Bentley
Add optional style checks to colordiff
205
    sys.stdout = dw
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
206
    try:
408.1.1 by Adeodato Simó
Get command objects via get_cmd_object(), instead of directly importing
207
        get_cmd_object('diff').run(*args, **kwargs)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
208
    finally:
209
        sys.stdout = real_stdout
497 by Aaron Bentley
Add optional style checks to colordiff
210
    if check_style:
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
211
        if dw.added_leading_tabs > 0:
212
            trace.warning('%d new line(s) have leading tabs.' %
213
                          dw.added_leading_tabs)
497 by Aaron Bentley
Add optional style checks to colordiff
214
        if dw.added_trailing_whitespace > 0:
503 by Aaron Bentley
Update from review comments
215
            trace.warning('%d new line(s) have trailing whitespace.' %
497 by Aaron Bentley
Add optional style checks to colordiff
216
                          dw.added_trailing_whitespace)
506 by Aaron Bentley
Colordiff warns on long lines
217
        if dw.long_lines > 0:
218
            trace.warning('%d new line(s) exceed(s) %d columns.' %
219
                          (dw.long_lines, dw.max_line_len))
497 by Aaron Bentley
Add optional style checks to colordiff
220
        if dw.spurious_whitespace > 0:
503 by Aaron Bentley
Update from review comments
221
            trace.warning('%d line(s) have spurious whitespace changes' %
497 by Aaron Bentley
Add optional style checks to colordiff
222
                          dw.spurious_whitespace)