##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Generic file and directory installer.

Typically called when installing Zope via the Makefile written by
'configure.py' in the 'make install' step.
"""
import getopt
import os
import re
import shutil
import stat
import sys

# RE that, if a pathname's base name matches, causes it to be ignored
# when copying that file or directory.
default_omitpattern = r'(\..*|CVS|.*~)$'

def main(src, dst, dirmode=0755, fmode=0644,
         omitpattern=default_omitpattern, retain_xbit=1):
    """
    Copy a file or directory named by src to a file or directory named by
    dst, normalizing mode bit settings as necessary.  Recursively copies
    directory trees using shutil.copy2().

    Errors are reported to standard output.

      - 'dirmode' is the directory creation mode.  All directories
        are created with this mode.
      - 'fmode' is the default file creation mode.  This mode
        is modified by the status of the source file.  If the source
        file is executable, mod the fmode to be +wgo executable.
      - omitpattern is a Python-style regex pattern.  If a file
        or directory name matches this pattern, it will never be copied.
      - if the dst directory already exists, don't raise an error.
    """
    try:
        if os.path.isdir(src):
            copydir(src, dst, dirmode, fmode, omitpattern,
                    retain_xbit)
        else:
            names = omit([src], omitpattern)
            names and copyfile(names[0], dst, fmode, retain_xbit)

    except (IOError, os.error), why:
        print "Can't copy %s to %s: %s" % (`src`, `dst`, str(why))

def copydir(src, dst, dirmode, fmode, omitpattern, retain_xbit):
    names = omit(os.listdir(src), omitpattern)
    try:
        # always create directories with dirmode
        os.makedirs(dst, dirmode)
    except os.error, why:
        if why[0] == 17:
            # directory already exists
            pass
        else:
            raise
    for name in omit(names, omitpattern):
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        if os.path.isdir(srcname):
            copydir(srcname, dstname, dirmode,fmode,omitpattern,
                    retain_xbit)
        else:
            copyfile(srcname, dstname, fmode, retain_xbit)

def copylink(src, dst):
    linkto = os.readlink(src)
    os.symlink(linkto, dst)

def copyfile(src, dst, mode, retain_xbit):
    shutil.copy2(src, dst)
    # change dest file mode to fmode but
    # make +wgo executable if source file is executable
    dstmode = mode
    st = os.stat(src)
    srcmode = st[stat.ST_MODE]
    if retain_xbit and (srcmode & stat.S_IEXEC):
        dstmode = (mode | 0111)
    if os.path.isdir(dst):
        # if dst is a directory, copy the file in to it
        os.chmod(os.path.join(dst, os.path.split(src)[-1]), dstmode)
    else:
        os.chmod(dst, dstmode)

omitcache = {}

def omit(names, omitpattern):
    return [ n for n in names
             if not re.match(omitpattern, os.path.basename(n)) ]

def usage():
    print "%s [opts] source dest" % sys.argv[0]
    print
    print "Copies a file or directory specified by 'source' to 'dest'"
    print "normalizing mode bit settings as necessary."
    print
    print "If src is a file and dst is a directory, the file will be"
    print "copied into the dst directory.  However, if src is a directory"
    print "and dst is a directory, the contents of src will be copied into"
    print "dst."
    print
    print "opts: --dirmode=mode --fmode=mode --omitpattern=patt"
    print
    print "  --dontcopyxbit   when copying a file marked as executable,"
    print "                   don't make the copy executable."
    print "  --dirmode        mode bit settings of dest dirs (e.g. '755')"
    print "  --fmode          mode bit settings of dest files (e.g. '644')"
    print "                   (modified wgo+x when dontcopyxbit is not"
    print "                   specified)"
    print "  --omitpattern    a Python-style regex pattern.  File and"
    print "                   directory names which match this pattern will "
    print "                   not be copied.  The default omitpattern is"
    print "                   '%s'" % default_omitpattern

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print "too few arguments"
        usage()
        sys.exit(2)
    dirmode = 0755
    fmode   = 0644
    omitpattern = default_omitpattern
    retain_xbit = 1
    longopts = ['dirmode=', 'fmode=', 'omitpattern=', 'help',
                'copyxmode' ]
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'h', longopts)
    except getopt.GetoptError, v:
        print v
        usage()
        sys.exit(2)
    try:
        source, dest = args
    except:
        print "wrong number of arguments"
        usage()
        sys.exit(2)
    for o, a in opts:
        if o in ('-h', '--help'):
            usage()
            sys.exit()
        if o == '--dirmode':
            if not a.startswith('0'):
                a = '0%s' % a
            dirmode = eval(a)
        if o == '--fmode':
            if not a.startswith('0'):
                a = '0%s' % a
            fmode = eval(a)
        if o == '--omitpattern':
            omitpattern = a
        if o == '--dontcopyxbit':
            retain_xbit = 0
    main(source, dest, dirmode, fmode, omitpattern, retain_xbit)
