blob: 98b7ea0060f8ad1fd584a6780154fb6479a4bc45 [file] [log] [blame]
Tarek Ziadéc3399782010-02-23 05:39:18 +00001"""Utility functions for copying and archiving files and directory trees.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +00002
Guido van Rossum959fa011999-08-18 20:03:17 +00003XXX The functions here don't copy the resource fork or other metadata on Mac.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +00004
5"""
Guido van Rossumc6360141990-10-13 19:23:40 +00006
Guido van Rossumc96207a1992-03-31 18:55:40 +00007import os
Guido van Rossum83c03e21999-02-23 23:07:51 +00008import sys
Guido van Rossum9d0a3df1997-04-29 14:45:19 +00009import stat
Brett Cannon1c3fa182004-06-19 21:11:35 +000010from os.path import abspath
Georg Brandl2ee470f2008-07-16 12:55:28 +000011import fnmatch
Tarek Ziadé396fad72010-02-23 05:30:31 +000012from warnings import warn
13import collections
Antoine Pitrou910bd512010-03-22 20:11:09 +000014import errno
Tarek Ziadé396fad72010-02-23 05:30:31 +000015
16try:
17 from pwd import getpwnam
18except ImportError:
19 getpwnam = None
20
21try:
22 from grp import getgrnam
23except ImportError:
24 getgrnam = None
Guido van Rossumc6360141990-10-13 19:23:40 +000025
Tarek Ziadéc3399782010-02-23 05:39:18 +000026__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
27 "copytree", "move", "rmtree", "Error", "SpecialFileError",
28 "ExecError", "make_archive", "get_archive_formats",
29 "register_archive_format", "unregister_archive_format"]
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000030
Neal Norwitz4ce69a52005-09-01 00:45:28 +000031class Error(EnvironmentError):
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000032 pass
Guido van Rossumc6360141990-10-13 19:23:40 +000033
Antoine Pitrou7fff0962009-05-01 21:09:44 +000034class SpecialFileError(EnvironmentError):
35 """Raised when trying to do a kind of operation (e.g. copying) which is
36 not supported on a special file (e.g. a named pipe)"""
37
Tarek Ziadé396fad72010-02-23 05:30:31 +000038class ExecError(EnvironmentError):
39 """Raised when a command could not be executed"""
40
Georg Brandl6aa2d1f2008-08-12 08:35:52 +000041try:
42 WindowsError
43except NameError:
44 WindowsError = None
45
Greg Stein42bb8b32000-07-12 09:55:30 +000046def copyfileobj(fsrc, fdst, length=16*1024):
47 """copy data from file-like object fsrc to file-like object fdst"""
48 while 1:
49 buf = fsrc.read(length)
50 if not buf:
51 break
52 fdst.write(buf)
53
Johannes Gijsbers46f14592004-08-14 13:30:02 +000054def _samefile(src, dst):
55 # Macintosh, Unix.
56 if hasattr(os.path,'samefile'):
Johannes Gijsbersf9a098e2004-08-14 14:51:01 +000057 try:
58 return os.path.samefile(src, dst)
59 except OSError:
60 return False
Johannes Gijsbers46f14592004-08-14 13:30:02 +000061
62 # All other platforms: check for same pathname.
63 return (os.path.normcase(os.path.abspath(src)) ==
64 os.path.normcase(os.path.abspath(dst)))
Tim Peters495ad3c2001-01-15 01:36:40 +000065
Guido van Rossumc6360141990-10-13 19:23:40 +000066def copyfile(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000067 """Copy data from src to dst"""
Johannes Gijsbers46f14592004-08-14 13:30:02 +000068 if _samefile(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +000069 raise Error("`%s` and `%s` are the same file" % (src, dst))
Johannes Gijsbers46f14592004-08-14 13:30:02 +000070
Guido van Rossuma2baf461997-04-29 14:06:46 +000071 fsrc = None
72 fdst = None
Antoine Pitrou7fff0962009-05-01 21:09:44 +000073 for fn in [src, dst]:
74 try:
75 st = os.stat(fn)
76 except OSError:
77 # File most likely does not exist
78 pass
Benjamin Petersonc0d98aa2009-06-05 19:13:27 +000079 else:
80 # XXX What about other special files? (sockets, devices...)
81 if stat.S_ISFIFO(st.st_mode):
82 raise SpecialFileError("`%s` is a named pipe" % fn)
Guido van Rossuma2baf461997-04-29 14:06:46 +000083 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 fsrc = open(src, 'rb')
85 fdst = open(dst, 'wb')
Greg Stein42bb8b32000-07-12 09:55:30 +000086 copyfileobj(fsrc, fdst)
Guido van Rossuma2baf461997-04-29 14:06:46 +000087 finally:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000088 if fdst:
89 fdst.close()
90 if fsrc:
91 fsrc.close()
Guido van Rossumc6360141990-10-13 19:23:40 +000092
Guido van Rossumc6360141990-10-13 19:23:40 +000093def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000094 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +000095 if hasattr(os, 'chmod'):
96 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000097 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000098 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +000099
Guido van Rossumc6360141990-10-13 19:23:40 +0000100def copystat(src, dst):
Thomas Wouterscf297e42007-02-23 15:07:44 +0000101 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +0000102 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +0000103 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +0000104 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +0000105 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +0000106 if hasattr(os, 'chmod'):
107 os.chmod(dst, mode)
Thomas Wouterscf297e42007-02-23 15:07:44 +0000108 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou910bd512010-03-22 20:11:09 +0000109 try:
110 os.chflags(dst, st.st_flags)
111 except OSError as why:
112 if not hasattr(errno, 'EOPNOTSUPP') or why.errno != errno.EOPNOTSUPP:
113 raise
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000114
Guido van Rossumc6360141990-10-13 19:23:40 +0000115def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000116 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000117
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000118 The destination may be a directory.
119
120 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000121 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000122 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000123 copyfile(src, dst)
124 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000125
Guido van Rossumc6360141990-10-13 19:23:40 +0000126def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000127 """Copy data and all stat info ("cp -p src dst").
128
129 The destination may be a directory.
130
131 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000132 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000133 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000134 copyfile(src, dst)
135 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000136
Georg Brandl2ee470f2008-07-16 12:55:28 +0000137def ignore_patterns(*patterns):
138 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000139
Georg Brandl2ee470f2008-07-16 12:55:28 +0000140 Patterns is a sequence of glob-style patterns
141 that are used to exclude files"""
142 def _ignore_patterns(path, names):
143 ignored_names = []
144 for pattern in patterns:
145 ignored_names.extend(fnmatch.filter(names, pattern))
146 return set(ignored_names)
147 return _ignore_patterns
148
149def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000150 """Recursively copy a directory tree using copy2().
151
152 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000153 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000154
155 If the optional symlinks flag is true, symbolic links in the
156 source tree result in symbolic links in the destination tree; if
157 it is false, the contents of the files pointed to by symbolic
158 links are copied.
159
Georg Brandl2ee470f2008-07-16 12:55:28 +0000160 The optional ignore argument is a callable. If given, it
161 is called with the `src` parameter, which is the directory
162 being visited by copytree(), and `names` which is the list of
163 `src` contents, as returned by os.listdir():
164
165 callable(src, names) -> ignored_names
166
167 Since copytree() is called recursively, the callable will be
168 called once for each directory that is copied. It returns a
169 list of names relative to the `src` directory that should
170 not be copied.
171
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000172 XXX Consider this example code rather than the ultimate tool.
173
174 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000175 names = os.listdir(src)
Georg Brandl2ee470f2008-07-16 12:55:28 +0000176 if ignore is not None:
177 ignored_names = ignore(src, names)
178 else:
179 ignored_names = set()
180
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000181 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000182 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000183 for name in names:
Georg Brandl2ee470f2008-07-16 12:55:28 +0000184 if name in ignored_names:
185 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000186 srcname = os.path.join(src, name)
187 dstname = os.path.join(dst, name)
188 try:
189 if symlinks and os.path.islink(srcname):
190 linkto = os.readlink(srcname)
191 os.symlink(linkto, dstname)
192 elif os.path.isdir(srcname):
Georg Brandl2ee470f2008-07-16 12:55:28 +0000193 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000194 else:
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000195 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000196 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000197 # catch the Error from the recursive copytree so that we can
198 # continue with other files
Guido van Rossumb940e112007-01-10 16:19:56 +0000199 except Error as err:
Georg Brandla1be88e2005-08-31 22:48:45 +0000200 errors.extend(err.args[0])
Antoine Pitrou7fff0962009-05-01 21:09:44 +0000201 except EnvironmentError as why:
202 errors.append((srcname, dstname, str(why)))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000203 try:
204 copystat(src, dst)
Guido van Rossumb940e112007-01-10 16:19:56 +0000205 except OSError as why:
Georg Brandl6aa2d1f2008-08-12 08:35:52 +0000206 if WindowsError is not None and isinstance(why, WindowsError):
207 # Copying file access times may fail on Windows
208 pass
209 else:
210 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000211 if errors:
Collin Winterce36ad82007-08-30 01:19:48 +0000212 raise Error(errors)
Guido van Rossumd7673291998-02-06 21:38:09 +0000213
Barry Warsaw234d9a92003-01-24 17:36:15 +0000214def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000215 """Recursively delete a directory tree.
216
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000217 If ignore_errors is set, errors are ignored; otherwise, if onerror
218 is set, it is called to handle the error with arguments (func,
219 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
220 path is the argument to that function that caused it to fail; and
221 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
222 is false and onerror is None, an exception is raised.
223
Guido van Rossumd7673291998-02-06 21:38:09 +0000224 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000225 if ignore_errors:
226 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000227 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000228 elif onerror is None:
229 def onerror(*args):
230 raise
Christian Heimes9bd667a2008-01-20 15:14:11 +0000231 try:
232 if os.path.islink(path):
233 # symlinks to directories are forbidden, see bug #1669
234 raise OSError("Cannot call rmtree on a symbolic link")
235 except OSError:
236 onerror(os.path.islink, path, sys.exc_info())
237 # can't continue even if onerror hook returns
238 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000239 names = []
240 try:
241 names = os.listdir(path)
Guido van Rossumb940e112007-01-10 16:19:56 +0000242 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000243 onerror(os.listdir, path, sys.exc_info())
244 for name in names:
245 fullname = os.path.join(path, name)
246 try:
247 mode = os.lstat(fullname).st_mode
248 except os.error:
249 mode = 0
250 if stat.S_ISDIR(mode):
251 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000252 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000253 try:
254 os.remove(fullname)
Guido van Rossumb940e112007-01-10 16:19:56 +0000255 except os.error as err:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000256 onerror(os.remove, fullname, sys.exc_info())
257 try:
258 os.rmdir(path)
259 except os.error:
260 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000261
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000262
Christian Heimesada8c3b2008-03-18 18:26:33 +0000263def _basename(path):
264 # A basename() variant which first strips the trailing slash, if present.
265 # Thus we always get the last component of the path, even for directories.
266 return os.path.basename(path.rstrip(os.path.sep))
267
268def move(src, dst):
269 """Recursively move a file or directory to another location. This is
270 similar to the Unix "mv" command.
271
272 If the destination is a directory or a symlink to a directory, the source
273 is moved inside the directory. The destination path must not already
274 exist.
275
276 If the destination already exists but is not a directory, it may be
277 overwritten depending on os.rename() semantics.
278
279 If the destination is on our current filesystem, then rename() is used.
280 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000281 A lot more could be done here... A look at a mv.c shows a lot of
282 the issues this implementation glosses over.
283
284 """
Christian Heimesada8c3b2008-03-18 18:26:33 +0000285 real_dst = dst
286 if os.path.isdir(dst):
287 real_dst = os.path.join(dst, _basename(src))
288 if os.path.exists(real_dst):
289 raise Error("Destination path '%s' already exists" % real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000290 try:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000291 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000292 except OSError:
293 if os.path.isdir(src):
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000294 if _destinsrc(src, dst):
Collin Winterce36ad82007-08-30 01:19:48 +0000295 raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
Christian Heimesada8c3b2008-03-18 18:26:33 +0000296 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000297 rmtree(src)
298 else:
Christian Heimesada8c3b2008-03-18 18:26:33 +0000299 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000300 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000301
Benjamin Peterson247a9b82009-02-20 04:09:19 +0000302def _destinsrc(src, dst):
Antoine Pitrou0dcc3cd2009-01-29 20:26:59 +0000303 src = abspath(src)
304 dst = abspath(dst)
305 if not src.endswith(os.path.sep):
306 src += os.path.sep
307 if not dst.endswith(os.path.sep):
308 dst += os.path.sep
309 return dst.startswith(src)
Tarek Ziadé396fad72010-02-23 05:30:31 +0000310
311def _get_gid(name):
312 """Returns a gid, given a group name."""
313 if getgrnam is None or name is None:
314 return None
315 try:
316 result = getgrnam(name)
317 except KeyError:
318 result = None
319 if result is not None:
320 return result[2]
321 return None
322
323def _get_uid(name):
324 """Returns an uid, given a user name."""
325 if getpwnam is None or name is None:
326 return None
327 try:
328 result = getpwnam(name)
329 except KeyError:
330 result = None
331 if result is not None:
332 return result[2]
333 return None
334
335def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
336 owner=None, group=None, logger=None):
337 """Create a (possibly compressed) tar file from all the files under
338 'base_dir'.
339
340 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
341 (compress will be deprecated in Python 3.2)
342
343 'owner' and 'group' can be used to define an owner and a group for the
344 archive that is being built. If not provided, the current owner and group
345 will be used.
346
347 The output tar file will be named 'base_dir' + ".tar", possibly plus
348 the appropriate compression extension (".gz", ".bz2" or ".Z").
349
350 Returns the output filename.
351 """
352 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
353 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'}
354
355 # flags for compression program, each element of list will be an argument
356 if compress is not None and compress not in compress_ext.keys():
357 raise ValueError("bad value for 'compress': must be None, 'gzip', "
358 "'bzip2' or 'compress'")
359
360 archive_name = base_name + '.tar'
361 if compress != 'compress':
362 archive_name += compress_ext.get(compress, '')
363
364 archive_dir = os.path.dirname(archive_name)
365 if not os.path.exists(archive_dir):
366 logger.info("creating %s" % archive_dir)
367 if not dry_run:
368 os.makedirs(archive_dir)
369
370
371 # creating the tarball
372 import tarfile # late import so Python build itself doesn't break
373
374 if logger is not None:
375 logger.info('Creating tar archive')
376
377 uid = _get_uid(owner)
378 gid = _get_gid(group)
379
380 def _set_uid_gid(tarinfo):
381 if gid is not None:
382 tarinfo.gid = gid
383 tarinfo.gname = group
384 if uid is not None:
385 tarinfo.uid = uid
386 tarinfo.uname = owner
387 return tarinfo
388
389 if not dry_run:
390 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
391 try:
392 tar.add(base_dir, filter=_set_uid_gid)
393 finally:
394 tar.close()
395
396 # compression using `compress`
397 # XXX this block will be removed in Python 3.2
398 if compress == 'compress':
399 warn("'compress' will be deprecated.", PendingDeprecationWarning)
400 # the option varies depending on the platform
401 compressed_name = archive_name + compress_ext[compress]
402 if sys.platform == 'win32':
403 cmd = [compress, archive_name, compressed_name]
404 else:
405 cmd = [compress, '-f', archive_name]
406 from distutils.spawn import spawn
407 spawn(cmd, dry_run=dry_run)
408 return compressed_name
409
410 return archive_name
411
412def _call_external_zip(directory, verbose=False):
413 # XXX see if we want to keep an external call here
414 if verbose:
415 zipoptions = "-r"
416 else:
417 zipoptions = "-rq"
418 from distutils.errors import DistutilsExecError
419 from distutils.spawn import spawn
420 try:
421 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
422 except DistutilsExecError:
423 # XXX really should distinguish between "couldn't find
424 # external 'zip' command" and "zip failed".
425 raise ExecError("unable to create zip file '%s': "
426 "could neither import the 'zipfile' module nor "
427 "find a standalone zip utility") % zip_filename
428
429def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
430 """Create a zip file from all the files under 'base_dir'.
431
432 The output zip file will be named 'base_dir' + ".zip". Uses either the
433 "zipfile" Python module (if available) or the InfoZIP "zip" utility
434 (if installed and found on the default search path). If neither tool is
435 available, raises ExecError. Returns the name of the output zip
436 file.
437 """
438 zip_filename = base_name + ".zip"
439 archive_dir = os.path.dirname(base_name)
440
441 if not os.path.exists(archive_dir):
442 if logger is not None:
443 logger.info("creating %s", archive_dir)
444 if not dry_run:
445 os.makedirs(archive_dir)
446
447 # If zipfile module is not available, try spawning an external 'zip'
448 # command.
449 try:
450 import zipfile
451 except ImportError:
452 zipfile = None
453
454 if zipfile is None:
455 _call_external_zip(base_dir, verbose)
456 else:
457 if logger is not None:
458 logger.info("creating '%s' and adding '%s' to it",
459 zip_filename, base_dir)
460
461 if not dry_run:
462 zip = zipfile.ZipFile(zip_filename, "w",
463 compression=zipfile.ZIP_DEFLATED)
464
465 for dirpath, dirnames, filenames in os.walk(base_dir):
466 for name in filenames:
467 path = os.path.normpath(os.path.join(dirpath, name))
468 if os.path.isfile(path):
469 zip.write(path, path)
470 if logger is not None:
471 logger.info("adding '%s'", path)
472 zip.close()
473
474 return zip_filename
475
476_ARCHIVE_FORMATS = {
477 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
478 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
479 'ztar': (_make_tarball, [('compress', 'compress')],
480 "compressed tar file"),
481 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
482 'zip': (_make_zipfile, [],"ZIP file")
483 }
484
485def get_archive_formats():
486 """Returns a list of supported formats for archiving and unarchiving.
487
488 Each element of the returned sequence is a tuple (name, description)
489 """
490 formats = [(name, registry[2]) for name, registry in
491 _ARCHIVE_FORMATS.items()]
492 formats.sort()
493 return formats
494
495def register_archive_format(name, function, extra_args=None, description=''):
496 """Registers an archive format.
497
498 name is the name of the format. function is the callable that will be
499 used to create archives. If provided, extra_args is a sequence of
500 (name, value) tuples that will be passed as arguments to the callable.
501 description can be provided to describe the format, and will be returned
502 by the get_archive_formats() function.
503 """
504 if extra_args is None:
505 extra_args = []
506 if not isinstance(function, collections.Callable):
507 raise TypeError('The %s object is not callable' % function)
508 if not isinstance(extra_args, (tuple, list)):
509 raise TypeError('extra_args needs to be a sequence')
510 for element in extra_args:
511 if not isinstance(element, (tuple, list)) or len(element) !=2 :
512 raise TypeError('extra_args elements are : (arg_name, value)')
513
514 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
515
516def unregister_archive_format(name):
517 del _ARCHIVE_FORMATS[name]
518
519def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
520 dry_run=0, owner=None, group=None, logger=None):
521 """Create an archive file (eg. zip or tar).
522
523 'base_name' is the name of the file to create, minus any format-specific
524 extension; 'format' is the archive format: one of "zip", "tar", "ztar",
525 or "gztar".
526
527 'root_dir' is a directory that will be the root directory of the
528 archive; ie. we typically chdir into 'root_dir' before creating the
529 archive. 'base_dir' is the directory where we start archiving from;
530 ie. 'base_dir' will be the common prefix of all files and
531 directories in the archive. 'root_dir' and 'base_dir' both default
532 to the current directory. Returns the name of the archive file.
533
534 'owner' and 'group' are used when creating a tar archive. By default,
535 uses the current owner and group.
536 """
537 save_cwd = os.getcwd()
538 if root_dir is not None:
539 if logger is not None:
540 logger.debug("changing into '%s'", root_dir)
541 base_name = os.path.abspath(base_name)
542 if not dry_run:
543 os.chdir(root_dir)
544
545 if base_dir is None:
546 base_dir = os.curdir
547
548 kwargs = {'dry_run': dry_run, 'logger': logger}
549
550 try:
551 format_info = _ARCHIVE_FORMATS[format]
552 except KeyError:
553 raise ValueError("unknown archive format '%s'" % format)
554
555 func = format_info[0]
556 for arg, val in format_info[1]:
557 kwargs[arg] = val
558
559 if format != 'zip':
560 kwargs['owner'] = owner
561 kwargs['group'] = group
562
563 try:
564 filename = func(base_name, base_dir, **kwargs)
565 finally:
566 if root_dir is not None:
567 if logger is not None:
568 logger.debug("changing back to '%s'", save_cwd)
569 os.chdir(save_cwd)
570
571 return filename