Updated for 2.1b2 distribution.
[python/dscho.git] / Lib / distutils / file_util.py
blob39f6eea28964ae07a2e35b77ab7d7d7fa97bd59a
1 """distutils.file_util
3 Utility functions for operating on single files.
4 """
6 # created 2000/04/03, Greg Ward (extracted from util.py)
8 __revision__ = "$Id$"
10 import os
11 from stat import *
12 from distutils.errors import DistutilsFileError
15 # for generating verbose output in 'copy_file()'
16 _copy_action = { None: 'copying',
17 'hard': 'hard linking',
18 'sym': 'symbolically linking' }
21 def _copy_file_contents (src, dst, buffer_size=16*1024):
22 """Copy the file 'src' to 'dst'; both must be filenames. Any error
23 opening either file, reading from 'src', or writing to 'dst', raises
24 DistutilsFileError. Data is read/written in chunks of 'buffer_size'
25 bytes (default 16k). No attempt is made to handle anything apart from
26 regular files.
27 """
28 # Stolen from shutil module in the standard library, but with
29 # custom error-handling added.
31 fsrc = None
32 fdst = None
33 try:
34 try:
35 fsrc = open(src, 'rb')
36 except os.error, (errno, errstr):
37 raise DistutilsFileError, \
38 "could not open '%s': %s" % (src, errstr)
40 try:
41 fdst = open(dst, 'wb')
42 except os.error, (errno, errstr):
43 raise DistutilsFileError, \
44 "could not create '%s': %s" % (dst, errstr)
46 while 1:
47 try:
48 buf = fsrc.read(buffer_size)
49 except os.error, (errno, errstr):
50 raise DistutilsFileError, \
51 "could not read from '%s': %s" % (src, errstr)
53 if not buf:
54 break
56 try:
57 fdst.write(buf)
58 except os.error, (errno, errstr):
59 raise DistutilsFileError, \
60 "could not write to '%s': %s" % (dst, errstr)
62 finally:
63 if fdst:
64 fdst.close()
65 if fsrc:
66 fsrc.close()
68 # _copy_file_contents()
71 def copy_file (src, dst,
72 preserve_mode=1,
73 preserve_times=1,
74 update=0,
75 link=None,
76 verbose=0,
77 dry_run=0):
79 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is
80 copied there with the same name; otherwise, it must be a filename. (If
81 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'
82 is true (the default), the file's mode (type and permission bits, or
83 whatever is analogous on the current platform) is copied. If
84 'preserve_times' is true (the default), the last-modified and
85 last-access times are copied as well. If 'update' is true, 'src' will
86 only be copied if 'dst' does not exist, or if 'dst' does exist but is
87 older than 'src'. If 'verbose' is true, then a one-line summary of the
88 copy will be printed to stdout.
90 'link' allows you to make hard links (os.link) or symbolic links
91 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is
92 None (the default), files are copied. Don't set 'link' on systems that
93 don't support it: 'copy_file()' doesn't check if hard or symbolic
94 linking is available.
96 Under Mac OS, uses the native file copy function in macostools; on
97 other systems, uses '_copy_file_contents()' to copy file contents.
99 Return a tuple (dest_name, copied): 'dest_name' is the actual name of
100 the output file, and 'copied' is true if the file was copied (or would
101 have been copied, if 'dry_run' true).
103 # XXX if the destination file already exists, we clobber it if
104 # copying, but blow up if linking. Hmmm. And I don't know what
105 # macostools.copyfile() does. Should definitely be consistent, and
106 # should probably blow up if destination exists and we would be
107 # changing it (ie. it's not already a hard/soft link to src OR
108 # (not update) and (src newer than dst).
110 from distutils.dep_util import newer
112 if not os.path.isfile(src):
113 raise DistutilsFileError, \
114 "can't copy '%s': doesn't exist or not a regular file" % src
116 if os.path.isdir(dst):
117 dir = dst
118 dst = os.path.join(dst, os.path.basename(src))
119 else:
120 dir = os.path.dirname(dst)
122 if update and not newer(src, dst):
123 if verbose:
124 print "not copying %s (output up-to-date)" % src
125 return (dst, 0)
127 try:
128 action = _copy_action[link]
129 except KeyError:
130 raise ValueError, \
131 "invalid value '%s' for 'link' argument" % link
132 if verbose:
133 if os.path.basename(dst) == os.path.basename(src):
134 print "%s %s -> %s" % (action, src, dir)
135 else:
136 print "%s %s -> %s" % (action, src, dst)
138 if dry_run:
139 return (dst, 1)
141 # On Mac OS, use the native file copy routine
142 if os.name == 'mac':
143 import macostools
144 try:
145 macostools.copy(src, dst, 0, preserve_times)
146 except os.error, exc:
147 raise DistutilsFileError, \
148 "could not copy '%s' to '%s': %s" % (src, dst, exc[-1])
150 # If linking (hard or symbolic), use the appropriate system call
151 # (Unix only, of course, but that's the caller's responsibility)
152 elif link == 'hard':
153 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
154 os.link(src, dst)
155 elif link == 'sym':
156 if not (os.path.exists(dst) and os.path.samefile(src, dst)):
157 os.symlink(src, dst)
159 # Otherwise (non-Mac, not linking), copy the file contents and
160 # (optionally) copy the times and mode.
161 else:
162 _copy_file_contents(src, dst)
163 if preserve_mode or preserve_times:
164 st = os.stat(src)
166 # According to David Ascher <da@ski.org>, utime() should be done
167 # before chmod() (at least under NT).
168 if preserve_times:
169 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
170 if preserve_mode:
171 os.chmod(dst, S_IMODE(st[ST_MODE]))
173 return (dst, 1)
175 # copy_file ()
178 # XXX I suspect this is Unix-specific -- need porting help!
179 def move_file (src, dst,
180 verbose=0,
181 dry_run=0):
183 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will
184 be moved into it with the same name; otherwise, 'src' is just renamed
185 to 'dst'. Return the new full name of the file.
187 Handles cross-device moves on Unix using 'copy_file()'. What about
188 other systems???
190 from os.path import exists, isfile, isdir, basename, dirname
192 if verbose:
193 print "moving %s -> %s" % (src, dst)
195 if dry_run:
196 return dst
198 if not isfile(src):
199 raise DistutilsFileError, \
200 "can't move '%s': not a regular file" % src
202 if isdir(dst):
203 dst = os.path.join(dst, basename(src))
204 elif exists(dst):
205 raise DistutilsFileError, \
206 "can't move '%s': destination '%s' already exists" % \
207 (src, dst)
209 if not isdir(dirname(dst)):
210 raise DistutilsFileError, \
211 "can't move '%s': destination '%s' not a valid path" % \
212 (src, dst)
214 copy_it = 0
215 try:
216 os.rename(src, dst)
217 except os.error, (num, msg):
218 if num == errno.EXDEV:
219 copy_it = 1
220 else:
221 raise DistutilsFileError, \
222 "couldn't move '%s' to '%s': %s" % (src, dst, msg)
224 if copy_it:
225 copy_file(src, dst)
226 try:
227 os.unlink(src)
228 except os.error, (num, msg):
229 try:
230 os.unlink(dst)
231 except os.error:
232 pass
233 raise DistutilsFileError, \
234 ("couldn't move '%s' to '%s' by copy/delete: " +
235 "delete '%s' failed: %s") % \
236 (src, dst, src, msg)
238 return dst
240 # move_file ()
243 def write_file (filename, contents):
244 """Create a file with the specified name and write 'contents' (a
245 sequence of strings without line terminators) to it.
247 f = open(filename, "w")
248 for line in contents:
249 f.write(line + "\n")
250 f.close()