/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: Jelmer Vernooij
  • Date: 2019-05-29 03:28:14 UTC
  • mfrom: (7303 work)
  • mto: This revision was merged to the branch mainline in revision 7305.
  • Revision ID: jelmer@jelmer.uk-20190529032814-fzqbrgf9647u9ptq
Merge trunk.

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:]))