summaryrefslogtreecommitdiffstats
path: root/Lib/shutil.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/shutil.py')
-rw-r--r--Lib/shutil.py109
1 files changed, 85 insertions, 24 deletions
diff --git a/Lib/shutil.py b/Lib/shutil.py
index f30ba86..4c182ae 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -8,6 +8,8 @@ import os
import sys
import stat
from os.path import abspath
+import fnmatch
+import errno
__all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
"copytree","move","rmtree","Error"]
@@ -43,19 +45,11 @@ def _samefile(src, dst):
def copyfile(src, dst):
"""Copy data from src to dst"""
if _samefile(src, dst):
- raise Error, "`%s` and `%s` are the same file" % (src, dst)
+ raise Error("`%s` and `%s` are the same file" % (src, dst))
- fsrc = None
- fdst = None
- try:
- fsrc = open(src, 'rb')
- fdst = open(dst, 'wb')
- copyfileobj(fsrc, fdst)
- finally:
- if fdst:
- fdst.close()
- if fsrc:
- fsrc.close()
+ with open(src, 'rb') as fsrc:
+ with open(dst, 'wb') as fdst:
+ copyfileobj(fsrc, fdst)
def copymode(src, dst):
"""Copy mode bits from src to dst"""
@@ -65,14 +59,19 @@ def copymode(src, dst):
os.chmod(dst, mode)
def copystat(src, dst):
- """Copy all stat info (mode bits, atime and mtime) from src to dst"""
+ """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
st = os.stat(src)
mode = stat.S_IMODE(st.st_mode)
if hasattr(os, 'utime'):
os.utime(dst, (st.st_atime, st.st_mtime))
if hasattr(os, 'chmod'):
os.chmod(dst, mode)
-
+ if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
+ try:
+ os.chflags(dst, st.st_flags)
+ except OSError, why:
+ if not hasattr(errno, 'EOPNOTSUPP') or why.errno != errno.EOPNOTSUPP:
+ raise
def copy(src, dst):
"""Copy data and mode bits ("cp src dst").
@@ -96,8 +95,19 @@ def copy2(src, dst):
copyfile(src, dst)
copystat(src, dst)
+def ignore_patterns(*patterns):
+ """Function that can be used as copytree() ignore parameter.
+
+ Patterns is a sequence of glob-style patterns
+ that are used to exclude files"""
+ def _ignore_patterns(path, names):
+ ignored_names = []
+ for pattern in patterns:
+ ignored_names.extend(fnmatch.filter(names, pattern))
+ return set(ignored_names)
+ return _ignore_patterns
-def copytree(src, dst, symlinks=False):
+def copytree(src, dst, symlinks=False, ignore=None):
"""Recursively copy a directory tree using copy2().
The destination directory must not already exist.
@@ -108,13 +118,32 @@ def copytree(src, dst, symlinks=False):
it is false, the contents of the files pointed to by symbolic
links are copied.
+ The optional ignore argument is a callable. If given, it
+ is called with the `src` parameter, which is the directory
+ being visited by copytree(), and `names` which is the list of
+ `src` contents, as returned by os.listdir():
+
+ callable(src, names) -> ignored_names
+
+ Since copytree() is called recursively, the callable will be
+ called once for each directory that is copied. It returns a
+ list of names relative to the `src` directory that should
+ not be copied.
+
XXX Consider this example code rather than the ultimate tool.
"""
names = os.listdir(src)
+ if ignore is not None:
+ ignored_names = ignore(src, names)
+ else:
+ ignored_names = set()
+
os.makedirs(dst)
errors = []
for name in names:
+ if name in ignored_names:
+ continue
srcname = os.path.join(src, name)
dstname = os.path.join(dst, name)
try:
@@ -122,7 +151,7 @@ def copytree(src, dst, symlinks=False):
linkto = os.readlink(srcname)
os.symlink(linkto, dstname)
elif os.path.isdir(srcname):
- copytree(srcname, dstname, symlinks)
+ copytree(srcname, dstname, symlinks, ignore)
else:
copy2(srcname, dstname)
# XXX What about devices, sockets etc.?
@@ -160,6 +189,14 @@ def rmtree(path, ignore_errors=False, onerror=None):
elif onerror is None:
def onerror(*args):
raise
+ try:
+ if os.path.islink(path):
+ # symlinks to directories are forbidden, see bug #1669
+ raise OSError("Cannot call rmtree on a symbolic link")
+ except OSError:
+ onerror(os.path.islink, path, sys.exc_info())
+ # can't continue even if onerror hook returns
+ return
names = []
try:
names = os.listdir(path)
@@ -183,27 +220,51 @@ def rmtree(path, ignore_errors=False, onerror=None):
except os.error:
onerror(os.rmdir, path, sys.exc_info())
+
+def _basename(path):
+ # A basename() variant which first strips the trailing slash, if present.
+ # Thus we always get the last component of the path, even for directories.
+ return os.path.basename(path.rstrip(os.path.sep))
+
def move(src, dst):
- """Recursively move a file or directory to another location.
+ """Recursively move a file or directory to another location. This is
+ similar to the Unix "mv" command.
- If the destination is on our current filesystem, then simply use
- rename. Otherwise, copy src to the dst and then remove src.
+ If the destination is a directory or a symlink to a directory, the source
+ is moved inside the directory. The destination path must not already
+ exist.
+
+ If the destination already exists but is not a directory, it may be
+ overwritten depending on os.rename() semantics.
+
+ If the destination is on our current filesystem, then rename() is used.
+ Otherwise, src is copied to the destination and then removed.
A lot more could be done here... A look at a mv.c shows a lot of
the issues this implementation glosses over.
"""
-
+ real_dst = dst
+ if os.path.isdir(dst):
+ real_dst = os.path.join(dst, _basename(src))
+ if os.path.exists(real_dst):
+ raise Error, "Destination path '%s' already exists" % real_dst
try:
- os.rename(src, dst)
+ os.rename(src, real_dst)
except OSError:
if os.path.isdir(src):
if destinsrc(src, dst):
raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
- copytree(src, dst, symlinks=True)
+ copytree(src, real_dst, symlinks=True)
rmtree(src)
else:
- copy2(src,dst)
+ copy2(src, real_dst)
os.unlink(src)
def destinsrc(src, dst):
- return abspath(dst).startswith(abspath(src))
+ src = abspath(src)
+ dst = abspath(dst)
+ if not src.endswith(os.path.sep):
+ src += os.path.sep
+ if not dst.endswith(os.path.sep):
+ dst += os.path.sep
+ return dst.startswith(src)