diff options
Diffstat (limited to 'Lib/shutil.py')
| -rw-r--r-- | Lib/shutil.py | 281 | 
1 files changed, 280 insertions, 1 deletions
diff --git a/Lib/shutil.py b/Lib/shutil.py index a689256..ebed140 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -9,9 +9,21 @@ import sys  import stat  from os.path import abspath  import fnmatch +from warnings import warn + +try: +    from pwd import getpwnam +except ImportError: +    getpwnam = None + +try: +    from grp import getgrnam +except ImportError: +    getgrnam = None  __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2", -           "copytree","move","rmtree","Error", "SpecialFileError"] +           "copytree","move","rmtree","Error", "SpecialFileError", +           "ExecError","make_archive"]  class Error(EnvironmentError):      pass @@ -20,6 +32,9 @@ class SpecialFileError(EnvironmentError):      """Raised when trying to do a kind of operation (e.g. copying) which is      not supported on a special file (e.g. a named pipe)""" +class ExecError(EnvironmentError): +    """Raised when a command could not be executed""" +  try:      WindowsError  except NameError: @@ -286,3 +301,267 @@ def _destinsrc(src, dst):      if not dst.endswith(os.path.sep):          dst += os.path.sep      return dst.startswith(src) + +def _get_gid(name): +    """Returns a gid, given a group name.""" +    if getgrnam is None or name is None: +        return None +    try: +        result = getgrnam(name) +    except KeyError: +        result = None +    if result is not None: +        return result[2] +    return None + +def _get_uid(name): +    """Returns an uid, given a user name.""" +    if getpwnam is None or name is None: +        return None +    try: +        result = getpwnam(name) +    except KeyError: +        result = None +    if result is not None: +        return result[2] +    return None + +def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, +                  owner=None, group=None, logger=None): +    """Create a (possibly compressed) tar file from all the files under +    'base_dir'. + +    'compress' must be "gzip" (the default), "compress", "bzip2", or None. +    (compress will be deprecated in Python 3.2) + +    'owner' and 'group' can be used to define an owner and a group for the +    archive that is being built. If not provided, the current owner and group +    will be used. + +    The output tar file will be named 'base_dir' +  ".tar", possibly plus +    the appropriate compression extension (".gz", ".bz2" or ".Z"). + +    Returns the output filename. +    """ +    tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''} +    compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'} + +    # flags for compression program, each element of list will be an argument +    if compress is not None and compress not in compress_ext.keys(): +        raise ValueError, \ +              ("bad value for 'compress': must be None, 'gzip', 'bzip2' " +               "or 'compress'") + +    archive_name = base_name + '.tar' +    if compress != 'compress': +        archive_name += compress_ext.get(compress, '') + +    archive_dir = os.path.dirname(archive_name) +    if not os.path.exists(archive_dir): +        logger.info("creating %s" % archive_dir) +        if not dry_run: +            os.makedirs(archive_dir) + + +    # creating the tarball +    import tarfile  # late import so Python build itself doesn't break + +    if logger is not None: +        logger.info('Creating tar archive') + +    uid = _get_uid(owner) +    gid = _get_gid(group) + +    def _set_uid_gid(tarinfo): +        if gid is not None: +            tarinfo.gid = gid +            tarinfo.gname = group +        if uid is not None: +            tarinfo.uid = uid +            tarinfo.uname = owner +        return tarinfo + +    if not dry_run: +        tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress]) +        try: +            tar.add(base_dir, filter=_set_uid_gid) +        finally: +            tar.close() + +    # compression using `compress` +    # XXX this block will be removed in Python 3.2 +    if compress == 'compress': +        warn("'compress' will be deprecated.", PendingDeprecationWarning) +        # the option varies depending on the platform +        compressed_name = archive_name + compress_ext[compress] +        if sys.platform == 'win32': +            cmd = [compress, archive_name, compressed_name] +        else: +            cmd = [compress, '-f', archive_name] +        from distutils.spawn import spawn +        spawn(cmd, dry_run=dry_run) +        return compressed_name + +    return archive_name + +def _call_external_zip(directory, verbose=False): +    # XXX see if we want to keep an external call here +    if verbose: +        zipoptions = "-r" +    else: +        zipoptions = "-rq" +    from distutils.errors import DistutilsExecError +    from distutils.spawn import spawn +    try: +        spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run) +    except DistutilsExecError: +        # XXX really should distinguish between "couldn't find +        # external 'zip' command" and "zip failed". +        raise ExecError, \ +            ("unable to create zip file '%s': " +            "could neither import the 'zipfile' module nor " +            "find a standalone zip utility") % zip_filename + +def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None): +    """Create a zip file from all the files under 'base_dir'. + +    The output zip file will be named 'base_dir' + ".zip".  Uses either the +    "zipfile" Python module (if available) or the InfoZIP "zip" utility +    (if installed and found on the default search path).  If neither tool is +    available, raises ExecError.  Returns the name of the output zip +    file. +    """ +    zip_filename = base_name + ".zip" +    archive_dir = os.path.dirname(base_name) + +    if not os.path.exists(archive_dir): +        if logger is not None: +            logger.info("creating %s", archive_dir) +        if not dry_run: +            os.makedirs(archive_dir) + +    # If zipfile module is not available, try spawning an external 'zip' +    # command. +    try: +        import zipfile +    except ImportError: +        zipfile = None + +    if zipfile is None: +        _call_external_zip(base_dir, verbose) +    else: +        if logger is not None: +            logger.info("creating '%s' and adding '%s' to it", +                        zip_filename, base_dir) + +        if not dry_run: +            zip = zipfile.ZipFile(zip_filename, "w", +                                  compression=zipfile.ZIP_DEFLATED) + +            for dirpath, dirnames, filenames in os.walk(base_dir): +                for name in filenames: +                    path = os.path.normpath(os.path.join(dirpath, name)) +                    if os.path.isfile(path): +                        zip.write(path, path) +                        if logger is not None: +                            logger.info("adding '%s'", path) +            zip.close() + +    return zip_filename + +_ARCHIVE_FORMATS = { +    'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), +    'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), +    'ztar':  (_make_tarball, [('compress', 'compress')], +                "compressed tar file"), +    'tar':   (_make_tarball, [('compress', None)], "uncompressed tar file"), +    'zip':   (_make_zipfile, [],"ZIP file") +    } + +def get_archive_formats(): +    """Returns a list of supported formats for archiving and unarchiving. + +    Each element of the returned sequence is a tuple (name, description) +    """ +    formats = [(name, registry[2]) for name, registry in +               _ARCHIVE_FORMATS.items()] +    formats.sort() +    return formats + +def register_archive_format(name, function, extra_args=None, description=''): +    """Registers an archive format. + +    name is the name of the format. function is the callable that will be +    used to create archives. If provided, extra_args is a sequence of +    (name, value) tuples that will be passed as arguments to the callable. +    description can be provided to describe the format, and will be returned +    by the get_archive_formats() function. +    """ +    if extra_args is None: +        extra_args = [] +    if not callable(function): +        raise TypeError('The %s object is not callable' % function) +    if not isinstance(extra_args, (tuple, list)): +        raise TypeError('extra_args needs to be a sequence') +    for element in extra_args: +        if not isinstance(element, (tuple, list)) or len(element) !=2 : +            raise TypeError('extra_args elements are : (arg_name, value)') + +    _ARCHIVE_FORMATS[name] = (function, extra_args, description) + +def unregister_archive_format(name): +    del _ARCHIVE_FORMATS[name] + +def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, +                 dry_run=0, owner=None, group=None, logger=None): +    """Create an archive file (eg. zip or tar). + +    'base_name' is the name of the file to create, minus any format-specific +    extension; 'format' is the archive format: one of "zip", "tar", "ztar", +    or "gztar". + +    'root_dir' is a directory that will be the root directory of the +    archive; ie. we typically chdir into 'root_dir' before creating the +    archive.  'base_dir' is the directory where we start archiving from; +    ie. 'base_dir' will be the common prefix of all files and +    directories in the archive.  'root_dir' and 'base_dir' both default +    to the current directory.  Returns the name of the archive file. + +    'owner' and 'group' are used when creating a tar archive. By default, +    uses the current owner and group. +    """ +    save_cwd = os.getcwd() +    if root_dir is not None: +        if logger is not None: +            logger.debug("changing into '%s'", root_dir) +        base_name = os.path.abspath(base_name) +        if not dry_run: +            os.chdir(root_dir) + +    if base_dir is None: +        base_dir = os.curdir + +    kwargs = {'dry_run': dry_run, 'logger': logger} + +    try: +        format_info = _ARCHIVE_FORMATS[format] +    except KeyError: +        raise ValueError, "unknown archive format '%s'" % format + +    func = format_info[0] +    for arg, val in format_info[1]: +        kwargs[arg] = val + +    if format != 'zip': +        kwargs['owner'] = owner +        kwargs['group'] = group + +    try: +        filename = func(base_name, base_dir, **kwargs) +    finally: +        if root_dir is not None: +            if logger is not None: +                logger.debug("changing back to '%s'", save_cwd) +            os.chdir(save_cwd) + +    return filename  | 
