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