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