~ubuntu-branches/ubuntu/hardy/xxdiff/hardy

« back to all changes in this revision

Viewing changes to bin/xxdiff-cond-replace

  • Committer: Bazaar Package Importer
  • Author(s): Tomas Pospisek
  • Date: 2005-03-29 08:43:56 UTC
  • mfrom: (1.2.1 upstream) (2.1.2 hoary)
  • Revision ID: james.westby@ubuntu.com-20050329084356-nkwv8jf18nc7u5qf
Tags: 1:3.1-3
re-upload

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#******************************************************************************\
 
3
#* $Source: /u/blais/cvsroot/xxdiff/bin/xxdiff-cond-replace,v $
 
4
#* $Id: xxdiff-cond-replace,v 1.9 2004/11/03 22:53:39 blais Exp $
 
5
#* $Date: 2004/11/03 22:53:39 $
 
6
#*
 
7
#* Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>
 
8
#*
 
9
#* This program is free software; you can redistribute it and/or modify
 
10
#* it under the terms of the GNU General Public License as published by
 
11
#* the Free Software Foundation; either version 2 of the License, or
 
12
#* (at your option) any later version.
 
13
#*
 
14
#* This program is distributed in the hope that it will be useful,
 
15
#* but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
17
#* GNU General Public License for more details.
 
18
#*
 
19
#* You should have received a copy of the GNU General Public License
 
20
#* along with this program; if not, write to the Free Software
 
21
#* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
22
#*
 
23
#*****************************************************************************/
 
24
 
 
25
"""xxdiff-cond-replace [<options>] <orig-file> <modified-file>
 
26
 
 
27
Useful script to conditionally replace a original by a generated file.
 
28
 
 
29
Basically, you invoke some script or pipeline of scripts to perform
 
30
modifications on an original text file and store the results in a temporary file
 
31
(the modified-file), and then invoke this script to conditionally overwrite the
 
32
contents of the orig-file with those of the modified file.
 
33
 
 
34
This works like copy (``cp``), except that xxdiff is shown to ask the user
 
35
whether to copy the file or not.  This is useful to run in a loop, for example::
 
36
 
 
37
   for i in `*/*.xml` ; do
 
38
      cat $i | ...pipeline of cmds that modifies the file... > /tmp/out.xml
 
39
      # copy only if the user accepts or merges.
 
40
      xxdiff-cond-replace $i /tmp/out.xml
 
41
   done
 
42
 
 
43
**IMPORTANT**: Notice well that the original file which will be overwritten is
 
44
on the LEFT side. The syntax is reversed that of the UNIX cp command.
 
45
 
 
46
Exit Status
 
47
-----------
 
48
 
 
49
This program exits with status 0 if the copy operation was accepted or merged,
 
50
with 1 otherwise.
 
51
 
 
52
Notes
 
53
-----
 
54
 
 
55
- the script automatically creates backup files.
 
56
- the script automatically generates a detailed log of its actions and
 
57
  a text summary of all the differences beteween the original and new files.
 
58
- the script can optionally checkout the file with ClearCase before performing
 
59
  the replacement.
 
60
 
 
61
"""
 
62
 
 
63
__version__ = "$Revision: 1.9 $"
 
64
__author__ = "Martin Blais <blais@furius.ca>"
 
65
__depends__ = ['xxdiff', 'Python-2.3']
 
66
__copyright__ = """Copyright (C) 2003-2004 Martin Blais <blais@furius.ca>.
 
67
This code is distributed under the terms of the GNU General Public License."""
 
68
 
 
69
#===============================================================================
 
70
# EXTERNAL DECLARATIONS
 
71
#===============================================================================
 
72
 
 
73
import sys, os, re
 
74
from os.path import *
 
75
import commands, tempfile, shutil
 
76
 
 
77
#===============================================================================
 
78
# LOCAL DECLARATIONS
 
79
#===============================================================================
 
80
 
 
81
tmpprefix = '%s.' % basename(sys.argv[0])
 
82
 
 
83
#-------------------------------------------------------------------------------
 
84
#
 
85
def backup( fn, opts ):
 
86
    """Compute backup filename and copy backup file."""
 
87
 
 
88
    if opts.backup_type == 'parallel':
 
89
        fmt = '%s.bak.%d'
 
90
        ii = 0
 
