blob: f02a2d9e7069c9c9fe855e732ac7f57e75405760 [file] [log] [blame]
Tarek Ziadé2900c442010-02-23 05:36:41 +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 Brandle78fbcc2008-07-05 10:13:36 +000011import fnmatch
Florent Xicluna1f3b4e12010-03-07 12:14:25 +000012import collections
Antoine Pitrou513d9ae2010-03-22 19:59:46 +000013import errno
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +000014
15try:
16 from pwd import getpwnam
17except ImportError:
18 getpwnam = None
19
20try:
21 from grp import getgrnam
22except ImportError:
23 getgrnam = None
Guido van Rossumc6360141990-10-13 19:23:40 +000024
Tarek Ziadé2900c442010-02-23 05:36:41 +000025__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
26 "copytree", "move", "rmtree", "Error", "SpecialFileError",
27 "ExecError", "make_archive", "get_archive_formats",
28 "register_archive_format", "unregister_archive_format"]
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000029
Neal Norwitz4ce69a52005-09-01 00:45:28 +000030class Error(EnvironmentError):
Martin v. Löwise9ce0b02002-10-07 13:23:24 +000031 pass
Guido van Rossumc6360141990-10-13 19:23:40 +000032
Antoine Pitrou1fc02312009-05-01 20:55:35 +000033class SpecialFileError(EnvironmentError):
34 """Raised when trying to do a kind of operation (e.g. copying) which is
35 not supported on a special file (e.g. a named pipe)"""
36
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +000037class ExecError(EnvironmentError):
38 """Raised when a command could not be executed"""
39
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +000040try:
41 WindowsError
42except NameError:
43 WindowsError = None
44
Greg Stein42bb8b32000-07-12 09:55:30 +000045def copyfileobj(fsrc, fdst, length=16*1024):
46 """copy data from file-like object fsrc to file-like object fdst"""
47 while 1:
48 buf = fsrc.read(length)
49 if not buf:
50 break
51 fdst.write(buf)
52
Johannes Gijsbers46f14592004-08-14 13:30:02 +000053def _samefile(src, dst):
54 # Macintosh, Unix.
Tarek Ziadéf1c28b72010-04-19 21:13:03 +000055 if hasattr(os.path, 'samefile'):
Johannes Gijsbersf9a098e2004-08-14 14:51:01 +000056 try:
57 return os.path.samefile(src, dst)
58 except OSError:
59 return False
Johannes Gijsbers46f14592004-08-14 13:30:02 +000060
61 # All other platforms: check for same pathname.
62 return (os.path.normcase(os.path.abspath(src)) ==
63 os.path.normcase(os.path.abspath(dst)))
Tim Peters495ad3c2001-01-15 01:36:40 +000064
Guido van Rossumc6360141990-10-13 19:23:40 +000065def copyfile(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000066 """Copy data from src to dst"""
Johannes Gijsbers46f14592004-08-14 13:30:02 +000067 if _samefile(src, dst):
Tarek Ziadéf1c28b72010-04-19 21:13:03 +000068 raise Error("`%s` and `%s` are the same file" % (src, dst))
Johannes Gijsbers46f14592004-08-14 13:30:02 +000069
Guido van Rossuma2baf461997-04-29 14:06:46 +000070 fsrc = None
71 fdst = None
Antoine Pitrou1fc02312009-05-01 20:55:35 +000072 for fn in [src, dst]:
73 try:
74 st = os.stat(fn)
75 except OSError:
76 # File most likely does not exist
77 pass
Benjamin Petersona663a372009-06-05 19:09:28 +000078 else:
79 # XXX What about other special files? (sockets, devices...)
80 if stat.S_ISFIFO(st.st_mode):
81 raise SpecialFileError("`%s` is a named pipe" % fn)
Tarek Ziadé38f81222010-05-05 22:15:31 +000082 with open(src, 'rb') as fsrc:
83 with open(dst, 'wb') as fdst:
84 copyfileobj(fsrc, fdst)
Guido van Rossumc6360141990-10-13 19:23:40 +000085
Guido van Rossumc6360141990-10-13 19:23:40 +000086def copymode(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +000087 """Copy mode bits from src to dst"""
Tim Peters0c947242001-01-21 20:00:00 +000088 if hasattr(os, 'chmod'):
89 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000090 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000091 os.chmod(dst, mode)
Guido van Rossumc6360141990-10-13 19:23:40 +000092
Guido van Rossumc6360141990-10-13 19:23:40 +000093def copystat(src, dst):
Martin v. Löwis382abef2007-02-19 10:55:19 +000094 """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
Guido van Rossuma2baf461997-04-29 14:06:46 +000095 st = os.stat(src)
Walter Dörwald294bbf32002-06-06 09:48:13 +000096 mode = stat.S_IMODE(st.st_mode)
Tim Peters0c947242001-01-21 20:00:00 +000097 if hasattr(os, 'utime'):
Walter Dörwald294bbf32002-06-06 09:48:13 +000098 os.utime(dst, (st.st_atime, st.st_mtime))
Tim Peters0c947242001-01-21 20:00:00 +000099 if hasattr(os, 'chmod'):
100 os.chmod(dst, mode)
Martin v. Löwis382abef2007-02-19 10:55:19 +0000101 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000102 try:
103 os.chflags(dst, st.st_flags)
104 except OSError, why:
Tarek Ziadéf1c28b72010-04-19 21:13:03 +0000105 if (not hasattr(errno, 'EOPNOTSUPP') or
106 why.errno != errno.EOPNOTSUPP):
Antoine Pitrou513d9ae2010-03-22 19:59:46 +0000107 raise
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000108
Guido van Rossumc6360141990-10-13 19:23:40 +0000109def copy(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000110 """Copy data and mode bits ("cp src dst").
Tim Peters495ad3c2001-01-15 01:36:40 +0000111
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000112 The destination may be a directory.
113
114 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000115 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000116 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000117 copyfile(src, dst)
118 copymode(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000119
Guido van Rossumc6360141990-10-13 19:23:40 +0000120def copy2(src, dst):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000121 """Copy data and all stat info ("cp -p src dst").
122
123 The destination may be a directory.
124
125 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000126 if os.path.isdir(dst):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000127 dst = os.path.join(dst, os.path.basename(src))
Guido van Rossuma2baf461997-04-29 14:06:46 +0000128 copyfile(src, dst)
129 copystat(src, dst)
Guido van Rossumc6360141990-10-13 19:23:40 +0000130
Georg Brandle78fbcc2008-07-05 10:13:36 +0000131def ignore_patterns(*patterns):
132 """Function that can be used as copytree() ignore parameter.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000133
Georg Brandle78fbcc2008-07-05 10:13:36 +0000134 Patterns is a sequence of glob-style patterns
135 that are used to exclude files"""
136 def _ignore_patterns(path, names):
137 ignored_names = []
138 for pattern in patterns:
139 ignored_names.extend(fnmatch.filter(names, pattern))
140 return set(ignored_names)
141 return _ignore_patterns
142
143def copytree(src, dst, symlinks=False, ignore=None):
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000144 """Recursively copy a directory tree using copy2().
145
146 The destination directory must not already exist.
Neal Norwitza4c93b62003-02-23 21:36:32 +0000147 If exception(s) occur, an Error is raised with a list of reasons.
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000148
149 If the optional symlinks flag is true, symbolic links in the
150 source tree result in symbolic links in the destination tree; if
151 it is false, the contents of the files pointed to by symbolic
152 links are copied.
153
Georg Brandle78fbcc2008-07-05 10:13:36 +0000154 The optional ignore argument is a callable. If given, it
155 is called with the `src` parameter, which is the directory
156 being visited by copytree(), and `names` which is the list of
157 `src` contents, as returned by os.listdir():
158
159 callable(src, names) -> ignored_names
160
161 Since copytree() is called recursively, the callable will be
162 called once for each directory that is copied. It returns a
163 list of names relative to the `src` directory that should
164 not be copied.
165
Guido van Rossum9d0a3df1997-04-29 14:45:19 +0000166 XXX Consider this example code rather than the ultimate tool.
167
168 """
Guido van Rossuma2baf461997-04-29 14:06:46 +0000169 names = os.listdir(src)
Georg Brandle78fbcc2008-07-05 10:13:36 +0000170 if ignore is not None:
171 ignored_names = ignore(src, names)
172 else:
173 ignored_names = set()
174
Johannes Gijsberse4172ea2005-01-08 12:31:29 +0000175 os.makedirs(dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000176 errors = []
Guido van Rossuma2baf461997-04-29 14:06:46 +0000177 for name in names:
Georg Brandle78fbcc2008-07-05 10:13:36 +0000178 if name in ignored_names:
179 continue
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000180 srcname = os.path.join(src, name)
181 dstname = os.path.join(dst, name)
182 try:
183 if symlinks and os.path.islink(srcname):
184 linkto = os.readlink(srcname)
185 os.symlink(linkto, dstname)
186 elif os.path.isdir(srcname):
Georg Brandle78fbcc2008-07-05 10:13:36 +0000187 copytree(srcname, dstname, symlinks, ignore)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000188 else:
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000189 # Will raise a SpecialFileError for unsupported file types
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000190 copy2(srcname, dstname)
Georg Brandla1be88e2005-08-31 22:48:45 +0000191 # catch the Error from the recursive copytree so that we can
192 # continue with other files
193 except Error, err:
194 errors.extend(err.args[0])
Antoine Pitrou1fc02312009-05-01 20:55:35 +0000195 except EnvironmentError, why:
196 errors.append((srcname, dstname, str(why)))
Martin v. Löwis4e678382006-07-30 13:00:31 +0000197 try:
198 copystat(src, dst)
Martin v. Löwis4e678382006-07-30 13:00:31 +0000199 except OSError, why:
Antoine Pitrou9fcd4b32008-08-11 17:21:36 +0000200 if WindowsError is not None and isinstance(why, WindowsError):
201 # Copying file access times may fail on Windows
202 pass
203 else:
204 errors.extend((src, dst, str(why)))
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000205 if errors:
206 raise Error, errors
Guido van Rossumd7673291998-02-06 21:38:09 +0000207
Barry Warsaw234d9a92003-01-24 17:36:15 +0000208def rmtree(path, ignore_errors=False, onerror=None):
Guido van Rossumd7673291998-02-06 21:38:09 +0000209 """Recursively delete a directory tree.
210
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000211 If ignore_errors is set, errors are ignored; otherwise, if onerror
212 is set, it is called to handle the error with arguments (func,
213 path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
214 path is the argument to that function that caused it to fail; and
215 exc_info is a tuple returned by sys.exc_info(). If ignore_errors
216 is false and onerror is None, an exception is raised.
217
Guido van Rossumd7673291998-02-06 21:38:09 +0000218 """
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000219 if ignore_errors:
220 def onerror(*args):
Barry Warsaw234d9a92003-01-24 17:36:15 +0000221 pass
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000222 elif onerror is None:
223 def onerror(*args):
224 raise
Georg Brandl52353982008-01-20 14:17:42 +0000225 try:
226 if os.path.islink(path):
227 # symlinks to directories are forbidden, see bug #1669
228 raise OSError("Cannot call rmtree on a symbolic link")
229 except OSError:
230 onerror(os.path.islink, path, sys.exc_info())
231 # can't continue even if onerror hook returns
232 return
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000233 names = []
234 try:
235 names = os.listdir(path)
236 except os.error, err:
237 onerror(os.listdir, path, sys.exc_info())
238 for name in names:
239 fullname = os.path.join(path, name)
240 try:
241 mode = os.lstat(fullname).st_mode
242 except os.error:
243 mode = 0
244 if stat.S_ISDIR(mode):
245 rmtree(fullname, ignore_errors, onerror)
Barry Warsaw234d9a92003-01-24 17:36:15 +0000246 else:
Johannes Gijsbersef5ffc42004-10-31 12:05:31 +0000247 try:
248 os.remove(fullname)
249 except os.error, err:
250 onerror(os.remove, fullname, sys.exc_info())
251 try:
252 os.rmdir(path)
253 except os.error:
254 onerror(os.rmdir, path, sys.exc_info())
Guido van Rossumd7673291998-02-06 21:38:09 +0000255
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000256
Sean Reifscheider493894c2008-03-18 17:24:12 +0000257def _basename(path):
258 # A basename() variant which first strips the trailing slash, if present.
259 # Thus we always get the last component of the path, even for directories.
260 return os.path.basename(path.rstrip(os.path.sep))
261
262def move(src, dst):
263 """Recursively move a file or directory to another location. This is
264 similar to the Unix "mv" command.
265
266 If the destination is a directory or a symlink to a directory, the source
267 is moved inside the directory. The destination path must not already
268 exist.
269
270 If the destination already exists but is not a directory, it may be
271 overwritten depending on os.rename() semantics.
272
273 If the destination is on our current filesystem, then rename() is used.
274 Otherwise, src is copied to the destination and then removed.
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000275 A lot more could be done here... A look at a mv.c shows a lot of
276 the issues this implementation glosses over.
277
278 """
Sean Reifscheider493894c2008-03-18 17:24:12 +0000279 real_dst = dst
280 if os.path.isdir(dst):
281 real_dst = os.path.join(dst, _basename(src))
282 if os.path.exists(real_dst):
283 raise Error, "Destination path '%s' already exists" % real_dst
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000284 try:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000285 os.rename(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000286 except OSError:
287 if os.path.isdir(src):
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000288 if _destinsrc(src, dst):
Brett Cannon1c3fa182004-06-19 21:11:35 +0000289 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
Sean Reifscheider493894c2008-03-18 17:24:12 +0000290 copytree(src, real_dst, symlinks=True)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000291 rmtree(src)
292 else:
Sean Reifscheider493894c2008-03-18 17:24:12 +0000293 copy2(src, real_dst)
Martin v. Löwise9ce0b02002-10-07 13:23:24 +0000294 os.unlink(src)
Brett Cannon1c3fa182004-06-19 21:11:35 +0000295
Benjamin Peterson096c3ad2009-02-07 19:08:22 +0000296def _destinsrc(src, dst):
Antoine Pitrou707c5932009-01-29 20:19:34 +0000297 src = abspath(src)
298 dst = abspath(dst)
299 if not src.endswith(os.path.sep):
300 src += os.path.sep
301 if not dst.endswith(os.path.sep):
302 dst += os.path.sep
303 return dst.startswith(src)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000304
305def _get_gid(name):
306 """Returns a gid, given a group name."""
307 if getgrnam is None or name is None:
308 return None
309 try:
310 result = getgrnam(name)
311 except KeyError:
312 result = None
313 if result is not None:
314 return result[2]
315 return None
316
317def _get_uid(name):
318 """Returns an uid, given a user name."""
319 if getpwnam is None or name is None:
320 return None
321 try:
322 result = getpwnam(name)
323 except KeyError:
324 result = None
325 if result is not None:
326 return result[2]
327 return None
328
329def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
330 owner=None, group=None, logger=None):
331 """Create a (possibly compressed) tar file from all the files under
332 'base_dir'.
333
Tarek Ziadée593fad2010-04-20 21:09:06 +0000334 'compress' must be "gzip" (the default), "bzip2", or None.
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000335
336 'owner' and 'group' can be used to define an owner and a group for the
337 archive that is being built. If not provided, the current owner and group
338 will be used.
339
340 The output tar file will be named 'base_dir' + ".tar", possibly plus
Tarek Ziadée593fad2010-04-20 21:09:06 +0000341 the appropriate compression extension (".gz", or ".bz2").
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000342
343 Returns the output filename.
344 """
Tarek Ziadée593fad2010-04-20 21:09:06 +0000345 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''}
346 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'}
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000347
348 # flags for compression program, each element of list will be an argument
349 if compress is not None and compress not in compress_ext.keys():
350 raise ValueError, \
Tarek Ziadée593fad2010-04-20 21:09:06 +0000351 ("bad value for 'compress': must be None, 'gzip' or 'bzip2'")
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000352
Tarek Ziadée593fad2010-04-20 21:09:06 +0000353 archive_name = base_name + '.tar' + compress_ext.get(compress, '')
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000354 archive_dir = os.path.dirname(archive_name)
Tarek Ziadée593fad2010-04-20 21:09:06 +0000355
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000356 if not os.path.exists(archive_dir):
357 logger.info("creating %s" % archive_dir)
358 if not dry_run:
359 os.makedirs(archive_dir)
360
361
362 # creating the tarball
363 import tarfile # late import so Python build itself doesn't break
364
365 if logger is not None:
366 logger.info('Creating tar archive')
367
368 uid = _get_uid(owner)
369 gid = _get_gid(group)
370
371 def _set_uid_gid(tarinfo):
372 if gid is not None:
373 tarinfo.gid = gid
374 tarinfo.gname = group
375 if uid is not None:
376 tarinfo.uid = uid
377 tarinfo.uname = owner
378 return tarinfo
379
380 if not dry_run:
381 tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
382 try:
383 tar.add(base_dir, filter=_set_uid_gid)
384 finally:
385 tar.close()
386
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000387 return archive_name
388
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000389def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000390 # XXX see if we want to keep an external call here
391 if verbose:
392 zipoptions = "-r"
393 else:
394 zipoptions = "-rq"
395 from distutils.errors import DistutilsExecError
396 from distutils.spawn import spawn
397 try:
398 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
399 except DistutilsExecError:
400 # XXX really should distinguish between "couldn't find
401 # external 'zip' command" and "zip failed".
402 raise ExecError, \
403 ("unable to create zip file '%s': "
404 "could neither import the 'zipfile' module nor "
405 "find a standalone zip utility") % zip_filename
406
407def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
408 """Create a zip file from all the files under 'base_dir'.
409
410 The output zip file will be named 'base_dir' + ".zip". Uses either the
411 "zipfile" Python module (if available) or the InfoZIP "zip" utility
412 (if installed and found on the default search path). If neither tool is
413 available, raises ExecError. Returns the name of the output zip
414 file.
415 """
416 zip_filename = base_name + ".zip"
417 archive_dir = os.path.dirname(base_name)
418
419 if not os.path.exists(archive_dir):
420 if logger is not None:
421 logger.info("creating %s", archive_dir)
422 if not dry_run:
423 os.makedirs(archive_dir)
424
425 # If zipfile module is not available, try spawning an external 'zip'
426 # command.
427 try:
428 import zipfile
429 except ImportError:
430 zipfile = None
431
432 if zipfile is None:
Tarek Ziadé62e17ad2010-04-21 13:32:26 +0000433 _call_external_zip(base_dir, zip_filename, verbose, dry_run)
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000434 else:
435 if logger is not None:
436 logger.info("creating '%s' and adding '%s' to it",
437 zip_filename, base_dir)
438
439 if not dry_run:
440 zip = zipfile.ZipFile(zip_filename, "w",
441 compression=zipfile.ZIP_DEFLATED)
442
443 for dirpath, dirnames, filenames in os.walk(base_dir):
444 for name in filenames:
445 path = os.path.normpath(os.path.join(dirpath, name))
446 if os.path.isfile(path):
447 zip.write(path, path)
448 if logger is not None:
449 logger.info("adding '%s'", path)
450 zip.close()
451
452 return zip_filename
453
454_ARCHIVE_FORMATS = {
455 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
456 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000457 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
458 'zip': (_make_zipfile, [],"ZIP file")
459 }
460
461def get_archive_formats():
462 """Returns a list of supported formats for archiving and unarchiving.
463
464 Each element of the returned sequence is a tuple (name, description)
465 """
466 formats = [(name, registry[2]) for name, registry in
467 _ARCHIVE_FORMATS.items()]
468 formats.sort()
469 return formats
470
471def register_archive_format(name, function, extra_args=None, description=''):
472 """Registers an archive format.
473
474 name is the name of the format. function is the callable that will be
475 used to create archives. If provided, extra_args is a sequence of
476 (name, value) tuples that will be passed as arguments to the callable.
477 description can be provided to describe the format, and will be returned
478 by the get_archive_formats() function.
479 """
480 if extra_args is None:
481 extra_args = []
Florent Xicluna1f3b4e12010-03-07 12:14:25 +0000482 if not isinstance(function, collections.Callable):
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000483 raise TypeError('The %s object is not callable' % function)
484 if not isinstance(extra_args, (tuple, list)):
485 raise TypeError('extra_args needs to be a sequence')
486 for element in extra_args:
487 if not isinstance(element, (tuple, list)) or len(element) !=2 :
488 raise TypeError('extra_args elements are : (arg_name, value)')
489
490 _ARCHIVE_FORMATS[name] = (function, extra_args, description)
491
492def unregister_archive_format(name):
493 del _ARCHIVE_FORMATS[name]
494
495def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
496 dry_run=0, owner=None, group=None, logger=None):
497 """Create an archive file (eg. zip or tar).
498
499 'base_name' is the name of the file to create, minus any format-specific
Tarek Ziadée593fad2010-04-20 21:09:06 +0000500 extension; 'format' is the archive format: one of "zip", "tar", "bztar"
501 or "gztar".
Tarek Ziadé48cc8dc2010-02-23 05:16:41 +0000502
503 'root_dir' is a directory that will be the root directory of the
504 archive; ie. we typically chdir into 'root_dir' before creating the
505 archive. 'base_dir' is the directory where we start archiving from;
506 ie. 'base_dir' will be the common prefix of all files and
507 directories in the archive. 'root_dir' and 'base_dir' both default
508 to the current directory. Returns the name of the archive file.
509
510 'owner' and 'group' are used when creating a tar archive. By default,
511 uses the current owner and group.
512 """
513 save_cwd = os.getcwd()
514 if root_dir is not None:
515 if logger is not None:
516 logger.debug("changing into '%s'", root_dir)
517 base_name = os.path.abspath(base_name)
518 if not dry_run:
519 os.chdir(root_dir)
520
521 if base_dir is None:
522 base_dir = os.curdir
523
524 kwargs = {'dry_run': dry_run, 'logger': logger}
525
526 try:
527 format_info = _ARCHIVE_FORMATS[format]
528 except KeyError:
529 raise ValueError, "unknown archive format '%s'" % format
530
531 func = format_info[0]
532 for arg, val in format_info[1]:
533 kwargs[arg] = val
534
535 if format != 'zip':
536 kwargs['owner'] = owner
537 kwargs['group'] = group
538
539 try:
540 filename = func(base_name, base_dir, **kwargs)
541 finally:
542 if root_dir is not None:
543 if logger is not None:
544 logger.debug("changing back to '%s'", save_cwd)
545 os.chdir(save_cwd)
546
547 return filename