~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/patiencediff.py

  • Committer: Patch Queue Manager
  • Date: 2011-09-22 14:12:18 UTC
  • mfrom: (6155.3.1 jam)
  • Revision ID: pqm@pqm.ubuntu.com-20110922141218-86s4uu6nqvourw4f
(jameinel) Cleanup comments bzrlib/smart/__init__.py (John A Meinel)

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
 
 
19
from bzrlib.lazy_import import lazy_import
 
20
lazy_import(globals(), """
 
21
import os
 
22
import sys
 
23
import time
 
24
import difflib
 
25
""")
 
26
 
 
27
 
 
28
__all__ = ['PatienceSequenceMatcher', 'unified_diff', 'unified_diff_files']
 
29
 
 
30
 
 
31
# This is a version of unified_diff which only adds a factory parameter
 
32
# so that you can override the default SequenceMatcher
 
33
# this has been submitted as a patch to python
 
34
def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
 
35
                 tofiledate='', n=3, lineterm='\n',
 
36
                 sequencematcher=None):
 
37
    r"""
 
38
    Compare two sequences of lines; generate the delta as a unified diff.
 
39
 
 
40
    Unified diffs are a compact way of showing line changes and a few
 
41
    lines of context.  The number of context lines is set by 'n' which
 
42
    defaults to three.
 
43
 
 
44
    By default, the diff control lines (those with ---, +++, or @@) are
 
45
    created with a trailing newline.  This is helpful so that inputs
 
46
    created from file.readlines() result in diffs that are suitable for
 
47
    file.writelines() since both the inputs and outputs have trailing
 
48
    newlines.
 
49
 
 
50
    For inputs that do not have trailing newlines, set the lineterm
 
51
    argument to "" so that the output will be uniformly newline free.
 
52
 
 
53
    The unidiff format normally has a header for filenames and modification
 
54
    times.  Any or all of these may be specified using strings for
 
55
    'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.  The modification
 
56
    times are normally expressed in the format returned by time.ctime().
 
57
 
 
58
    Example:
 
59
 
 
60
    >>> for line in unified_diff('one two three four'.split(),
 
61
    ...             'zero one tree four'.split(), 'Original', 'Current',
 
62
    ...             'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:20:52 2003',
 
63
    ...             lineterm=''):
 
64
    ...     print line
 
65
    --- Original Sat Jan 26 23:30:50 1991
 
66
    +++ Current Fri Jun 06 10:20:52 2003
 
67
    @@ -1,4 +1,4 @@
 
68
    +zero
 
69
     one
 
70
    -two
 
71
    -three
 
72
    +tree
 
73
     four
 
74
    """
 
75
    if sequencematcher is None:
 
76
        sequencematcher = difflib.SequenceMatcher
 
77
 
 
78
    if fromfiledate:
 
79
        fromfiledate = '\t' + str(fromfiledate)
 
80
    if tofiledate:
 
81
        tofiledate = '\t' + str(tofiledate)
 
82
 
 
83
    started = False
 
84
    for group in sequencematcher(None,a,b).get_grouped_opcodes(n):
 
85
        if not started:
 
86
            yield '--- %s%s%s' % (fromfile, fromfiledate, lineterm)
 
87
            yield '+++ %s%s%s' % (tofile, tofiledate, lineterm)
 
88
            started = True
 
89
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
 
90
        yield "@@ -%d,%d +%d,%d @@%s" % (i1+1, i2-i1, j1+1, j2-j1, lineterm)
 
91
        for tag, i1, i2, j1, j2 in group:
 
92
            if tag == 'equal':
 
93
                for line in a[i1:i2]:
 
94
                    yield ' ' + line
 
95
                continue
 
96
            if tag == 'replace' or tag == 'delete':
 
97
                for line in a[i1:i2]:
 
98
                    yield '-' + line
 
99
            if tag == 'replace' or tag == 'insert':
 
100
                for line in b[j1:j2]:
 
101
                    yield '+' + line
 
102
 
 
103
 
 
104
def unified_diff_files(a, b, sequencematcher=None):
 
105
    """Generate the diff for two files.
 
106
    """
 
107
    # Should this actually be an error?
 
108
    if a == b:
 
109
        return []
 
110
    if a == '-':
 
111
        file_a = sys.stdin
 
112
        time_a = time.time()
 
113
    else:
 
114
        file_a = open(a, 'rb')
 
115
        time_a = os.stat(a).st_mtime
 
116
 
 
117
    if b == '-':
 
118
        file_b = sys.stdin
 
119
        time_b = time.time()
 
120
    else:
 
121
        file_b = open(b, 'rb')
 
122
        time_b = os.stat(b).st_mtime
 
123
 
 
124
    # TODO: Include fromfiledate and tofiledate
 
125
    return unified_diff(file_a.readlines(), file_b.readlines(),
 
126
                        fromfile=a, tofile=b,
 
127
                        sequencematcher=sequencematcher)
 
128
 
 
129
 
 
130
try:
 
131
    from bzrlib._patiencediff_c import (
 
132
        unique_lcs_c as unique_lcs,
 
133
        recurse_matches_c as recurse_matches,
 
134
        PatienceSequenceMatcher_c as PatienceSequenceMatcher
 
135
        )
 
136
except ImportError:
 
137
    from bzrlib._patiencediff_py import (
 
138
        unique_lcs_py as unique_lcs,
 
139
        recurse_matches_py as recurse_matches,
 
140
        PatienceSequenceMatcher_py as PatienceSequenceMatcher
 
141
        )
 
142
 
 
143
 
 
144
def main(args):
 
145
    import optparse
 
146
    p = optparse.OptionParser(usage='%prog [options] file_a file_b'
 
147
                                    '\nFiles can be "-" to read from stdin')
 
148
    p.add_option('--patience', dest='matcher', action='store_const', const='patience',
 
149
                 default='patience', help='Use the patience difference algorithm')
 
150
    p.add_option('--difflib', dest='matcher', action='store_const', const='difflib',
 
151
                 default='patience', help='Use python\'s difflib algorithm')
 
152
 
 
153
    algorithms = {'patience':PatienceSequenceMatcher, 'difflib':difflib.SequenceMatcher}
 
154
 
 
155
    (opts, args) = p.parse_args(args)
 
156
    matcher = algorithms[opts.matcher]
 
157
 
 
158
    if len(args) != 2:
 
159
        print 'You must supply 2 filenames to diff'
 
160
        return -1
 
161
 
 
162
    for line in unified_diff_files(args[0], args[1], sequencematcher=matcher):
 
163
        sys.stdout.write(line)
 
164
 
 
165
 
 
166
if __name__ == '__main__':
 
167
    sys.exit(main(sys.argv[1:]))