~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
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
506 by Aaron Bentley
Colordiff warns on long lines
59
        self.long_lines = 0
531.2.2 by Charlie Shepherd
Remove all trailing whitespace
60
        self.max_line_len = 79
497 by Aaron Bentley
Add optional style checks to colordiff
61
        self._new_lines = []
62
        self._old_lines = []
63
        self.check_style = check_style
400.1.2 by Michael Ellerman
Parse ~/.colordiffrc for colour information if it's there.
64
65
    def _read_colordiffrc(self):
66
        path = expanduser('~/.colordiffrc')
67
        try:
68
            f = open(path, 'r')
69
        except IOError:
70
            return
71
72
        for line in f.readlines():
73
            try:
74
                key, val = line.split('=')
75
            except ValueError:
76
                continue
77
78
            key = key.strip()
79
            val = val.strip()
80
81
            tmp = val
82
            if val.startswith('dark'):
83
                tmp = val[4:]
84
85
            if tmp not in terminal.colors:
86
                continue
87
88
            self.colors[key] = val
89
90
    def colorstring(self, type, string):
91
        color = self.colors[type]
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
92
        if color is not None:
93
            string = terminal.colorstring(str(string), color)
94
        self.target.write(string)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
95
96
    def write(self, text):
400 by Aaron Bentley
Don't just assume we get lines
97
        newstuff = text.split('\n')
98
        for newchunk in newstuff[:-1]:
99
            self._writeline(''.join(self.chunks + [newchunk, '\n']))
100
            self.chunks = []
101
        self.chunks = [newstuff[-1]]
102
103
    def _writeline(self, line):
104
        item = self.lp.parse_line(line)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
105
        if isinstance(item, Hunk):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
106
            line_class = 'diffstuff'
497 by Aaron Bentley
Add optional style checks to colordiff
107
            self._analyse_old_new()
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
108
        elif isinstance(item, InsertLine):
503 by Aaron Bentley
Update from review comments
109
            if item.contents.endswith(' \n'):
497 by Aaron Bentley
Add optional style checks to colordiff
110
                self.added_trailing_whitespace += 1
539 by Aaron Bentley
fix long-line detection
111
            if (len(item.contents.rstrip('\n')) > self.max_line_len and
521 by Aaron Bentley
colordiff long-line detection skips hunk headers
112
                not item.contents.startswith('++ ')):
506 by Aaron Bentley
Colordiff warns on long lines
113
                self.long_lines += 1
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
114
            line_class = 'newtext'
503 by Aaron Bentley
Update from review comments
115
            self._new_lines.append(item)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
116
        elif isinstance(item, RemoveLine):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
117
            line_class = 'oldtext'
503 by Aaron Bentley
Update from review comments
118
            self._old_lines.append(item)
400.1.3 by Michael Ellerman
Support for colordiff's 'plain', which colours the context text.
119
        elif isinstance(item, basestring) and item.startswith('==='):
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
120
            line_class = 'metaline'
497 by Aaron Bentley
Add optional style checks to colordiff
121
            self._analyse_old_new()
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
122
        else:
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
123
            line_class = 'plain'
497 by Aaron Bentley
Add optional style checks to colordiff
124
            self._analyse_old_new()
406 by Aaron Bentley
Rename modline -> metaline, style tweakage
125
        self.colorstring(line_class, str(item))
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
126
127
    def flush(self):
128
        self.target.flush()
129
497 by Aaron Bentley
Add optional style checks to colordiff
130
    @staticmethod
131
    def _matched_lines(old, new):
132
        matcher = patiencediff.PatienceSequenceMatcher(None, old, new)
133
        matched_lines = sum (n for i, j, n in matcher.get_matching_blocks())
134
        return matched_lines
135
136
    def _analyse_old_new(self):
137
        if (self._old_lines, self._new_lines) == ([], []):
138
            return
139
        if not self.check_style:
140
            return
503 by Aaron Bentley
Update from review comments
141
        old = [l.contents for l in self._old_lines]
142
        new = [l.contents for l in self._new_lines]
143
        ws_matched = self._matched_lines(old, new)
144
        old = [l.rstrip() for l in old]
145
        new = [l.rstrip() for l in new]
497 by Aaron Bentley
Add optional style checks to colordiff
146
        no_ws_matched = self._matched_lines(old, new)
147
        assert no_ws_matched >= ws_matched
148
        if no_ws_matched > ws_matched:
149
            self.spurious_whitespace += no_ws_matched - ws_matched
150
            self.target.write('^ Spurious whitespace change above.\n')
151
        self._old_lines, self._new_lines = ([], [])
152
153
154
def colordiff(check_style, *args, **kwargs):
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
155
    real_stdout = sys.stdout
497 by Aaron Bentley
Add optional style checks to colordiff
156
    dw = DiffWriter(real_stdout, check_style)
157
    sys.stdout = dw
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
158
    try:
408.1.1 by Adeodato Simó
Get command objects via get_cmd_object(), instead of directly importing
159
        get_cmd_object('diff').run(*args, **kwargs)
399 by Aaron Bentley
Implement cdiff (based on old Fai code)
160
    finally:
161
        sys.stdout = real_stdout
497 by Aaron Bentley
Add optional style checks to colordiff
162
    if check_style:
163
        if dw.added_trailing_whitespace > 0:
503 by Aaron Bentley
Update from review comments
164
            trace.warning('%d new line(s) have trailing whitespace.' %
497 by Aaron Bentley
Add optional style checks to colordiff
165
                          dw.added_trailing_whitespace)
506 by Aaron Bentley
Colordiff warns on long lines
166
        if dw.long_lines > 0:
167
            trace.warning('%d new line(s) exceed(s) %d columns.' %
168
                          (dw.long_lines, dw.max_line_len))
497 by Aaron Bentley
Add optional style checks to colordiff
169
        if dw.spurious_whitespace > 0:
503 by Aaron Bentley
Update from review comments
170
            trace.warning('%d line(s) have spurious whitespace changes' %
497 by Aaron Bentley
Add optional style checks to colordiff
171
                          dw.spurious_whitespace)