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