#!/usr/bin/env python
#
# $Source: /home/blais/repos/cvsroot/conf/bin/mv-filelist,v $
# $Id: mv-filelist,v 1.3 2005/10/17 01:03:55 blais Exp $
#

"""mv-filelist [<options>] <filelist-file>

Script that takes list of pairs of filenames and renames the files as such.  The
particularity of this script is that compared to a Bourne shell script, it can
handle arbitrary filenames with spaces, single-quotes, double-quotes and
parentheses easily.  There is no quoting happening on the filenames.

The input file we are expecting is a list of lines, each representing a
filename. We are expecting filenames to be ordered in input, output pairs, for
example::

   input-filename-1
   output-filename-1
   input-filename-2
   output-filename-2
   input-filename-3
   output-filename-3
   ...

Empty lines are ignored.

This may seem silly, but attempting to move weird filenames from a shell scripts
has become a very annoying task and I find this rather useful to clean up weird
filenames.  This is great for cleaning up various media filenames of the kind of
files that can be downloaded off of the internet, unicode names, names with
spaces, etc.
"""

__version__ = "Revision: 1.3 "
__author__ = "Martin Blais <blais@furius.ca>"


# stdlib imports
import os, sys
from os.path import *
import re, subprocess


FROM, TO = object(), object()
_copy = ('cp', FROM, TO)
_svn = ('svn' ,'mv', FROM, TO)
_hg = ('hg', 'mv', FROM, TO)

_methods = {
    'rename': None,
    'copy': _copy,
    'subversion': _svn,
    'svn': _svn,
    'mercurial': _hg,
    'hg': _hg,
    }

def replace(cmd, from_, to_):
    r = []
    for x in cmd:
        if x is FROM:
            r.append(from_)
        elif x is TO:
            r.append(to_)
        else:
            r.append(x)
    return r



def main():
    import optparse
    parser = optparse.OptionParser(__doc__.strip(), version=__version__)

    parser.add_option('-n', '--dry-run', action='store_true',
                      help="Do not actually rename. Just print what to do.")

    parser.add_option('-m', '-M', '--method', action='store',
                      choices=_methods.keys(),
                      help="Select the mv method (%s)." % ','.join(_methods.iterkeys()))
    parser.add_option('-S', '--subversion', '--svn',
                      action='store_const', const='svn', dest='method',
                      help="Move the files with Subversion. "
                      "(Use -M instead; this option kept for backward compatbility.)")

    parser.add_option('-d', '--accept-move-to-dir', action='store_true',
                      help="Accept the destination filename be a directory")

    parser.add_option('-K', '--makedirs', action='store_true',
                      help="Create missing directories at destination")

    opts, args = parser.parse_args()

    filenames = args
    if not args:
        filenames = ['-']

    # read input pairs.
    empty_re = re.compile('^\s*$')
    lsp = len(os.linesep)
    lines = []
    for fn in filenames:
        try:
            f = fn == '-' and sys.stdin or open(fn, 'r')
        except IOError, e:
            raise SystemExit("Error: reading input file (%s)" % str(e))

        newlines = [x[:-lsp] for x in f if not empty_re.match(x)]
        if len(newlines) % 2 != 0:
            raise SystemExit(
                "Error: file '%s' has odd number of lines." % fn)

        lines += newlines

    # create list of (from, to) pairs to be renamed.
    pairs = [[lines[ki], lines[ki+1]] for ki in xrange(0, len(lines), 2)]

    # ignore all renames to self
    pairs = filter(lambda np: np[0] != np[1], pairs)

    # if moving to directories is allowed and the directory exists, adjust the
    # destination name appropriately
    if opts.accept_move_to_dir:
        for pair in pairs:
            fromm, too = pair
            if exists(too) and isdir(too):
                pair[1] = join(too, basename(fromm))

    # pre-run checks.
    toset = {}
    exit = False
    for fromm, too in pairs:
        # check that the source files exists before doing anything.
        if not exists(fromm):
            print >> sys.stderr, "Error: no such file or directory '%s'" % fromm
            exit = True

        # check that the destination filenames are either directories or do not
        # exist
        if exists(too):
            print >> sys.stderr, "Error: destination file exists '%s'" % too
            print >> sys.stderr, "  (Source is '%s')" % fromm
            exit = True

        # check that the destination directories exist
        dn = dirname(too)
        if not opts.makedirs and dn and not exists(dn):
            print >> sys.stderr, ("Error: destination directory does "
                                  "not exist: '%s'" % dn)
            print >> sys.stderr, "  (Source is '%s')" % fromm
            exit = True

        # check that the output filenames do not collide.
        elif too in toset:
            print >> sys.stderr, "Error: multiple destinations with '%s'" % too
            exit = True
        toset[too] = 1

    if exit:
        sys.exit(1)

    # perform the actual renames.
    for fromm, too in pairs:
        print 'Renaming: [%s]' % fromm
        print '      To: [%s]' % too
        print

        if not opts.dry_run:
            cmd = _methods.get(opts.method, None)
            if cmd is not None:
                _cmd = replace(cmd, fromm, too)
                p = subprocess.Popen(_cmd, shell=False)
                r = p.wait()
                if r != 0:
                    raise SystemExit("Error: moving file with \"%s\": '%s'" % (_cmd, fromm))
            else:
                # Create output directory if requested
                dn = dirname(too)
                if opts.makedirs and dn and not exists(dn):
                    print 'Making dir: [%s]' % dn
                    print
                    os.makedirs(dn)

                try:
                    os.rename(fromm, too)
                except OSError, e:
                    raise SystemExit("Error: renaming file (%s)" % str(e))

if __name__ == '__main__':
    main()

