codekingpro/portable-devtools
114k
1"""Common operations on Posix pathnames.2 3Instead of importing this module directly, import os and refer to4this module as os.path. The "os.path" name is an alias for this5module on Posix systems; on other systems (e.g. Windows),6os.path provides the same operations in a manner specific to that7platform, and is an alias to another module (e.g. ntpath).8 9Some of this can actually be useful on non-Posix systems too, e.g.10for manipulation of the pathname component of URLs.11"""12 13# Strings representing various path-related bits and pieces.14# These are primarily for export; internally, they are hardcoded.15# Should be set before imports for resolving cyclic dependency.16curdir = '.'17pardir = '..'18extsep = '.'19sep = '/'20pathsep = ':'21defpath = '/bin:/usr/bin'22altsep = None23devnull = '/dev/null'24 25import errno26import os27import sys28import stat29import genericpath30from genericpath import *31 32__all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",33 "basename","dirname","commonprefix","getsize","getmtime",34 "getatime","getctime","islink","exists","lexists","isdir","isfile",35 "ismount", "expanduser","expandvars","normpath","abspath",36 "samefile","sameopenfile","samestat",37 "curdir","pardir","sep","pathsep","defpath","altsep","extsep",38 "devnull","realpath","supports_unicode_filenames","relpath",39 "commonpath", "isjunction","isdevdrive","ALLOW_MISSING"]40 41 42def _get_sep(path):43 if isinstance(path, bytes):44 return b'/'45 else:46 return '/'47 48# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac.49# On MS-DOS this may also turn slashes into backslashes; however, other50# normalizations (such as optimizing '../' away) are not allowed51# (another function should be defined to do that).52 53def normcase(s):54 """Normalize case of pathname. Has no effect under Posix"""55 return os.fspath(s)56 57 58# Return whether a path is absolute.59# Trivial in Posix, harder on the Mac or MS-DOS.60 61def isabs(s):62 """Test whether a path is absolute"""63 s = os.fspath(s)64 sep = _get_sep(s)65 return s.startswith(sep)66 67 68# Join pathnames.69# Ignore the previous parts if a part is absolute.70# Insert a '/' unless the first part is empty or already ends in '/'.71 72def join(a, *p):73 """Join two or more pathname components, inserting '/' as needed.74 If any component is an absolute path, all previous path components75 will be discarded. An empty last part will result in a path that76 ends with a separator."""77 a = os.fspath(a)78 sep = _get_sep(a)79 path = a80 try:81 for b in p:82 b = os.fspath(b)83 if b.startswith(sep) or not path:84 path = b85 elif path.endswith(sep):86 path += b87 else:88 path += sep + b89 except (TypeError, AttributeError, BytesWarning):90 genericpath._check_arg_types('join', a, *p)91 raise92 return path93 94 95# Split a path in head (everything up to the last '/') and tail (the96# rest). If the path ends in '/', tail will be empty. If there is no97# '/' in the path, head will be empty.98# Trailing '/'es are stripped from head unless it is the root.99 100def split(p):101 """Split a pathname. Returns tuple "(head, tail)" where "tail" is102 everything after the final slash. Either part may be empty."""103 p = os.fspath(p)104 sep = _get_sep(p)105 i = p.rfind(sep) + 1106 head, tail = p[:i], p[i:]107 if head and head != sep*len(head):108 head = head.rstrip(sep)109 return head, tail110 111 112# Split a path in root and extension.113# The extension is everything starting at the last dot in the last114# pathname component; the root is everything before that.115# It is always true that root + ext == p.116 117def splitext(p):118 p = os.fspath(p)119 if isinstance(p, bytes):120 sep = b'/'121 extsep = b'.'122 else:123 sep = '/'124 extsep = '.'125 return genericpath._splitext(p, sep, None, extsep)126splitext.__doc__ = genericpath._splitext.__doc__127 128# Split a pathname into a drive specification and the rest of the129# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty.130 131def splitdrive(p):132 """Split a pathname into drive and path. On Posix, drive is always133 empty."""134 p = os.fspath(p)135 return p[:0], p136 137 138try:139 from posix import _path_splitroot_ex as splitroot140except ImportError:141 def splitroot(p):142 """Split a pathname into drive, root and tail.143 144 The tail contains anything after the root."""145 p = os.fspath(p)146 if isinstance(p, bytes):147 sep = b'/'148 empty = b''149 else:150 sep = '/'151 empty = ''152 if p[:1] != sep:153 # Relative path, e.g.: 'foo'154 return empty, empty, p155 elif p[1:2] != sep or p[2:3] == sep:156 # Absolute path, e.g.: '/foo', '///foo', '////foo', etc.157 return empty, sep, p[1:]158 else:159 # Precisely two leading slashes, e.g.: '//foo'. Implementation defined per POSIX, see160 # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13161 return empty, p[:2], p[2:]162 163 164# Return the tail (basename) part of a path, same as split(path)[1].165 166def basename(p):167 """Returns the final component of a pathname"""168 p = os.fspath(p)169 sep = _get_sep(p)170 i = p.rfind(sep) + 1171 return p[i:]172 173 174# Return the head (dirname) part of a path, same as split(path)[0].175 176def dirname(p):177 """Returns the directory component of a pathname"""178 p = os.fspath(p)179 sep = _get_sep(p)180 i = p.rfind(sep) + 1181 head = p[:i]182 if head and head != sep*len(head):183 head = head.rstrip(sep)184 return head185 186 187# Is a path a mount point?188# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)189 190def ismount(path):191 """Test whether a path is a mount point"""192 try:193 s1 = os.lstat(path)194 except (OSError, ValueError):195 # It doesn't exist -- so not a mount point. :-)196 return False197 else:198 # A symlink can never be a mount point199 if stat.S_ISLNK(s1.st_mode):200 return False201 202 path = os.fspath(path)203 if isinstance(path, bytes):204 parent = join(path, b'..')205 else:206 parent = join(path, '..')207 try:208 s2 = os.lstat(parent)209 except OSError:210 parent = realpath(parent)211 try:212 s2 = os.lstat(parent)213 except OSError:214 return False215 216 # path/.. on a different device as path or the same i-node as path217 return s1.st_dev != s2.st_dev or s1.st_ino == s2.st_ino218 219 220# Expand paths beginning with '~' or '~user'.221# '~' means $HOME; '~user' means that user's home directory.222# If the path doesn't begin with '~', or if the user or $HOME is unknown,223# the path is returned unchanged (leaving error reporting to whatever224# function is called with the expanded path as argument).225# See also module 'glob' for expansion of *, ? and [...] in pathnames.226# (A function should also be defined to do full *sh-style environment227# variable expansion.)228 229def expanduser(path):230 """Expand ~ and ~user constructions. If user or $HOME is unknown,231 do nothing."""232 path = os.fspath(path)233 if isinstance(path, bytes):234 tilde = b'~'235 else:236 tilde = '~'237 if not path.startswith(tilde):238 return path239 sep = _get_sep(path)240 i = path.find(sep, 1)241 if i < 0:242 i = len(path)243 if i == 1:244 if 'HOME' not in os.environ:245 try:246 import pwd247 except ImportError:248 # pwd module unavailable, return path unchanged249 return path250 try:251 userhome = pwd.getpwuid(os.getuid()).pw_dir252 except KeyError:253 # bpo-10496: if the current user identifier doesn't exist in the254 # password database, return the path unchanged255 return path256 else:257 userhome = os.environ['HOME']258 else:259 try:260 import pwd261 except ImportError:262 # pwd module unavailable, return path unchanged263 return path264 name = path[1:i]265 if isinstance(name, bytes):266 name = os.fsdecode(name)267 try:268 pwent = pwd.getpwnam(name)269 except KeyError:270 # bpo-10496: if the user name from the path doesn't exist in the271 # password database, return the path unchanged272 return path273 userhome = pwent.pw_dir274 # if no user home, return the path unchanged on VxWorks275 if userhome is None and sys.platform == "vxworks":276 return path277 if isinstance(path, bytes):278 userhome = os.fsencode(userhome)279 userhome = userhome.rstrip(sep)280 return (userhome + path[i:]) or sep281 282 283# Expand paths containing shell variable substitutions.284# This expands the forms $variable and ${variable} only.285# Non-existent variables are left unchanged.286 287_varpattern = r'\$(\w+|\{[^}]*\}?)'288_varsub = None289_varsubb = None290 291def expandvars(path):292 """Expand shell variables of form $var and ${var}. Unknown variables293 are left unchanged."""294 path = os.fspath(path)295 global _varsub, _varsubb296 if isinstance(path, bytes):297 if b'$' not in path:298 return path299 if not _varsubb:300 import re301 _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub302 sub = _varsubb303 start = b'{'304 end = b'}'305 environ = getattr(os, 'environb', None)306 else:307 if '$' not in path:308 return path309 if not _varsub:310 import re311 _varsub = re.compile(_varpattern, re.ASCII).sub312 sub = _varsub313 start = '{'314 end = '}'315 environ = os.environ316 317 def repl(m):318 name = m[1]319 if name.startswith(start):320 if not name.endswith(end):321 return m[0]322 name = name[1:-1]323 try:324 if environ is None:325 value = os.fsencode(os.environ[os.fsdecode(name)])326 else:327 value = environ[name]328 except KeyError:329 return m[0]330 else:331 return value332 333 return sub(repl, path)334 335 336# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.337# It should be understood that this may change the meaning of the path338# if it contains symbolic links!339 340try:341 from posix import _path_normpath as normpath342 343except ImportError:344 def normpath(path):345 """Normalize path, eliminating double slashes, etc."""346 path = os.fspath(path)347 if isinstance(path, bytes):348 sep = b'/'349 dot = b'.'350 dotdot = b'..'351 else:352 sep = '/'353 dot = '.'354 dotdot = '..'355 if not path:356 return dot357 _, initial_slashes, path = splitroot(path)358 comps = path.split(sep)359 new_comps = []360 for comp in comps:361 if not comp or comp == dot:362 continue363 if (comp != dotdot or (not initial_slashes and not new_comps) or364 (new_comps and new_comps[-1] == dotdot)):365 new_comps.append(comp)366 elif new_comps:367 new_comps.pop()368 comps = new_comps369 path = initial_slashes + sep.join(comps)370 return path or dot371 372 373def abspath(path):374 """Return an absolute path."""375 path = os.fspath(path)376 if isinstance(path, bytes):377 if not path.startswith(b'/'):378 path = join(os.getcwdb(), path)379 else:380 if not path.startswith('/'):381 path = join(os.getcwd(), path)382 return normpath(path)383 384 385# Return a canonical path (i.e. the absolute location of a file on the386# filesystem).387 388def realpath(filename, *, strict=False):389 """Return the canonical path of the specified filename, eliminating any390symbolic links encountered in the path."""391 filename = os.fspath(filename)392 if isinstance(filename, bytes):393 sep = b'/'394 curdir = b'.'395 pardir = b'..'396 getcwd = os.getcwdb397 else:398 sep = '/'399 curdir = '.'400 pardir = '..'401 getcwd = os.getcwd402 if strict is ALLOW_MISSING:403 ignored_error = FileNotFoundError404 strict = True405 elif strict:406 ignored_error = ()407 else:408 ignored_error = OSError409 410 lstat = os.lstat411 readlink = os.readlink412 maxlinks = None413 414 # The stack of unresolved path parts. When popped, a special value of None415 # indicates that a symlink target has been resolved, and that the original416 # symlink path can be retrieved by popping again. The [::-1] slice is a417 # very fast way of spelling list(reversed(...)).418 rest = filename.split(sep)[::-1]419 420 # Number of unprocessed parts in 'rest'. This can differ from len(rest)421 # later, because 'rest' might contain markers for unresolved symlinks.422 part_count = len(rest)423 424 # The resolved path, which is absolute throughout this function.425 # Note: getcwd() returns a normalized and symlink-free path.426 path = sep if filename.startswith(sep) else getcwd()427 428 # Mapping from symlink paths to *fully resolved* symlink targets. If a429 # symlink is encountered but not yet resolved, the value is None. This is430 # used both to detect symlink loops and to speed up repeated traversals of431 # the same links.432 seen = {}433 434 # Number of symlinks traversed. When the number of traversals is limited435 # by *maxlinks*, this is used instead of *seen* to detect symlink loops.436 link_count = 0437 438 while part_count:439 name = rest.pop()440 if name is None:441 # resolved symlink target442 seen[rest.pop()] = path443 continue444 part_count -= 1445 if not name or name == curdir:446 # current dir447 continue448 if name == pardir:449 # parent dir450 path = path[:path.rindex(sep)] or sep451 continue452 if path == sep:453 newpath = path + name454 else:455 newpath = path + sep + name456 try:457 st_mode = lstat(newpath).st_mode458 if not stat.S_ISLNK(st_mode):459 if strict and part_count and not stat.S_ISDIR(st_mode):460 raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR),461 newpath)462 path = newpath463 continue464 elif maxlinks is not None:465 link_count += 1466 if link_count > maxlinks:467 if strict:468 raise OSError(errno.ELOOP, os.strerror(errno.ELOOP),469 newpath)470 path = newpath471 continue472 elif newpath in seen:473 # Already seen this path474 path = seen[newpath]475 if path is not None:476 # use cached value477 continue478 # The symlink is not resolved, so we must have a symlink loop.479 if strict:480 raise OSError(errno.ELOOP, os.strerror(errno.ELOOP),481 newpath)482 path = newpath483 continue484 target = readlink(newpath)485 except ignored_error:486 pass487 else:488 # Resolve the symbolic link489 if target.startswith(sep):490 # Symlink target is absolute; reset resolved path.491 path = sep492 if maxlinks is None:493 # Mark this symlink as seen but not fully resolved.494 seen[newpath] = None495 # Push the symlink path onto the stack, and signal its specialness496 # by also pushing None. When these entries are popped, we'll497 # record the fully-resolved symlink target in the 'seen' mapping.498 rest.append(newpath)499 rest.append(None)500 # Push the unresolved symlink target parts onto the stack.501 target_parts = target.split(sep)[::-1]502 rest.extend(target_parts)503 part_count += len(target_parts)504 continue505 # An error occurred and was ignored.506 path = newpath507 508 return path509 510 511supports_unicode_filenames = (sys.platform == 'darwin')512 513def relpath(path, start=None):514 """Return a relative version of a path"""515 516 path = os.fspath(path)517 if not path:518 raise ValueError("no path specified")519 520 if isinstance(path, bytes):521 curdir = b'.'522 sep = b'/'523 pardir = b'..'524 else:525 curdir = '.'526 sep = '/'527 pardir = '..'528 529 if start is None:530 start = curdir531 else:532 start = os.fspath(start)533 534 try:535 start_tail = abspath(start).lstrip(sep)536 path_tail = abspath(path).lstrip(sep)537 start_list = start_tail.split(sep) if start_tail else []538 path_list = path_tail.split(sep) if path_tail else []539 # Work out how much of the filepath is shared by start and path.540 i = len(commonprefix([start_list, path_list]))541 542 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]543 if not rel_list:544 return curdir545 return sep.join(rel_list)546 except (TypeError, AttributeError, BytesWarning, DeprecationWarning):547 genericpath._check_arg_types('relpath', path, start)548 raise549 550 551# Return the longest common sub-path of the sequence of paths given as input.552# The paths are not normalized before comparing them (this is the553# responsibility of the caller). Any trailing separator is stripped from the554# returned path.555 556def commonpath(paths):557 """Given a sequence of path names, returns the longest common sub-path."""558 559 paths = tuple(map(os.fspath, paths))560 561 if not paths:562 raise ValueError('commonpath() arg is an empty sequence')563 564 if isinstance(paths[0], bytes):565 sep = b'/'566 curdir = b'.'567 else:568 sep = '/'569 curdir = '.'570 571 try:572 split_paths = [path.split(sep) for path in paths]573 574 try:575 isabs, = {p.startswith(sep) for p in paths}576 except ValueError:577 raise ValueError("Can't mix absolute and relative paths") from None578 579 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]580 s1 = min(split_paths)581 s2 = max(split_paths)582 common = s1583 for i, c in enumerate(s1):584 if c != s2[i]:585 common = s1[:i]586 break587 588 prefix = sep if isabs else sep[:0]589 return prefix + sep.join(common)590 except (TypeError, AttributeError):591 genericpath._check_arg_types('commonpath', *paths)592 raise593 