91
        while 1:
 
92
            backupfn = fmt % (fn, ii)
 
93
            if not exists(backupfn):
 
94
                break
 
95
            ii += 1
 
96
 
 
97
    elif opts.backup_type == 'other':
 
98
        ##afn = abspath(fn)
 
99
        backupfn = normpath(join(opts.backup_dir, fn))
 
100
    else:
 
101
        backupfn = None
 
102
 
 
103
    if backupfn:
 
104
        if not opts.silent:
 
105
            print 'Backup:', backupfn
 
106
        ddn = dirname(backupfn)
 
107
        if ddn and not exists(ddn):
 
108
            os.makedirs(ddn)
 
109
        shutil.copy2(fn, backupfn)
 
110
 
 
111
#-------------------------------------------------------------------------------
 
112
#
 
113
def cc_checkout(fn, opts):
 
114
    if not opts.silent:
 
115
        print 'Checking out the file'
 
116
    os.system('cleartool co -nc "%s"' % fn)
 
117
 
 
118
#-------------------------------------------------------------------------------
 
119
#
 
120
def cond_replace( origfile, modfile, opts ):
 
121
 
 
122
    """Spawn xxdiff and perform the replacement if confirmed."""
 
123
 
 
124
    if opts.diff:
 
125
        print '=' * 80
 
126
        print 'File:    ', origfile
 
127
        print 'Absolute:', abspath(origfile)
 
128
        print
 
129
 
 
130
        # Run diff command.
 
131
        difffmt = 'diff -y --suppress-common-lines "%s" "%s" 2>&1'
 
132
        diffcmd = difffmt % (origfile, modfile)
 
133
        s, o = commands.getstatusoutput(diffcmd)
 
134
 
 
135
        rv = os.WEXITSTATUS(s)
 
136
        if rv == 0:
 
137
            print >> sys.stderr
 
138
            print >> sys.stderr, "Warning: no differences."
 
139
            print >> sys.stderr
 
140
 
 
141
        #print
 
142
        #print '_' * 80
 
143
        print o
 
144
        #print '_' * 80
 
145
        #print
 
146
        print
 
147
 
 
148
    rval = 0
 
149
    if not opts.dry_run:
 
150
        tmpf2 = tempfile.NamedTemporaryFile('w', prefix=tmpprefix)
 
151
 
 
152
        if not opts.no_confirm:
 
153
            cmd = ('%s --decision --merged-filename "%s" ' + \
 
154
                   '--title2 "NEW FILE" "%s" "%s" ') % \
 
155
                   (opts.command, tmpf2.name, origfile, modfile)
 
156
            s, o = commands.getstatusoutput(cmd)
 
157
 
 
158
        else:
 
159
            s, o = 0, 'NO_CONFIRM'
 
160
 
 
161
        if not opts.silent:
 
162
            if opts.diff:
 
163
                print o
 
164
            else:
 
165
                print o, origfile
 
166
        if o == 'ACCEPT' or o == 'NO_CONFIRM':
 
167
            backup(origfile, opts)
 
168
            if opts.checkout_clearcase:
 
169
                cc_checkout(origfile, opts)
 
170
 
 
171
            shutil.copyfile(modfile, origfile)
 
172
        elif o == 'REJECT' or o == 'NODECISION':
 
173
            rval = 1
 
174
        elif o == 'MERGED':
 
175
            if opts.diff:
 
176
                # run diff again to show the changes that have actually been
 
177
                # merged in the output log.
 
178
                diffcmd = difffmt % (origfile, tmpf2.name)
 
179
                s, o = commands.getstatusoutput(diffcmd)
 
180
                print 'Actual merged changes:'
 
181
                print
 
182
                print o
 
183
                print
 
184
 
 
185
            backup(origfile, opts)
 
186
            if opts.checkout_clearcase:
 
187
                cc_checkout(origfile, opts)
 
188
 
 
189
            shutil.copyfile(tmpf2.name, origfile)
 
190
        else:
 
191
            raise SystemExit("Error: unexpected answer from xxdiff: %s" % o)
 
192
 
 
193
        if opts.delete:
 
194
            try:
 
195
                os.unlink(modfile)
 
196
            except OSError, e:
 
197
                raise SystemExit("Error: deleting modified file (%s)" % str(e))
 
