/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/patiencediff.py

  • Committer: Martin
  • Date: 2018-11-16 19:10:17 UTC
  • mto: This revision was merged to the branch mainline in revision 7177.
  • Revision ID: gzlist@googlemail.com-20181116191017-kyedz1qck0ovon3h
Remove lazy_regexp reset in bt.test_source

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
17
 
 
18
from __future__ import absolute_import
 
19
 
 
20
from .lazy_import import lazy_import
 
21
lazy_import(globals(), """
 
22
import os
 
23
import sys
 
24
import time
 
25
import difflib
 
26
""")
 
27
 
 
28
 
 
29
__all__ = ['PatienceSequenceMatcher', 'unified_diff', 'unified_diff_bytes',
 
30
           'unified_diff_files']
 
31
 
 
32
 
 
33
# This is a version of unified_diff which only adds a factory parameter
 
34
# so that you can override the default SequenceMatcher
 
35
# this has been submitted as a patch to python
 
36
def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
 
37
                 tofiledate='', n=3, lineterm='\n',
 
38
                 sequencematcher=None):
 
39
    r"""
 
40
    Compare two sequences of lines; generate the delta as a unified diff.
 
41
 
 
42
    Unified diffs are a compact way of showing line changes and a few
 
43
    lines of context.  The number of context lines is set by 'n' which
 
44
    defaults to three.
 
45
 
 
46
    By default, the diff control lines (those with ---, +++, or @@) are
 
47
    created with a trailing newline.  This is helpful so that inputs
 
48
    created from file.readlines() result in diffs that are suitable for
 
49
    file.writelines() since both the inputs and outputs have trailing
 
50
    newlines.
 
51
 
 
52
    For inputs that do not have trailing newlines, set the lineterm
 
53
    argument to "" so that the output will be uniformly newline free.
 
54
 
 
55
    The unidiff format normally has a header for filenames and modification
 
56
    times.  Any or all of these may be specified using strings for
 
57
    'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.  The modification
 
58
    times are normally expressed in the format returned by time.ctime().
 
59
 
 
60
    Example:
 
61
 
 
62
    >>> for line in unified_diff('one two three four'.split(),
 
63
    ...             'zero one tree four'.split(), 'Original', 'Current',
 
64
    ...             'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:20:52 2003',
 
65
    ...             lineterm=''):
 
66
    ...     print line
 
67
    --- Original Sat Jan 26 23:30:50 1991
 
68
    +++ Current Fri Jun 06 10:20:52 2003
 
69
    @@ -1,4 +1,4 @@
 
70
    +zero
 
71
     one
 
72
    -two
 
73
    -three
 
74
    +tree
 
75
     four
 
76
    """
 
77
    if sequencematcher is None:
 
78
        sequencematcher = difflib.SequenceMatcher
 
79
 
 
80
    if fromfiledate:
 
81
        fromfiledate = '\t' + str(fromfiledate)
 
82
    if tofiledate:
 
83
        tofiledate = '\t' + str(tofiledate)
 
84
 
 
85
    started = False
 
86
    for group in sequencematcher(None, a, b).get_grouped_opcodes(n):
 
87
        if not started:
 
88
            yield '--- %s%s%s' % (fromfile, fromfiledate, lineterm)
 
89
            yield '+++ %s%s%s' % (tofile, tofiledate, lineterm)
 
90
            started = True
 
91
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
 
92
        yield "@@ -%d,%d +%d,%d @@%s" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
 
93
        for tag, i1, i2, j1, j2 in group:
 
94
            if tag == 'equal':
 
95
                for line in a[i1:i2]:
 
96
                    yield ' ' + line
 
97
                continue
 
98
            if tag == 'replace' or tag == 'delete':
 
99
                for line in a[i1:i2]:
 
100
                    yield '-' + line
 
101
            if tag == 'replace' or tag == 'insert':
 
102
                for line in b[j1:j2]:
 
103
                    yield '+' + line
 
104
 
 
105
 
 
106
def unified_diff_bytes(a, b, fromfile=b'', tofile=b'', fromfiledate=b'',
 
107
                       tofiledate=b'', n=3, lineterm=b'\n', sequencematcher=None):
 
