|
1 """Utility functions for copying files and directory trees. |
|
2 |
|
3 XXX The functions here don't copy the resource fork or other metadata on Mac. |
|
4 |
|
5 """ |
|
6 |
|
7 import os |
|
8 import sys |
|
9 import stat |
|
10 from os.path import abspath |
|
11 import fnmatch |
|
12 |
|
13 __all__ = ["copyfileobj","copyfile","copymode","copystat","copy","copy2", |
|
14 "copytree","move","rmtree","Error"] |
|
15 |
|
16 class Error(EnvironmentError): |
|
17 pass |
|
18 |
|
19 try: |
|
20 WindowsError |
|
21 except NameError: |
|
22 WindowsError = None |
|
23 |
|
24 def 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 |
|
32 def _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 |
|
44 def 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 |
|
61 def 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 |
|
68 def 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 |
|
80 def 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 |
|
91 def 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 |
|
102 def 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 |
|
114 def 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 |
|
179 def 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 |
|
228 def _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 |
|
233 def 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 |
|
267 def destinsrc(src, dst): |
|
268 return abspath(dst).startswith(abspath(src)) |