mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	added make_archive (and secondary APIs) to shutil
This commit is contained in:
		
							parent
							
								
									b0aad6cd09
								
							
						
					
					
						commit
						48cc8dc958
					
				
					 3 changed files with 694 additions and 5 deletions
				
			
		
							
								
								
									
										281
									
								
								Lib/shutil.py
									
										
									
									
									
								
							
							
						
						
									
										281
									
								
								Lib/shutil.py
									
										
									
									
									
								
							|  | @ -9,9 +9,21 @@ | |||
| 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 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tarek Ziadé
						Tarek Ziadé