Changeset 388 for python/vendor/current/Lib/shutil.py
- Timestamp:
- Mar 19, 2014, 11:11:30 AM (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
python/vendor/current/Lib/shutil.py
r2 r388 1 """Utility functions for copying files and directory trees.1 """Utility functions for copying and archiving files and directory trees. 2 2 3 3 XXX The functions here don't copy the resource fork or other metadata on Mac. … … 10 10 from os.path import abspath 11 11 import fnmatch 12 13 __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2", 14 "copytree","move","rmtree","Error"] 12 import collections 13 import errno 14 15 try: 16 from pwd import getpwnam 17 except ImportError: 18 getpwnam = None 19 20 try: 21 from grp import getgrnam 22 except ImportError: 23 getgrnam = None 24 25 __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", 29 "ignore_patterns"] 15 30 16 31 class Error(EnvironmentError): 17 32 pass 33 34 class 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 38 class ExecError(EnvironmentError): 39 """Raised when a command could not be executed""" 18 40 19 41 try: … … 32 54 def _samefile(src, dst): 33 55 # Macintosh, Unix. 34 if hasattr(os.path, 'samefile'):56 if hasattr(os.path, 'samefile'): 35 57 try: 36 58 return os.path.samefile(src, dst) … … 45 67 """Copy data from src to dst""" 46 68 if _samefile(src, dst): 47 raise Error, "`%s` and `%s` are the same file" % (src, dst) 48 49 fsrc = None 50 fdst = None 51 try: 52 fsrc = open(src, 'rb') 53 fdst = open(dst, 'wb') 54 copyfileobj(fsrc, fdst) 55 finally: 56 if fdst: 57 fdst.close() 58 if fsrc: 59 fsrc.close() 69 raise Error("`%s` and `%s` are the same file" % (src, dst)) 70 71 for fn in [src, dst]: 72 try: 73 st = os.stat(fn) 74 except OSError: 75 # File most likely does not exist 76 pass 77 else: 78 # XXX What about other special files? (sockets, devices...) 79 if stat.S_ISFIFO(st.st_mode): 80 raise SpecialFileError("`%s` is a named pipe" % fn) 81 82 with open(src, 'rb') as fsrc: 83 with open(dst, 'wb') as fdst: 84 copyfileobj(fsrc, fdst) 60 85 61 86 def copymode(src, dst): … … 75 100 os.chmod(dst, mode) 76 101 if hasattr(os, 'chflags') and hasattr(st, 'st_flags'): 77 os.chflags(dst, st.st_flags) 78 102 try: 103 os.chflags(dst, st.st_flags) 104 except OSError, why: 105 for err in 'EOPNOTSUPP', 'ENOTSUP': 106 if hasattr(errno, err) and why.errno == getattr(errno, err): 107 break 108 else: 109 raise 79 110 80 111 def copy(src, dst): … … 158 189 copytree(srcname, dstname, symlinks, ignore) 159 190 else: 191 # Will raise a SpecialFileError for unsupported file types 160 192 copy2(srcname, dstname) 161 # XXX What about devices, sockets etc.?162 except (IOError, os.error), why:163 errors.append((srcname, dstname, str(why)))164 193 # catch the Error from the recursive copytree so that we can 165 194 # continue with other files 166 195 except Error, err: 167 196 errors.extend(err.args[0]) 197 except EnvironmentError, why: 198 errors.append((srcname, dstname, str(why))) 168 199 try: 169 200 copystat(src, dst) … … 173 204 pass 174 205 else: 175 errors. extend((src, dst, str(why)))206 errors.append((src, dst, str(why))) 176 207 if errors: 177 208 raise Error, errors … … 250 281 real_dst = dst 251 282 if os.path.isdir(dst): 283 if _samefile(src, dst): 284 # We might be on a case insensitive filesystem, 285 # perform the rename anyway. 286 os.rename(src, dst) 287 return 288 252 289 real_dst = os.path.join(dst, _basename(src)) 253 290 if os.path.exists(real_dst): … … 257 294 except OSError: 258 295 if os.path.isdir(src): 259 if destinsrc(src, dst):296 if _destinsrc(src, dst): 260 297 raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst) 261 298 copytree(src, real_dst, symlinks=True) … … 265 302 os.unlink(src) 266 303 267 def destinsrc(src, dst):304 def _destinsrc(src, dst): 268 305 src = abspath(src) 269 306 dst = abspath(dst) … … 273 310 dst += os.path.sep 274 311 return dst.startswith(src) 312 313 def _get_gid(name): 314 """Returns a gid, given a group name.""" 315 if getgrnam is None or name is None: 316 return None 317 try: 318 result = getgrnam(name) 319 except KeyError: 320 result = None 321 if result is not None: 322 return result[2] 323 return None 324 325 def _get_uid(name): 326 """Returns an uid, given a user name.""" 327 if getpwnam is None or name is None: 328 return None 329 try: 330 result = getpwnam(name) 331 except KeyError: 332 result = None 333 if result is not None: 334 return result[2] 335 return None 336 337 def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, 338 owner=None, group=None, logger=None): 339 """Create a (possibly compressed) tar file from all the files under 340 'base_dir'. 341 342 'compress' must be "gzip" (the default), "bzip2", or None. 343 344 'owner' and 'group' can be used to define an owner and a group for the 345 archive that is being built. If not provided, the current owner and group 346 will be used. 347 348 The output tar file will be named 'base_name' + ".tar", possibly plus 349 the appropriate compression extension (".gz", or ".bz2"). 350 351 Returns the output filename. 352 """ 353 tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''} 354 compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'} 355 356 # flags for compression program, each element of list will be an argument 357 if compress is not None and compress not in compress_ext.keys(): 358 raise ValueError, \ 359 ("bad value for 'compress': must be None, 'gzip' or 'bzip2'") 360 361 archive_name = base_name + '.tar' + compress_ext.get(compress, '') 362 archive_dir = os.path.dirname(archive_name) 363 364 if not os.path.exists(archive_dir): 365 if logger is not None: 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 return archive_name 397 398 def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False): 399 # XXX see if we want to keep an external call here 400 if verbose: 401 zipoptions = "-r" 402 else: 403 zipoptions = "-rq" 404 from distutils.errors import DistutilsExecError 405 from distutils.spawn import spawn 406 try: 407 spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run) 408 except DistutilsExecError: 409 # XXX really should distinguish between "couldn't find 410 # external 'zip' command" and "zip failed". 411 raise ExecError, \ 412 ("unable to create zip file '%s': " 413 "could neither import the 'zipfile' module nor " 414 "find a standalone zip utility") % zip_filename 415 416 def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None): 417 """Create a zip file from all the files under 'base_dir'. 418 419 The output zip file will be named 'base_name' + ".zip". Uses either the 420 "zipfile" Python module (if available) or the InfoZIP "zip" utility 421 (if installed and found on the default search path). If neither tool is 422 available, raises ExecError. Returns the name of the output zip 423 file. 424 """ 425 zip_filename = base_name + ".zip" 426 archive_dir = os.path.dirname(base_name) 427 428 if not os.path.exists(archive_dir): 429 if logger is not None: 430 logger.info("creating %s", archive_dir) 431 if not dry_run: 432 os.makedirs(archive_dir) 433 434 # If zipfile module is not available, try spawning an external 'zip' 435 # command. 436 try: 437 import zipfile 438 except ImportError: 439 zipfile = None 440 441 if zipfile is None: 442 _call_external_zip(base_dir, zip_filename, verbose, dry_run) 443 else: 444 if logger is not None: 445 logger.info("creating '%s' and adding '%s' to it", 446 zip_filename, base_dir) 447 448 if not dry_run: 449 zip = zipfile.ZipFile(zip_filename, "w", 450 compression=zipfile.ZIP_DEFLATED) 451 452 for dirpath, dirnames, filenames in os.walk(base_dir): 453 for name in filenames: 454 path = os.path.normpath(os.path.join(dirpath, name)) 455 if os.path.isfile(path): 456 zip.write(path, path) 457 if logger is not None: 458 logger.info("adding '%s'", path) 459 zip.close() 460 461 return zip_filename 462 463 _ARCHIVE_FORMATS = { 464 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), 465 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), 466 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"), 467 'zip': (_make_zipfile, [],"ZIP file") 468 } 469 470 def get_archive_formats(): 471 """Returns a list of supported formats for archiving and unarchiving. 472 473 Each element of the returned sequence is a tuple (name, description) 474 """ 475 formats = [(name, registry[2]) for name, registry in 476 _ARCHIVE_FORMATS.items()] 477 formats.sort() 478 return formats 479 480 def register_archive_format(name, function, extra_args=None, description=''): 481 """Registers an archive format. 482 483 name is the name of the format. function is the callable that will be 484 used to create archives. If provided, extra_args is a sequence of 485 (name, value) tuples that will be passed as arguments to the callable. 486 description can be provided to describe the format, and will be returned 487 by the get_archive_formats() function. 488 """ 489 if extra_args is None: 490 extra_args = [] 491 if not isinstance(function, collections.Callable): 492 raise TypeError('The %s object is not callable' % function) 493 if not isinstance(extra_args, (tuple, list)): 494 raise TypeError('extra_args needs to be a sequence') 495 for element in extra_args: 496 if not isinstance(element, (tuple, list)) or len(element) !=2 : 497 raise TypeError('extra_args elements are : (arg_name, value)') 498 499 _ARCHIVE_FORMATS[name] = (function, extra_args, description) 500 501 def unregister_archive_format(name): 502 del _ARCHIVE_FORMATS[name] 503 504 def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, 505 dry_run=0, owner=None, group=None, logger=None): 506 """Create an archive file (eg. zip or tar). 507 508 'base_name' is the name of the file to create, minus any format-specific 509 extension; 'format' is the archive format: one of "zip", "tar", "bztar" 510 or "gztar". 511 512 'root_dir' is a directory that will be the root directory of the 513 archive; ie. we typically chdir into 'root_dir' before creating the 514 archive. 'base_dir' is the directory where we start archiving from; 515 ie. 'base_dir' will be the common prefix of all files and 516 directories in the archive. 'root_dir' and 'base_dir' both default 517 to the current directory. Returns the name of the archive file. 518 519 'owner' and 'group' are used when creating a tar archive. By default, 520 uses the current owner and group. 521 """ 522 save_cwd = os.getcwd() 523 if root_dir is not None: 524 if logger is not None: 525 logger.debug("changing into '%s'", root_dir) 526 base_name = os.path.abspath(base_name) 527 if not dry_run: 528 os.chdir(root_dir) 529 530 if base_dir is None: 531 base_dir = os.curdir 532 533 kwargs = {'dry_run': dry_run, 'logger': logger} 534 535 try: 536 format_info = _ARCHIVE_FORMATS[format] 537 except KeyError: 538 raise ValueError, "unknown archive format '%s'" % format 539 540 func = format_info[0] 541 for arg, val in format_info[1]: 542 kwargs[arg] = val 543 544 if format != 'zip': 545 kwargs['owner'] = owner 546 kwargs['group'] = group 547 548 try: 549 filename = func(base_name, base_dir, **kwargs) 550 finally: 551 if root_dir is not None: 552 if logger is not None: 553 logger.debug("changing back to '%s'", save_cwd) 554 os.chdir(save_cwd) 555 556 return filename
Note:
See TracChangeset
for help on using the changeset viewer.