108
    r"""
 
109
    Compare two sequences of lines; generate the delta as a unified diff.
 
110
 
 
111
    Unified diffs are a compact way of showing line changes and a few
 
112
    lines of context.  The number of context lines is set by 'n' which
 
113
    defaults to three.
 
114
 
 
115
    By default, the diff control lines (those with ---, +++, or @@) are
 
116
    created with a trailing newline.  This is helpful so that inputs
 
117
    created from file.readlines() result in diffs that are suitable for
 
118
    file.writelines() since both the inputs and outputs have trailing
 
119
    newlines.
 
120
 
 
121
    For inputs that do not have trailing newlines, set the lineterm
 
122
    argument to "" so that the output will be uniformly newline free.
 
123
 
 
124
    The unidiff format normally has a header for filenames and modification
 
125
    times.  Any or all of these may be specified using strings for
 
126
    'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.  The modification
 
127
    times are normally expressed in the format returned by time.ctime().
 
128
 
 
129
    Example:
 
130
 
 
131
    >>> for line in bytes_unified_diff(b'one two three four'.split(),
 
132
    ...             b'zero one tree four'.split(), b'Original', b'Current',
 
133
    ...             b'Sat Jan 26 23:30:50 1991', b'Fri Jun 06 10:20:52 2003',
 
134
    ...             lineterm=b''):
 
135
    ...     print line
 
136
    --- Original Sat Jan 26 23:30:50 1991
 
137
    +++ Current Fri Jun 06 10:20:52 2003
 
138
    @@ -1,4 +1,4 @@
 
139
    +zero
 
140
     one
 
141
    -two
 
142
    -three
 
143
    +tree
 
144
     four
 
145
    """
 
146
    if sequencematcher is None:
 
147
        sequencematcher = difflib.SequenceMatcher
 
148
 
 
149
    if fromfiledate:
 
150
        fromfiledate = b'\t' + bytes(fromfiledate)
 
151
    if tofiledate:
 
152
        tofiledate = b'\t' + bytes(tofiledate)
 
153
 
 
154
    started = False
 
155
    for group in sequencematcher(None, a, b).get_grouped_opcodes(n):
 
156
        if not started:
 
157
            yield b'--- %s%s%s' % (fromfile, fromfiledate, lineterm)
 
158
            yield b'+++ %s%s%s' % (tofile, tofiledate, lineterm)
 
159
            started = True
 
160
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
 
161
        yield b"@@ -%d,%d +%d,%d @@%s" % (i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm)
 
162
        for tag, i1, i2, j1, j2 in group:
 
163
            if tag == 'equal':
 
164
                for line in a[i1:i2]:
 
165
                    yield b' ' + line
 
166
                continue
 
167
            if tag == 'replace' or tag == 'delete':
 
168
                for line in a[i1:i2]:
 
169
                    yield b'-' + line
 
170
            if tag == 'replace' or tag == 'insert':
 
171
                for line in b[j1:j2]:
 
172
                    yield b'+' + line
 
173
 
 
174
 
 
175
def unified_diff_files(a, b, sequencematcher=None):
 
176
    """Generate the diff for two files.
 
177
    """
 
178
    # Should this actually be an error?
 
179
    if a == b:
 
180
        return []
 
181
    if a == '-':
 
182
        file_a = sys.stdin
 
183
        time_a = time.time()
 
184
    else:
 
185
        file_a = open(a, 'rb')
 
186
        time_a = os.stat(a).st_mtime
 
187
 
 
188
    if b == '-':
 
189
        file_b = sys.stdin
 
190
        time_b = time.time()
 
191
    else:
 
192
        file_b = open(b, 'rb')
 
193
        time_b = os.stat(b).st_mtime
 
194
 
 
195
    # TODO: Include fromfiledate and tofiledate
 
196
    return unified_diff_bytes(file_a.readlines(), file_b.readlines(),
 
197
                              fromfile=a, tofile=b,
 
198
                              sequencematcher=sequencematcher)
 
199
 
 
200
 
 
201
try:
 
202
    from ._patiencediff_c import (
 
203
        unique_lcs_c as unique_lcs,
 
204
        recurse_matches_c as recurse_matches,
 
205
        PatienceSequenceMatcher_c as PatienceSequenceMatcher
 
206
        )
 
207
except ImportError:
 
208
    from ._patiencediff_py import (
 
209
        unique_lcs_py as unique_lcs,
 
210
        recurse_matches_py as recurse_matches,
 
211
        PatienceSequenceMatcher_py as PatienceSequenceMatcher
 
212
        )  # noqa: F401
 
213
 
 
214
 
 
215
def main(args):
 
216
    import optparse
 
217
    p = optparse.OptionParser(usage='%prog [options] file_a file_b'
 
218
                                    '\nFiles can be "-" to read from stdin')
 
219
    p.add_option('--patience', dest='matcher', action='store_const', const='patience',
 
220
                 default='patience', help='Use the patience difference algorithm')
 
221
    p.add_option('--difflib', dest='matcher', action='store_const', const='difflib',
 
222
                 default='patience', help='Use python\'s difflib algorithm')
 
223
 
 
224
    algorithms = {'patience': PatienceSequenceMatcher,
 
225
                  'difflib': difflib.SequenceMatcher}
 
226
 
 
227
    (opts, args) = p.parse_args(args)
 
228
    matcher = algorithms[opts.matcher]
 
229
 
 
230
    if len(args) != 2:
 
231
        print('You must supply 2 filenames to diff')
 
232
        return -1
 
233
 
 
234
    for line in unified_diff_files(args[0], args[1], sequencematcher=matcher):
 
235
        sys.stdout.write(line)
 
236
 
 
237
 
 
238
if __name__ == '__main__':
 
239
    sys.exit(main(sys.argv[1:]))