PageRenderTime 94ms CodeModel.GetById 30ms app.highlight 33ms RepoModel.GetById 28ms app.codeStats 0ms

/Lib/shutil.py

http://unladen-swallow.googlecode.com/
Python | 274 lines | 263 code | 1 blank | 10 comment | 2 complexity | 5c3c293bbca07f973b9659195f718377 MD5 | raw file
  1"""Utility functions for copying files and directory trees.
  2
  3XXX The functions here don't copy the resource fork or other metadata on Mac.
  4
  5"""
  6
  7import os
  8import sys
  9import stat
 10from os.path import abspath
 11import fnmatch
 12
 13__all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2",
 14           "copytree","move","rmtree","Error"]
 15
 16class Error(EnvironmentError):
 17    pass
 18
 19try:
 20    WindowsError
 21except NameError:
 22    WindowsError = None
 23
 24def copyfileobj(fsrc, fdst, length=16*1024):
 25    """copy data from file-like object fsrc to file-like object fdst"""
 26    while 1:
 27        buf = fsrc.read(length)
 28        if not buf:
 29            break
 30        fdst.write(buf)
 31
 32def _samefile(src, dst):
 33    # Macintosh, Unix.
 34    if hasattr(os.path,'samefile'):
 35        try:
 36            return os.path.samefile(src, dst)
 37        except OSError:
 38            return False
 39
 40    # All other platforms: check for same pathname.
 41    return (os.path.normcase(os.path.abspath(src)) ==
 42            os.path.normcase(os.path.abspath(dst)))
 43
 44def copyfile(src, dst):
 45    """Copy data from src to dst"""
 46    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()
 60
 61def copymode(src, dst):
 62    """Copy mode bits from src to dst"""
 63    if hasattr(os, 'chmod'):
 64        st = os.stat(src)
 65        mode = stat.S_IMODE(st.st_mode)
 66        os.chmod(dst, mode)
 67
 68def copystat(src, dst):
 69    """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
 70    st = os.stat(src)
 71    mode = stat.S_IMODE(st.st_mode)
 72    if hasattr(os, 'utime'):
 73        os.utime(dst, (st.st_atime, st.st_mtime))
 74    if hasattr(os, 'chmod'):
 75        os.chmod(dst, mode)
 76    if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
 77        os.chflags(dst, st.st_flags)
 78
 79
 80def copy(src, dst):
 81    """Copy data and mode bits ("cp src dst").
 82
 83    The destination may be a directory.
 84
 85    """
 86    if os.path.isdir(dst):
 87        dst = os.path.join(dst, os.path.basename(src))
 88    copyfile(src, dst)
 89    copymode(src, dst)
 90
 91def copy2(src, dst):
 92    """Copy data and all stat info ("cp -p src dst").
 93
 94    The destination may be a directory.
 95
 96    """
 97    if os.path.isdir(dst):
 98        dst = os.path.join(dst, os.path.basename(src))
 99    copyfile(src, dst)
100    copystat(src, dst)
101
102def ignore_patterns(*patterns):
103    """Function that can be used as copytree() ignore parameter.
104
105    Patterns is a sequence of glob-style patterns
106    that are used to exclude files"""
107    def _ignore_patterns(path, names):
108        ignored_names = []
109        for pattern in patterns:
110            ignored_names.extend(fnmatch.filter(names, pattern))
111        return set(ignored_names)
112    return _ignore_patterns
113
114def copytree(src, dst, symlinks=False, ignore=None):
115    """Recursively copy a directory tree using copy2().
116
117    The destination directory must not already exist.
118    If exception(s) occur, an Error is raised with a list of reasons.
119
120    If the optional symlinks flag is true, symbolic links in the
121    source tree result in symbolic links in the destination tree; if
122    it is false, the contents of the files pointed to by symbolic
123    links are copied.
124
125    The optional ignore argument is a callable. If given, it
126    is called with the `src` parameter, which is the directory
127    being visited by copytree(), and `names` which is the list of
128    `src` contents, as returned by os.listdir():
129
130        callable(src, names) -> ignored_names
131
132    Since copytree() is called recursively, the callable will be
133    called once for each directory that is copied. It returns a
134    list of names relative to the `src` directory that should
135    not be copied.
136
137    XXX Consider this example code rather than the ultimate tool.
138
139    """
140    names = os.listdir(src)
141    if ignore is not None:
142        ignored_names = ignore(src, names)
143    else:
144        ignored_names = set()
145
146    os.makedirs(dst)
147    errors = []
148    for name in names:
149        if name in ignored_names:
150            continue
151        srcname = os.path.join(src, name)
152        dstname = os.path.join(dst, name)
153        try:
154            if symlinks and os.path.islink(srcname):
155                linkto = os.readlink(srcname)
156                os.symlink(linkto, dstname)
157            elif os.path.isdir(srcname):
158                copytree(srcname, dstname, symlinks, ignore)
159            else:
160                copy2(srcname, dstname)
161            # XXX What about devices, sockets etc.?
162        except (IOError, os.error), why:
163            errors.append((srcname, dstname, str(why)))
164        # catch the Error from the recursive copytree so that we can
165        # continue with other files
166        except Error, err:
167            errors.extend(err.args[0])
168    try:
169        copystat(src, dst)
170    except OSError, why:
171        if WindowsError is not None and isinstance(why, WindowsError):
172            # Copying file access times may fail on Windows
173            pass
174        else:
175            errors.extend((src, dst, str(why)))
176    if errors:
177        raise Error, errors
178
179def rmtree(path, ignore_errors=False, onerror=None):
180    """Recursively delete a directory tree.
181
182    If ignore_errors is set, errors are ignored; otherwise, if onerror
183    is set, it is called to handle the error with arguments (func,
184    path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
185    path is the argument to that function that caused it to fail; and
186    exc_info is a tuple returned by sys.exc_info().  If ignore_errors
187    is false and onerror is None, an exception is raised.
188
189    """
190    if ignore_errors:
191        def onerror(*args):
192            pass
193    elif onerror is None:
194        def onerror(*args):
195            raise
196    try:
197        if os.path.islink(path):
198            # symlinks to directories are forbidden, see bug #1669
199            raise OSError("Cannot call rmtree on a symbolic link")
200    except OSError:
201        onerror(os.path.islink, path, sys.exc_info())
202        # can't continue even if onerror hook returns
203        return
204    names = []
205    try:
206        names = os.listdir(path)
207    except os.error, err:
208        onerror(os.listdir, path, sys.exc_info())
209    for name in names:
210        fullname = os.path.join(path, name)
211        try:
212            mode = os.lstat(fullname).st_mode
213        except os.error:
214            mode = 0
215        if stat.S_ISDIR(mode):
216            rmtree(fullname, ignore_errors, onerror)
217        else:
218            try:
219                os.remove(fullname)
220            except os.error, err:
221                onerror(os.remove, fullname, sys.exc_info())
222    try:
223        os.rmdir(path)
224    except os.error:
225        onerror(os.rmdir, path, sys.exc_info())
226
227
228def _basename(path):
229    # A basename() variant which first strips the trailing slash, if present.
230    # Thus we always get the last component of the path, even for directories.
231    return os.path.basename(path.rstrip(os.path.sep))
232
233def move(src, dst):
234    """Recursively move a file or directory to another location. This is
235    similar to the Unix "mv" command.
236
237    If the destination is a directory or a symlink to a directory, the source
238    is moved inside the directory. The destination path must not already
239    exist.
240
241    If the destination already exists but is not a directory, it may be
242    overwritten depending on os.rename() semantics.
243
244    If the destination is on our current filesystem, then rename() is used.
245    Otherwise, src is copied to the destination and then removed.
246    A lot more could be done here...  A look at a mv.c shows a lot of
247    the issues this implementation glosses over.
248
249    """
250    real_dst = dst
251    if os.path.isdir(dst):
252        real_dst = os.path.join(dst, _basename(src))
253        if os.path.exists(real_dst):
254            raise Error, "Destination path '%s' already exists" % real_dst
255    try:
256        os.rename(src, real_dst)
257    except OSError:
258        if os.path.isdir(src):
259            if destinsrc(src, dst):
260                raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
261            copytree(src, real_dst, symlinks=True)
262            rmtree(src)
263        else:
264            copy2(src, real_dst)
265            os.unlink(src)
266
267def destinsrc(src, dst):
268    src = abspath(src)
269    dst = abspath(dst)
270    if not src.endswith(os.path.sep):
271        src += os.path.sep
272    if not dst.endswith(os.path.sep):
273        dst += os.path.sep
274    return dst.startswith(src)