1# Module 'ntpath' -- common operations on WinNT/Win95 pathnames 2"""Common pathname manipulations, WindowsNT/95 version. 3 4Instead of importing this module directly, import os and refer to this 5module as os.path. 6""" 7 8# strings representing various path-related bits and pieces 9# These are primarily for export; internally, they are hardcoded. 10# Should be set before imports for resolving cyclic dependency. 11curdir = '.' 12pardir = '..' 13extsep = '.' 14sep = '\\' 15pathsep = ';' 16altsep = '/' 17defpath = '.;C:\\bin' 18devnull = 'nul' 19 20import os 21import sys 22import stat 23import genericpath 24from genericpath import * 25 26 27__all__ = ["normcase","isabs","join","splitdrive","split","splitext", 28 "basename","dirname","commonprefix","getsize","getmtime", 29 "getatime","getctime", "islink","exists","lexists","isdir","isfile", 30 "ismount", "expanduser","expandvars","normpath","abspath", 31 "curdir","pardir","sep","pathsep","defpath","altsep", 32 "extsep","devnull","realpath","supports_unicode_filenames","relpath", 33 "samefile", "sameopenfile", "samestat", "commonpath", 34 "ALLOW_MISSING"] 35 36def _get_bothseps(path): 37 if isinstance(path, bytes): 38 return b'\\/' 39 else: 40 return '\\/' 41 42# Normalize the case of a pathname and map slashes to backslashes. 43# Other normalizations (such as optimizing '../' away) are not done 44# (this is done by normpath). 45 46try: 47 from _winapi import ( 48 LCMapStringEx as _LCMapStringEx, 49 LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT, 50 LCMAP_LOWERCASE as _LCMAP_LOWERCASE) 51 52 def normcase(s): 53 """Normalize case of pathname. 54 55 Makes all characters lowercase and all slashes into backslashes. 56 """ 57 s = os.fspath(s) 58 if not s: 59 return s 60 if isinstance(s, bytes): 61 encoding = sys.getfilesystemencoding() 62 s = s.decode(encoding, 'surrogateescape').replace('/', '\\') 63 s = _LCMapStringEx(_LOCALE_NAME_INVARIANT, 64 _LCMAP_LOWERCASE, s) 65 return s.encode(encoding, 'surrogateescape') 66 else: 67 return _LCMapStringEx(_LOCALE_NAME_INVARIANT, 68 _LCMAP_LOWERCASE, 69 s.replace('/', '\\')) 70except ImportError: 71 def normcase(s): 72 """Normalize case of pathname. 73 74 Makes all characters lowercase and all slashes into backslashes. 75 """ 76 s = os.fspath(s) 77 if isinstance(s, bytes): 78 return os.fsencode(os.fsdecode(s).replace('/', '\\').lower()) 79 return s.replace('/', '\\').lower() 80 81 82# Return whether a path is absolute. 83# Trivial in Posix, harder on Windows. 84# For Windows it is absolute if it starts with a slash or backslash (current 85# volume), or if a pathname after the volume-letter-and-colon or UNC-resource 86# starts with a slash or backslash. 87 88def isabs(s): 89 """Test whether a path is absolute""" 90 s = os.fspath(s) 91 if isinstance(s, bytes): 92 sep = b'\\' 93 altsep = b'/' 94 colon_sep = b':\\' 95 else: 96 sep = '\\' 97 altsep = '/' 98 colon_sep = ':\\' 99 s = s[:3].replace(altsep, sep) 100 # Absolute: UNC, device, and paths with a drive and root. 101 # LEGACY BUG: isabs("/x") should be false since the path has no drive. 102 if s.startswith(sep) or s.startswith(colon_sep, 1): 103 return True 104 return False 105 106 107# Join two (or more) paths. 108def join(path, *paths): 109 path = os.fspath(path) 110 if isinstance(path, bytes): 111 sep = b'\\' 112 seps = b'\\/' 113 colon = b':' 114 else: 115 sep = '\\' 116 seps = '\\/' 117 colon = ':' 118 try: 119 if not paths: 120 path[:0] + sep #23780: Ensure compatible data type even if p is null. 121 result_drive, result_path = splitdrive(path) 122 for p in map(os.fspath, paths): 123 p_drive, p_path = splitdrive(p) 124 if p_path and p_path[0] in seps: 125 # Second path is absolute 126 if p_drive or not result_drive: 127 result_drive = p_drive 128 result_path = p_path 129 continue 130 elif p_drive and p_drive != result_drive: 131 if p_drive.lower() != result_drive.lower(): 132 # Different drives => ignore the first path entirely 133 result_drive = p_drive 134 result_path = p_path 135 continue 136 # Same drive in different case 137 result_drive = p_drive 138 # Second path is relative to the first 139 if result_path and result_path[-1] not in seps: 140 result_path = result_path + sep 141 result_path = result_path + p_path 142 ## add separator between UNC and non-absolute path 143 if (result_path and result_path[0] not in seps and 144 result_drive and result_drive[-1:] != colon): 145 return result_drive + sep + result_path 146 return result_drive + result_path 147 except (TypeError, AttributeError, BytesWarning): 148 genericpath._check_arg_types('join', path, *paths) 149 raise 150 151 152# Split a path in a drive specification (a drive letter followed by a 153# colon) and the path specification. 154# It is always true that drivespec + pathspec == p 155def splitdrive(p): 156 """Split a pathname into drive/UNC sharepoint and relative path specifiers. 157 Returns a 2-tuple (drive_or_unc, path); either part may be empty. 158 159 If you assign 160 result = splitdrive(p) 161 It is always true that: 162 result[0] + result[1] == p 163 164 If the path contained a drive letter, drive_or_unc will contain everything 165 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir") 166 167 If the path contained a UNC path, the drive_or_unc will contain the host name 168 and share up to but not including the fourth directory separator character. 169 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir") 170 171 Paths cannot contain both a drive letter and a UNC path. 172 173 """ 174 p = os.fspath(p) 175 if len(p) >= 2: 176 if isinstance(p, bytes): 177 sep = b'\\' 178 altsep = b'/' 179 colon = b':' 180 unc_prefix = b'\\\\?\\UNC\\' 181 else: 182 sep = '\\' 183 altsep = '/' 184 colon = ':' 185 unc_prefix = '\\\\?\\UNC\\' 186 normp = p.replace(altsep, sep) 187 if normp[0:2] == sep * 2: 188 # UNC drives, e.g. \\server\share or \\?\UNC\server\share 189 # Device drives, e.g. \\.\device or \\?\device 190 start = 8 if normp[:8].upper() == unc_prefix else 2 191 index = normp.find(sep, start) 192 if index == -1: 193 return p, p[:0] 194 index2 = normp.find(sep, index + 1) 195 if index2 == -1: 196 return p, p[:0] 197 return p[:index2], p[index2:] 198 if normp[1:2] == colon: 199 # Drive-letter drives, e.g. X: 200 return p[:2], p[2:] 201 return p[:0], p 202 203 204# Split a path in head (everything up to the last '/') and tail (the 205# rest). After the trailing '/' is stripped, the invariant 206# join(head, tail) == p holds. 207# The resulting head won't end in '/' unless it is the root. 208 209def split(p): 210 """Split a pathname. 211 212 Return tuple (head, tail) where tail is everything after the final slash. 213 Either part may be empty.""" 214 p = os.fspath(p) 215 seps = _get_bothseps(p) 216 d, p = splitdrive(p) 217 # set i to index beyond p's last slash 218 i = len(p) 219 while i and p[i-1] not in seps: 220 i -= 1 221 head, tail = p[:i], p[i:] # now tail has no slashes 222 # remove trailing slashes from head, unless it's all slashes 223 head = head.rstrip(seps) or head 224 return d + head, tail 225 226 227# Split a path in root and extension. 228# The extension is everything starting at the last dot in the last 229# pathname component; the root is everything before that. 230# It is always true that root + ext == p. 231 232def splitext(p): 233 p = os.fspath(p) 234 if isinstance(p, bytes): 235 return genericpath._splitext(p, b'\\', b'/', b'.') 236 else: 237 return genericpath._splitext(p, '\\', '/', '.') 238splitext.__doc__ = genericpath._splitext.__doc__ 239 240 241# Return the tail (basename) part of a path. 242 243def basename(p): 244 """Returns the final component of a pathname""" 245 return split(p)[1] 246 247 248# Return the head (dirname) part of a path. 249 250def dirname(p): 251 """Returns the directory component of a pathname""" 252 return split(p)[0] 253 254# Is a path a symbolic link? 255# This will always return false on systems where os.lstat doesn't exist. 256 257def islink(path): 258 """Test whether a path is a symbolic link. 259 This will always return false for Windows prior to 6.0. 260 """ 261 try: 262 st = os.lstat(path) 263 except (OSError, ValueError, AttributeError): 264 return False 265 return stat.S_ISLNK(st.st_mode) 266 267# Being true for dangling symbolic links is also useful. 268 269def lexists(path): 270 """Test whether a path exists. Returns True for broken symbolic links""" 271 try: 272 st = os.lstat(path) 273 except (OSError, ValueError): 274 return False 275 return True 276 277# Is a path a mount point? 278# Any drive letter root (eg c:\) 279# Any share UNC (eg \\server\share) 280# Any volume mounted on a filesystem folder 281# 282# No one method detects all three situations. Historically we've lexically 283# detected drive letter roots and share UNCs. The canonical approach to 284# detecting mounted volumes (querying the reparse tag) fails for the most 285# common case: drive letter roots. The alternative which uses GetVolumePathName 286# fails if the drive letter is the result of a SUBST. 287try: 288 from nt import _getvolumepathname 289except ImportError: 290 _getvolumepathname = None 291def ismount(path): 292 """Test whether a path is a mount point (a drive root, the root of a 293 share, or a mounted volume)""" 294 path = os.fspath(path) 295 seps = _get_bothseps(path) 296 path = abspath(path) 297 root, rest = splitdrive(path) 298 if root and root[0] in seps: 299 return (not rest) or (rest in seps) 300 if rest and rest in seps: 301 return True 302 303 if _getvolumepathname: 304 x = path.rstrip(seps) 305 y =_getvolumepathname(path).rstrip(seps) 306 return x.casefold() == y.casefold() 307 else: 308 return False 309 310 311# Expand paths beginning with '~' or '~user'. 312# '~' means $HOME; '~user' means that user's home directory. 313# If the path doesn't begin with '~', or if the user or $HOME is unknown, 314# the path is returned unchanged (leaving error reporting to whatever 315# function is called with the expanded path as argument). 316# See also module 'glob' for expansion of *, ? and [...] in pathnames. 317# (A function should also be defined to do full *sh-style environment 318# variable expansion.) 319 320def expanduser(path): 321 """Expand ~ and ~user constructs. 322 323 If user or $HOME is unknown, do nothing.""" 324 path = os.fspath(path) 325 if isinstance(path, bytes): 326 tilde = b'~' 327 else: 328 tilde = '~' 329 if not path.startswith(tilde): 330 return path 331 i, n = 1, len(path) 332 while i < n and path[i] not in _get_bothseps(path): 333 i += 1 334 335 if 'USERPROFILE' in os.environ: 336 userhome = os.environ['USERPROFILE'] 337 elif not 'HOMEPATH' in os.environ: 338 return path 339 else: 340 try: 341 drive = os.environ['HOMEDRIVE'] 342 except KeyError: 343 drive = '' 344 userhome = join(drive, os.environ['HOMEPATH']) 345 346 if i != 1: #~user 347 target_user = path[1:i] 348 if isinstance(target_user, bytes): 349 target_user = os.fsdecode(target_user) 350 current_user = os.environ.get('USERNAME') 351 352 if target_user != current_user: 353 # Try to guess user home directory. By default all user 354 # profile directories are located in the same place and are 355 # named by corresponding usernames. If userhome isn't a 356 # normal profile directory, this guess is likely wrong, 357 # so we bail out. 358 if current_user != basename(userhome): 359 return path 360 userhome = join(dirname(userhome), target_user) 361 362 if isinstance(path, bytes): 363 userhome = os.fsencode(userhome) 364 365 return userhome + path[i:] 366 367 368# Expand paths containing shell variable substitutions. 369# The following rules apply: 370# - no expansion within single quotes 371# - '$$' is translated into '$' 372# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% 373# - ${varname} is accepted. 374# - $varname is accepted. 375# - %varname% is accepted. 376# - varnames can be made out of letters, digits and the characters '_-' 377# (though is not verified in the ${varname} and %varname% cases) 378# XXX With COMMAND.COM you can use any characters in a variable name, 379# XXX except '^|<>='. 380 381def expandvars(path): 382 """Expand shell variables of the forms $var, ${var} and %var%. 383 384 Unknown variables are left unchanged.""" 385 path = os.fspath(path) 386 if isinstance(path, bytes): 387 if b'$' not in path and b'%' not in path: 388 return path 389 import string 390 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii') 391 quote = b'\'' 392 percent = b'%' 393 brace = b'{' 394 rbrace = b'}' 395 dollar = b'$' 396 environ = getattr(os, 'environb', None) 397 else: 398 if '$' not in path and '%' not in path: 399 return path 400 import string 401 varchars = string.ascii_letters + string.digits + '_-' 402 quote = '\'' 403 percent = '%' 404 brace = '{' 405 rbrace = '}' 406 dollar = '$' 407 environ = os.environ 408 res = path[:0] 409 index = 0 410 pathlen = len(path) 411 while index < pathlen: 412 c = path[index:index+1] 413 if c == quote: # no expansion within single quotes 414 path = path[index + 1:] 415 pathlen = len(path) 416 try: 417 index = path.index(c) 418 res += c + path[:index + 1] 419 except ValueError: 420 res += c + path 421 index = pathlen - 1 422 elif c == percent: # variable or '%' 423 if path[index + 1:index + 2] == percent: 424 res += c 425 index += 1 426 else: 427 path = path[index+1:] 428 pathlen = len(path) 429 try: 430 index = path.index(percent) 431 except ValueError: 432 res += percent + path 433 index = pathlen - 1 434 else: 435 var = path[:index] 436 try: 437 if environ is None: 438 value = os.fsencode(os.environ[os.fsdecode(var)]) 439 else: 440 value = environ[var] 441 except KeyError: 442 value = percent + var + percent 443 res += value 444 elif c == dollar: # variable or '$$' 445 if path[index + 1:index + 2] == dollar: 446 res += c 447 index += 1 448 elif path[index + 1:index + 2] == brace: 449 path = path[index+2:] 450 pathlen = len(path) 451 try: 452 index = path.index(rbrace) 453 except ValueError: 454 res += dollar + brace + path 455 index = pathlen - 1 456 else: 457 var = path[:index] 458 try: 459 if environ is None: 460 value = os.fsencode(os.environ[os.fsdecode(var)]) 461 else: 462 value = environ[var] 463 except KeyError: 464 value = dollar + brace + var + rbrace 465 res += value 466 else: 467 var = path[:0] 468 index += 1 469 c = path[index:index + 1] 470 while c and c in varchars: 471 var += c 472 index += 1 473 c = path[index:index + 1] 474 try: 475 if environ is None: 476 value = os.fsencode(os.environ[os.fsdecode(var)]) 477 else: 478 value = environ[var] 479 except KeyError: 480 value = dollar + var 481 res += value 482 if c: 483 index -= 1 484 else: 485 res += c 486 index += 1 487 return res 488 489 490# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. 491# Previously, this function also truncated pathnames to 8+3 format, 492# but as this module is called "ntpath", that's obviously wrong! 493try: 494 from nt import _path_normpath 495 496except ImportError: 497 def normpath(path): 498 """Normalize path, eliminating double slashes, etc.""" 499 path = os.fspath(path) 500 if isinstance(path, bytes): 501 sep = b'\\' 502 altsep = b'/' 503 curdir = b'.' 504 pardir = b'..' 505 else: 506 sep = '\\' 507 altsep = '/' 508 curdir = '.' 509 pardir = '..' 510 path = path.replace(altsep, sep) 511 prefix, path = splitdrive(path) 512 513 # collapse initial backslashes 514 if path.startswith(sep): 515 prefix += sep 516 path = path.lstrip(sep) 517 518 comps = path.split(sep) 519 i = 0 520 while i < len(comps): 521 if not comps[i] or comps[i] == curdir: 522 del comps[i] 523 elif comps[i] == pardir: 524 if i > 0 and comps[i-1] != pardir: 525 del comps[i-1:i+1] 526 i -= 1 527 elif i == 0 and prefix.endswith(sep): 528 del comps[i] 529 else: 530 i += 1 531 else: 532 i += 1 533 # If the path is now empty, substitute '.' 534 if not prefix and not comps: 535 comps.append(curdir) 536 return prefix + sep.join(comps) 537 538else: 539 def normpath(path): 540 """Normalize path, eliminating double slashes, etc.""" 541 path = os.fspath(path) 542 if isinstance(path, bytes): 543 return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." 544 return _path_normpath(path) or "." 545 546 547def _abspath_fallback(path): 548 """Return the absolute version of a path as a fallback function in case 549 `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for 550 more. 551 552 """ 553 554 path = os.fspath(path) 555 if not isabs(path): 556 if isinstance(path, bytes): 557 cwd = os.getcwdb() 558 else: 559 cwd = os.getcwd() 560 path = join(cwd, path) 561 return normpath(path) 562 563# Return an absolute path. 564try: 565 from nt import _getfullpathname 566 567except ImportError: # not running on Windows - mock up something sensible 568 abspath = _abspath_fallback 569 570else: # use native Windows method on Windows 571 def abspath(path): 572 """Return the absolute version of a path.""" 573 try: 574 return _getfullpathname(normpath(path)) 575 except (OSError, ValueError): 576 return _abspath_fallback(path) 577 578try: 579 from nt import _getfinalpathname, readlink as _nt_readlink 580except ImportError: 581 # realpath is a no-op on systems without _getfinalpathname support. 582 def realpath(path, *, strict=False): 583 return abspath(path) 584else: 585 def _readlink_deep(path, ignored_error=OSError): 586 # These error codes indicate that we should stop reading links and 587 # return the path we currently have. 588 # 1: ERROR_INVALID_FUNCTION 589 # 2: ERROR_FILE_NOT_FOUND 590 # 3: ERROR_DIRECTORY_NOT_FOUND 591 # 5: ERROR_ACCESS_DENIED 592 # 21: ERROR_NOT_READY (implies drive with no media) 593 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file) 594 # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points) 595 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable) 596 # 87: ERROR_INVALID_PARAMETER 597 # 4390: ERROR_NOT_A_REPARSE_POINT 598 # 4392: ERROR_INVALID_REPARSE_DATA 599 # 4393: ERROR_REPARSE_TAG_INVALID 600 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393 601 602 seen = set() 603 while normcase(path) not in seen: 604 seen.add(normcase(path)) 605 try: 606 old_path = path 607 path = _nt_readlink(path) 608 # Links may be relative, so resolve them against their 609 # own location 610 if not isabs(path): 611 # If it's something other than a symlink, we don't know 612 # what it's actually going to be resolved against, so 613 # just return the old path. 614 if not islink(old_path): 615 path = old_path 616 break 617 path = normpath(join(dirname(old_path), path)) 618 except ignored_error as ex: 619 if ex.winerror in allowed_winerror: 620 break 621 raise 622 except ValueError: 623 # Stop on reparse points that are not symlinks 624 break 625 return path 626 627 def _getfinalpathname_nonstrict(path, ignored_error=OSError): 628 # These error codes indicate that we should stop resolving the path 629 # and return the value we currently have. 630 # 1: ERROR_INVALID_FUNCTION 631 # 2: ERROR_FILE_NOT_FOUND 632 # 3: ERROR_DIRECTORY_NOT_FOUND 633 # 5: ERROR_ACCESS_DENIED 634 # 21: ERROR_NOT_READY (implies drive with no media) 635 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file) 636 # 50: ERROR_NOT_SUPPORTED 637 # 53: ERROR_BAD_NETPATH 638 # 65: ERROR_NETWORK_ACCESS_DENIED 639 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable) 640 # 87: ERROR_INVALID_PARAMETER 641 # 123: ERROR_INVALID_NAME 642 # 161: ERROR_BAD_PATHNAME 643 # 1920: ERROR_CANT_ACCESS_FILE 644 # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink) 645 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1920, 1921 646 647 # Non-strict algorithm is to find as much of the target directory 648 # as we can and join the rest. 649 tail = path[:0] 650 while path: 651 try: 652 path = _getfinalpathname(path) 653 return join(path, tail) if tail else path 654 except ignored_error as ex: 655 if ex.winerror not in allowed_winerror: 656 raise 657 try: 658 # The OS could not resolve this path fully, so we attempt 659 # to follow the link ourselves. If we succeed, join the tail 660 # and return. 661 new_path = _readlink_deep(path, 662 ignored_error=ignored_error) 663 if new_path != path: 664 return join(new_path, tail) if tail else new_path 665 except ignored_error: 666 # If we fail to readlink(), let's keep traversing 667 pass 668 path, name = split(path) 669 # TODO (bpo-38186): Request the real file name from the directory 670 # entry using FindFirstFileW. For now, we will return the path 671 # as best we have it 672 if path and not name: 673 return path + tail 674 tail = join(name, tail) if tail else name 675 return tail 676 677 def realpath(path, *, strict=False): 678 path = normpath(path) 679 if isinstance(path, bytes): 680 prefix = b'\\\\?\\' 681 unc_prefix = b'\\\\?\\UNC\\' 682 new_unc_prefix = b'\\\\' 683 cwd = os.getcwdb() 684 # bpo-38081: Special case for realpath(b'nul') 685 if normcase(path) == normcase(os.fsencode(devnull)): 686 return b'\\\\.\\NUL' 687 else: 688 prefix = '\\\\?\\' 689 unc_prefix = '\\\\?\\UNC\\' 690 new_unc_prefix = '\\\\' 691 cwd = os.getcwd() 692 # bpo-38081: Special case for realpath('nul') 693 if normcase(path) == normcase(devnull): 694 return '\\\\.\\NUL' 695 had_prefix = path.startswith(prefix) 696 697 if strict is ALLOW_MISSING: 698 ignored_error = FileNotFoundError 699 strict = True 700 elif strict: 701 ignored_error = () 702 else: 703 ignored_error = OSError 704 705 if not had_prefix and not isabs(path): 706 path = join(cwd, path) 707 try: 708 path = _getfinalpathname(path) 709 initial_winerror = 0 710 except ValueError as ex: 711 # gh-106242: Raised for embedded null characters 712 # In strict modes, we convert into an OSError. 713 # Non-strict mode returns the path as-is, since we've already 714 # made it absolute. 715 if strict: 716 raise OSError(str(ex)) from None 717 path = normpath(path) 718 except ignored_error as ex: 719 initial_winerror = ex.winerror 720 path = _getfinalpathname_nonstrict(path, 721 ignored_error=ignored_error) 722 # The path returned by _getfinalpathname will always start with \\?\ - 723 # strip off that prefix unless it was already provided on the original 724 # path. 725 if not had_prefix and path.startswith(prefix): 726 # For UNC paths, the prefix will actually be \\?\UNC\ 727 # Handle that case as well. 728 if path.startswith(unc_prefix): 729 spath = new_unc_prefix + path[len(unc_prefix):] 730 else: 731 spath = path[len(prefix):] 732 # Ensure that the non-prefixed path resolves to the same path 733 try: 734 if _getfinalpathname(spath) == path: 735 path = spath 736 except ValueError as ex: 737 # Unexpected, as an invalid path should not have gained a prefix 738 # at any point, but we ignore this error just in case. 739 pass 740 except OSError as ex: 741 # If the path does not exist and originally did not exist, then 742 # strip the prefix anyway. 743 if ex.winerror == initial_winerror: 744 path = spath 745 return path 746 747 748# Win9x family and earlier have no Unicode filename support. 749supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and 750 sys.getwindowsversion()[3] >= 2) 751 752def relpath(path, start=None): 753 """Return a relative version of a path""" 754 path = os.fspath(path) 755 if isinstance(path, bytes): 756 sep = b'\\' 757 curdir = b'.' 758 pardir = b'..' 759 else: 760 sep = '\\' 761 curdir = '.' 762 pardir = '..' 763 764 if start is None: 765 start = curdir 766 767 if not path: 768 raise ValueError("no path specified") 769 770 start = os.fspath(start) 771 try: 772 start_abs = abspath(normpath(start)) 773 path_abs = abspath(normpath(path)) 774 start_drive, start_rest = splitdrive(start_abs) 775 path_drive, path_rest = splitdrive(path_abs) 776 if normcase(start_drive) != normcase(path_drive): 777 raise ValueError("path is on mount %r, start on mount %r" % ( 778 path_drive, start_drive)) 779 780 start_list = [x for x in start_rest.split(sep) if x] 781 path_list = [x for x in path_rest.split(sep) if x] 782 # Work out how much of the filepath is shared by start and path. 783 i = 0 784 for e1, e2 in zip(start_list, path_list): 785 if normcase(e1) != normcase(e2): 786 break 787 i += 1 788 789 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 790 if not rel_list: 791 return curdir 792 return join(*rel_list) 793 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning): 794 genericpath._check_arg_types('relpath', path, start) 795 raise 796 797 798# Return the longest common sub-path of the sequence of paths given as input. 799# The function is case-insensitive and 'separator-insensitive', i.e. if the 800# only difference between two paths is the use of '\' versus '/' as separator, 801# they are deemed to be equal. 802# 803# However, the returned path will have the standard '\' separator (even if the 804# given paths had the alternative '/' separator) and will have the case of the 805# first path given in the sequence. Additionally, any trailing separator is 806# stripped from the returned path. 807 808def commonpath(paths): 809 """Given a sequence of path names, returns the longest common sub-path.""" 810 811 if not paths: 812 raise ValueError('commonpath() arg is an empty sequence') 813 814 paths = tuple(map(os.fspath, paths)) 815 if isinstance(paths[0], bytes): 816 sep = b'\\' 817 altsep = b'/' 818 curdir = b'.' 819 else: 820 sep = '\\' 821 altsep = '/' 822 curdir = '.' 823 824 try: 825 drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths] 826 split_paths = [p.split(sep) for d, p in drivesplits] 827 828 try: 829 isabs, = set(p[:1] == sep for d, p in drivesplits) 830 except ValueError: 831 raise ValueError("Can't mix absolute and relative paths") from None 832 833 # Check that all drive letters or UNC paths match. The check is made only 834 # now otherwise type errors for mixing strings and bytes would not be 835 # caught. 836 if len(set(d for d, p in drivesplits)) != 1: 837 raise ValueError("Paths don't have the same drive") 838 839 drive, path = splitdrive(paths[0].replace(altsep, sep)) 840 common = path.split(sep) 841 common = [c for c in common if c and c != curdir] 842 843 split_paths = [[c for c in s if c and c != curdir] for s in split_paths] 844 s1 = min(split_paths) 845 s2 = max(split_paths) 846 for i, c in enumerate(s1): 847 if c != s2[i]: 848 common = common[:i] 849 break 850 else: 851 common = common[:len(s1)] 852 853 prefix = drive + sep if isabs else drive 854 return prefix + sep.join(common) 855 except (TypeError, AttributeError): 856 genericpath._check_arg_types('commonpath', *paths) 857 raise 858 859 860try: 861 # The genericpath.isdir implementation uses os.stat and checks the mode 862 # attribute to tell whether or not the path is a directory. 863 # This is overkill on Windows - just pass the path to GetFileAttributes 864 # and check the attribute from there. 865 from nt import _isdir as isdir 866except ImportError: 867 # Use genericpath.isdir as imported above. 868 pass 869