file_util.py revision a392dcb2117739ad0dc7f67bd550083ee860226b
1"""distutils.file_util 2 3Utility functions for operating on single files.""" 4 5# created 2000/04/03, Greg Ward (extracted from util.py) 6 7__revision__ = "$Id$" 8 9import os 10from distutils.errors import DistutilsFileError 11 12 13# for generating verbose output in 'copy_file()' 14_copy_action = { None: 'copying', 15 'hard': 'hard linking', 16 'sym': 'symbolically linking' } 17 18 19def _copy_file_contents (src, dst, buffer_size=16*1024): 20 """Copy the file 'src' to 'dst'; both must be filenames. Any error 21 opening either file, reading from 'src', or writing to 'dst', 22 raises DistutilsFileError. Data is read/written in chunks of 23 'buffer_size' bytes (default 16k). No attempt is made to handle 24 anything apart from regular files.""" 25 26 # Stolen from shutil module in the standard library, but with 27 # custom error-handling added. 28 29 fsrc = None 30 fdst = None 31 try: 32 try: 33 fsrc = open(src, 'rb') 34 except os.error, (errno, errstr): 35 raise DistutilsFileError, \ 36 "could not open '%s': %s" % (src, errstr) 37 38 try: 39 fdst = open(dst, 'wb') 40 except os.error, (errno, errstr): 41 raise DistutilsFileError, \ 42 "could not create '%s': %s" % (dst, errstr) 43 44 while 1: 45 try: 46 buf = fsrc.read (buffer_size) 47 except os.error, (errno, errstr): 48 raise DistutilsFileError, \ 49 "could not read from '%s': %s" % (src, errstr) 50 51 if not buf: 52 break 53 54 try: 55 fdst.write(buf) 56 except os.error, (errno, errstr): 57 raise DistutilsFileError, \ 58 "could not write to '%s': %s" % (dst, errstr) 59 60 finally: 61 if fdst: 62 fdst.close() 63 if fsrc: 64 fsrc.close() 65 66# _copy_file_contents() 67 68 69def copy_file (src, dst, 70 preserve_mode=1, 71 preserve_times=1, 72 update=0, 73 link=None, 74 verbose=0, 75 dry_run=0): 76 77 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' 78 is copied there with the same name; otherwise, it must be a 79 filename. (If the file exists, it will be ruthlessly clobbered.) 80 If 'preserve_mode' is true (the default), the file's mode (type 81 and permission bits, or whatever is analogous on the current 82 platform) is copied. If 'preserve_times' is true (the default), 83 the last-modified and last-access times are copied as well. If 84 'update' is true, 'src' will only be copied if 'dst' does not 85 exist, or if 'dst' does exist but is older than 'src'. If 86 'verbose' is true, then a one-line summary of the copy will be 87 printed to stdout. 88 89 'link' allows you to make hard links (os.link) or symbolic links 90 (os.symlink) instead of copying: set it to "hard" or "sym"; if it 91 is None (the default), files are copied. Don't set 'link' on 92 systems that don't support it: 'copy_file()' doesn't check if 93 hard or symbolic linking is available. 94 95 Under Mac OS, uses the native file copy function in macostools; 96 on other systems, uses '_copy_file_contents()' to copy file 97 contents. 98 99 Return the name of the destination file, whether it was actually 100 copied or not.""" 101 102 # XXX if the destination file already exists, we clobber it if 103 # copying, but blow up if linking. Hmmm. And I don't know what 104 # macostools.copyfile() does. Should definitely be consistent, and 105 # should probably blow up if destination exists and we would be 106 # changing it (ie. it's not already a hard/soft link to src OR 107 # (not update) and (src newer than dst). 108 109 from stat import * 110 from distutils.dep_util import newer 111 112 if not os.path.isfile (src): 113 raise DistutilsFileError, \ 114 "can't copy '%s': doesn't exist or not a regular file" % src 115 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) 121 122 if update and not newer (src, dst): 123 if verbose: 124 print "not copying %s (output up-to-date)" % src 125 return dst 126 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) 137 138 if dry_run: 139 return dst 140 141 # On a Mac, 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]) 149 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) 158 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) 165 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])) 172 173 return dst 174 175# copy_file () 176 177 178# XXX I suspect this is Unix-specific -- need porting help! 179def move_file (src, dst, 180 verbose=0, 181 dry_run=0): 182 183 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file 184 will be moved into it with the same name; otherwise, 'src' is 185 just renamed to 'dst'. Return the new full name of the file. 186 187 Handles cross-device moves on Unix using 188 'copy_file()'. What about other systems???""" 189 190 from os.path import exists, isfile, isdir, basename, dirname 191 192 if verbose: 193 print "moving %s -> %s" % (src, dst) 194 195 if dry_run: 196 return dst 197 198 if not isfile (src): 199 raise DistutilsFileError, \ 200 "can't move '%s': not a regular file" % src 201 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) 208 209 if not isdir (dirname (dst)): 210 raise DistutilsFileError, \ 211 "can't move '%s': destination '%s' not a valid path" % \ 212 (src, dst) 213 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) 223 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) 237 238 return dst 239 240# move_file () 241 242 243def 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.""" 246 247 f = open (filename, "w") 248 for line in contents: 249 f.write (line + "\n") 250 f.close () 251