2
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
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.
 
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.
 
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
 
19
 
from bzrlib.lazy_import import lazy_import
 
20
 
lazy_import(globals(), """
 
28
 
__all__ = ['PatienceSequenceMatcher', 'unified_diff', 'unified_diff_files']
 
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):
 
38
 
    Compare two sequences of lines; generate the delta as a unified diff.
 
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
 
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
 
50
 
    For inputs that do not have trailing newlines, set the lineterm
 
51
 
    argument to "" so that the output will be uniformly newline free.
 
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().
 
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',
 
65
 
    --- Original Sat Jan 26 23:30:50 1991
 
66
 
    +++ Current Fri Jun 06 10:20:52 2003
 
75
 
    if sequencematcher is None:
 
77
 
        sequencematcher = difflib.SequenceMatcher
 
80
 
        fromfiledate = '\t' + str(fromfiledate)
 
82
 
        tofiledate = '\t' + str(tofiledate)
 
85
 
    for group in sequencematcher(None,a,b).get_grouped_opcodes(n):
 
87
 
            yield '--- %s%s%s' % (fromfile, fromfiledate, lineterm)
 
88
 
            yield '+++ %s%s%s' % (tofile, tofiledate, lineterm)
 
90
 
        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
 
91
 
        yield "@@ -%d,%d +%d,%d @@%s" % (i1+1, i2-i1, j1+1, j2-j1, lineterm)
 
92
 
        for tag, i1, i2, j1, j2 in group:
 
97
 
            if tag == 'replace' or tag == 'delete':
 
100
 
            if tag == 'replace' or tag == 'insert':
 
101
 
                for line in b[j1:j2]:
 
105
 
def unified_diff_files(a, b, sequencematcher=None):
 
106
 
    """Generate the diff for two files.
 
108
 
    # Should this actually be an error?
 
115
 
        file_a = open(a, 'rb')
 
116
 
        time_a = os.stat(a).st_mtime
 
122
 
        file_b = open(b, 'rb')
 
123
 
        time_b = os.stat(b).st_mtime
 
125
 
    # TODO: Include fromfiledate and tofiledate
 
126
 
    return unified_diff(file_a.readlines(), file_b.readlines(),
 
127
 
                        fromfile=a, tofile=b,
 
128
 
                        sequencematcher=sequencematcher)
 
132
 
    from bzrlib._patiencediff_c import (
 
133
 
        unique_lcs_c as unique_lcs,
 
134
 
        recurse_matches_c as recurse_matches,
 
135
 
        PatienceSequenceMatcher_c as PatienceSequenceMatcher
 
138
 
    from bzrlib._patiencediff_py import (
 
139
 
        unique_lcs_py as unique_lcs,
 
140
 
        recurse_matches_py as recurse_matches,
 
141
 
        PatienceSequenceMatcher_py as PatienceSequenceMatcher
 
147
 
    p = optparse.OptionParser(usage='%prog [options] file_a file_b'
 
148
 
                                    '\nFiles can be "-" to read from stdin')
 
149
 
    p.add_option('--patience', dest='matcher', action='store_const', const='patience',
 
150
 
                 default='patience', help='Use the patience difference algorithm')
 
151
 
    p.add_option('--difflib', dest='matcher', action='store_const', const='difflib',
 
152
 
                 default='patience', help='Use python\'s difflib algorithm')
 
154
 
    algorithms = {'patience':PatienceSequenceMatcher, 'difflib':difflib.SequenceMatcher}
 
156
 
    (opts, args) = p.parse_args(args)
 
157
 
    matcher = algorithms[opts.matcher]
 
160
 
        print 'You must supply 2 filenames to diff'
 
163
 
    for line in unified_diff_files(args[0], args[1], sequencematcher=matcher):
 
164
 
        sys.stdout.write(line)
 
167
 
if __name__ == '__main__':
 
168
 
    sys.exit(main(sys.argv[1:]))