codekingpro/portable-devtools
114k
1"""distutils.file_util2 3Utility functions for operating on single files.4"""5 6import os7 8from ._log import log9from .errors import DistutilsFileError10 11# for generating verbose output in 'copy_file()'12_copy_action = {None: 'copying', 'hard': 'hard linking', 'sym': 'symbolically linking'}13 14 15def _copy_file_contents(src, dst, buffer_size=16 * 1024): # noqa: C90116 """Copy the file 'src' to 'dst'; both must be filenames. Any error17 opening either file, reading from 'src', or writing to 'dst', raises18 DistutilsFileError. Data is read/written in chunks of 'buffer_size'19 bytes (default 16k). No attempt is made to handle anything apart from20 regular files.21 """22 # Stolen from shutil module in the standard library, but with23 # custom error-handling added.24 fsrc = None25 fdst = None26 try:27 try:28 fsrc = open(src, 'rb')29 except OSError as e:30 raise DistutilsFileError(f"could not open '{src}': {e.strerror}")31 32 if os.path.exists(dst):33 try:34 os.unlink(dst)35 except OSError as e:36 raise DistutilsFileError(f"could not delete '{dst}': {e.strerror}")37 38 try:39 fdst = open(dst, 'wb')40 except OSError as e:41 raise DistutilsFileError(f"could not create '{dst}': {e.strerror}")42 43 while True:44 try:45 buf = fsrc.read(buffer_size)46 except OSError as e:47 raise DistutilsFileError(f"could not read from '{src}': {e.strerror}")48 49 if not buf:50 break51 52 try:53 fdst.write(buf)54 except OSError as e:55 raise DistutilsFileError(f"could not write to '{dst}': {e.strerror}")56 finally:57 if fdst:58 fdst.close()59 if fsrc:60 fsrc.close()61 62 63def copy_file( # noqa: C90164 src,65 dst,66 preserve_mode=1,67 preserve_times=1,68 update=0,69 link=None,70 verbose=1,71 dry_run=0,72):73 """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is74 copied there with the same name; otherwise, it must be a filename. (If75 the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'76 is true (the default), the file's mode (type and permission bits, or77 whatever is analogous on the current platform) is copied. If78 'preserve_times' is true (the default), the last-modified and79 last-access times are copied as well. If 'update' is true, 'src' will80 only be copied if 'dst' does not exist, or if 'dst' does exist but is81 older than 'src'.82 83 'link' allows you to make hard links (os.link) or symbolic links84 (os.symlink) instead of copying: set it to "hard" or "sym"; if it is85 None (the default), files are copied. Don't set 'link' on systems that86 don't support it: 'copy_file()' doesn't check if hard or symbolic87 linking is available. If hardlink fails, falls back to88 _copy_file_contents().89 90 Under Mac OS, uses the native file copy function in macostools; on91 other systems, uses '_copy_file_contents()' to copy file contents.92 93 Return a tuple (dest_name, copied): 'dest_name' is the actual name of94 the output file, and 'copied' is true if the file was copied (or would95 have been copied, if 'dry_run' true).96 """97 # XXX if the destination file already exists, we clobber it if98 # copying, but blow up if linking. Hmmm. And I don't know what99 # macostools.copyfile() does. Should definitely be consistent, and100 # should probably blow up if destination exists and we would be101 # changing it (ie. it's not already a hard/soft link to src OR102 # (not update) and (src newer than dst).103 104 from distutils._modified import newer105 from stat import S_IMODE, ST_ATIME, ST_MODE, ST_MTIME106 107 if not os.path.isfile(src):108 raise DistutilsFileError(109 "can't copy '%s': doesn't exist or not a regular file" % src110 )111 112 if os.path.isdir(dst):113 dir = dst114 dst = os.path.join(dst, os.path.basename(src))115 else:116 dir = os.path.dirname(dst)117 118 if update and not newer(src, dst):119 if verbose >= 1:120 log.debug("not copying %s (output up-to-date)", src)121 return (dst, 0)122 123 try:124 action = _copy_action[link]125 except KeyError:126 raise ValueError("invalid value '%s' for 'link' argument" % link)127 128 if verbose >= 1:129 if os.path.basename(dst) == os.path.basename(src):130 log.info("%s %s -> %s", action, src, dir)131 else:132 log.info("%s %s -> %s", action, src, dst)133 134 if dry_run:135 return (dst, 1)136 137 # If linking (hard or symbolic), use the appropriate system call138 # (Unix only, of course, but that's the caller's responsibility)139 elif link == 'hard':140 if not (os.path.exists(dst) and os.path.samefile(src, dst)):141 try:142 os.link(src, dst)143 return (dst, 1)144 except OSError:145 # If hard linking fails, fall back on copying file146 # (some special filesystems don't support hard linking147 # even under Unix, see issue #8876).148 pass149 elif link == 'sym':150 if not (os.path.exists(dst) and os.path.samefile(src, dst)):151 os.symlink(src, dst)152 return (dst, 1)153 154 # Otherwise (non-Mac, not linking), copy the file contents and155 # (optionally) copy the times and mode.156 _copy_file_contents(src, dst)157 if preserve_mode or preserve_times:158 st = os.stat(src)159 160 # According to David Ascher <da@ski.org>, utime() should be done161 # before chmod() (at least under NT).162 if preserve_times:163 os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))164 if preserve_mode:165 os.chmod(dst, S_IMODE(st[ST_MODE]))166 167 return (dst, 1)168 169 170# XXX I suspect this is Unix-specific -- need porting help!171def move_file(src, dst, verbose=1, dry_run=0): # noqa: C901172 """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will173 be moved into it with the same name; otherwise, 'src' is just renamed174 to 'dst'. Return the new full name of the file.175 176 Handles cross-device moves on Unix using 'copy_file()'. What about177 other systems???178 """179 import errno180 from os.path import basename, dirname, exists, isdir, isfile181 182 if verbose >= 1:183 log.info("moving %s -> %s", src, dst)184 185 if dry_run:186 return dst187 188 if not isfile(src):189 raise DistutilsFileError("can't move '%s': not a regular file" % src)190 191 if isdir(dst):192 dst = os.path.join(dst, basename(src))193 elif exists(dst):194 raise DistutilsFileError(195 f"can't move '{src}': destination '{dst}' already exists"196 )197 198 if not isdir(dirname(dst)):199 raise DistutilsFileError(200 f"can't move '{src}': destination '{dst}' not a valid path"201 )202 203 copy_it = False204 try:205 os.rename(src, dst)206 except OSError as e:207 (num, msg) = e.args208 if num == errno.EXDEV:209 copy_it = True210 else:211 raise DistutilsFileError(f"couldn't move '{src}' to '{dst}': {msg}")212 213 if copy_it:214 copy_file(src, dst, verbose=verbose)215 try:216 os.unlink(src)217 except OSError as e:218 (num, msg) = e.args219 try:220 os.unlink(dst)221 except OSError:222 pass223 raise DistutilsFileError(224 f"couldn't move '{src}' to '{dst}' by copy/delete: "225 f"delete '{src}' failed: {msg}"226 )227 return dst228 229 230def write_file(filename, contents):231 """Create a file with the specified name and write 'contents' (a232 sequence of strings without line terminators) to it.233 """234 with open(filename, 'w', encoding='utf-8') as f:235 f.writelines(line + '\n' for line in contents)236 