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