~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
133
    def _writeline(self, line):
134
        item = self.lp.parse_line(line)
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
135
        bad_ws_match = None
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
136
        if isinstance(item, Hunk):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
137
            line_class = 'diffstuff'
497 by Aaron Bentley
Add optional style checks to colordiff
138
            self._analyse_old_new()
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
139
        elif isinstance(item, HunkLine):
140
            bad_ws_match = re.match(r'^([\t]*)(.*?)([\t ]*)(\r?\n)$',
141
                                    item.contents)
142
            has_leading_tabs = bool(bad_ws_match.group(1))
143
            has_trailing_whitespace = bool(bad_ws_match.group(3))
144
            if isinstance(item, InsertLine):
145
                if has_leading_tabs:
146
                    self.added_leading_tabs += 1
147
                if has_trailing_whitespace:
148
                    self.added_trailing_whitespace += 1
149
                if (len(bad_ws_match.group(2)) > self.max_line_len and
150
                    not item.contents.startswith('++ ')):
151
                    self.long_lines += 1
152
                line_class = 'newtext'
153
                self._new_lines.append(item)
154
            elif isinstance(item, RemoveLine):
155
                line_class = 'oldtext'
156
                self._old_lines.append(item)
157
            else:
158
                line_class = 'plain'
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
159
        elif isinstance(item, basestring) and item.startswith('==='):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
160
            line_class = 'metaline'
497 by Aaron Bentley
Add optional style checks to colordiff
161
            self._analyse_old_new()
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
162
        else:
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
163
            line_class = 'plain'
497 by Aaron Bentley
Add optional style checks to colordiff
164
            self._analyse_old_new()
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
165
        self.colorstring(line_class, item, bad_ws_match)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
166
167
    def flush(self):
168
        self.target.flush()
169
497 by Aaron Bentley
Add optional style checks to colordiff
170
    @staticmethod
171
    def _matched_lines(old, new):
172
        matcher = patiencediff.PatienceSequenceMatcher(None, old, new)
173
        matched_lines = sum (n for i, j, n in matcher.get_matching_blocks())
174
        return matched_lines
175
176
    def _analyse_old_new(self):
177
        if (self._old_lines, self._new_lines) == ([], []):
178
            return
179
        if not self.check_style:
180
            return
503 by Aaron Bentley
Update from review comments
181
        old = [l.contents for l in self._old_lines]
182
        new = [l.contents for l in self._new_lines]
183
        ws_matched = self._matched_lines(old, new)
184
        old = [l.rstrip() for l in old]
185
        new = [l.rstrip() for l in new]
497 by Aaron Bentley
Add optional style checks to colordiff
186
        no_ws_matched = self._matched_lines(old, new)
187
        assert no_ws_matched >= ws_matched
188
        if no_ws_matched > ws_matched:
189
            self.spurious_whitespace += no_ws_matched - ws_matched
190
            self.target.write('^ Spurious whitespace change above.\n')
191
        self._old_lines, self._new_lines = ([], [])
192
193
685 by Aaron Bentley
Fix has_ansi_colors
194
def auto_diff_writer(output):
195
    return DiffWriter(output, color='auto')
196
197
680.1.3 by Benoît Pierre
Add --color=MODE option to cdiff; MODE can be:
198
def colordiff(color, check_style, *args, **kwargs):
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
199
    real_stdout = sys.stdout
684 by Aaron Bentley
Fix cdiff color switch by deferring to DiffWriter
200
    dw = DiffWriter(real_stdout, check_style, color)
497 by Aaron Bentley
Add optional style checks to colordiff
201
    sys.stdout = dw
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
202
    try:
408.1.1 by Adeodato Simó
Get command objects via get_cmd_object(), instead of directly importing
203
        get_cmd_object('diff').run(*args, **kwargs)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
204
    finally:
205
        sys.stdout = real_stdout
497 by Aaron Bentley
Add optional style checks to colordiff
206
    if check_style:
501.1.3 by Marius Kruger
* do trainling white space detection in one place.
207
        if dw.added_leading_tabs > 0:
208
            trace.warning('%d new line(s) have leading tabs.' %
209
                          dw.added_leading_tabs)
497 by Aaron Bentley
Add optional style checks to colordiff
210
        if dw.added_trailing_whitespace > 0:
503 by Aaron Bentley
Update from review comments
211
            trace.warning('%d new line(s) have trailing whitespace.' %
497 by Aaron Bentley
Add optional style checks to colordiff
212
                          dw.added_trailing_whitespace)
506 by Aaron Bentley
Colordiff warns on long lines
213
        if dw.long_lines > 0:
214
            trace.warning('%d new line(s) exceed(s) %d columns.' %
215
                          (dw.long_lines, dw.max_line_len))
497 by Aaron Bentley
Add optional style checks to colordiff
216
        if dw.spurious_whitespace > 0:
503 by Aaron Bentley
Update from review comments
217
            trace.warning('%d line(s) have spurious whitespace changes' %
497 by Aaron Bentley
Add optional style checks to colordiff
218
                          dw.spurious_whitespace)