198
 
 
199
    return rval
 
200
 
 
201
#-------------------------------------------------------------------------------
 
202
#
 
203
def complete( parser ):
 
204
    "Programmable completion support. Script should work without it."
 
205
    try:
 
206
        import optcomplete
 
207
        optcomplete.autocomplete(parser)
 
208
    except ImportError:
 
209
        pass
 
210
 
 
211
#===============================================================================
 
212
# MAIN
 
213
#===============================================================================
 
214
 
 
215
def main():
 
216
    import optparse
 
217
    parser = optparse.OptionParser(__doc__.strip(), version=__version__)
 
218
    parser.add_option('--command', action='store', default='xxdiff',
 
219
                      help="xxdiff command prefix to use.")
 
220
    parser.add_option('-b', '--backup-type', action='store', type='choice',
 
221
                      choices=['parallel', 'other', 'none'], metavar="CHOICE",
 
222
                      default='none',
 
223
                      help="selects the backup type "
 
224
                      "('parallel', 'other', 'none')")
 
225
    parser.add_option('--backup-dir', action='store',
 
226
                      help="specify backup directory for type 'other'")
 
227
    parser.add_option('-C', '--checkout-clearcase', action='store_true',
 
228
                      help="checkout files with clearcase before storing.")
 
229
    parser.add_option('-n', '--dry-run', action='store_true',
 
230
                      help="print the commands that would be executed " +
 
231
                      "but don't really run them.")
 
232
    parser.add_option('-q', '--silent', '--quiet', action='store_true',
 
233
                      help="Do not output anything. Normally the decision "
 
234
                      "status and backup file location is output.")
 
235
    parser.add_option('-x', '--diff', action='store_true',
 
236
                      help="Run a diff and log the differences on stdout.")
 
237
    parser.add_option('-d', '--delete', action='store_true',
 
238
                      help="Instead of copying the temporary file, move it "
 
239
                      "(delete it after copying).")
 
240
    parser.add_option('-X', '--no-confirm', action='store_true',
 
241
                      help="do not ask for confirmation with graphical "
 
242
                      "diff viewer. This essentially generates a diff log and "
 
243
                      "copies the file over with backups.")
 
244
    complete(parser)
 
245
    opts, args = parser.parse_args()
 
246
 
 
247
 
 
248
    if not args or len(args) > 2:
 
249
        raise parser.error("you must specify exactly two files.")
 
250
    if len(args) == 1:
 
251
        if opts.delete:
 
252
            raise parser.error("no need to use --delete on file from stdin.")
 
253
 
 
254
        origfile, = args
 
255
        intmpf = tempfile.NamedTemporaryFile('w', prefix=tmpprefix)
 
256
        try:
 
257
            intmpf.write(sys.stdin.read())
 
258
            sys.stdin.close()
 
259
            intmpf.flush()
 
260
        except IOError, e:
 
261
            raise SystemExit(
 
262
                "Error: saving stdin to temporary file (%s)" % str(e))
 
263
        modfile = intmpf.name
 
264
    else:
 
265
        origfile, modfile = args
 
266
 
 
267
    if opts.silent and opts.diff:
 
268
        raise parser.error("you cannot ask for a diff output and for silent at "
 
269
                           "the same time.")
 
270
        
 
271
    if opts.backup_type == 'other':
 
272
        if not opts.backup_dir:
 
273
            opts.backup_dir = tempfile.mkdtemp(prefix=tmpprefix)
 
274
        print "Storing backup files under:", opts.backup_dir
 
275
    else:
 
276
        if opts.backup_dir:
 
277
            raise SystemExit("Error: backup-dir is only valid for backups of "
 
278
                             "type 'other'.")
 
279
 
 
280
    # call xxdiff and perform the conditional replacement.
 
281
    rval = cond_replace(origfile, modfile, opts)
 
282
 
 
283
    # repeat message at the end for convenience.
 
284
    if opts.backup_type == 'other' and opts.backup_dir:
 
285
        print
 
286
        print "Storing backup files under:", opts.backup_dir
 
287
        print
 
288
 
 
289
    return rval
 
290
 
 
291
if __name__ == '__main__':
 
292
    try:
 
293
        sys.exit(main())
 
294
    except KeyboardInterrupt:
 
295
        print >> sys.stderr, 'Interrupted.'