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] 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 and rest in seps: 272 return True 273 274 if _getvolumepathname: 275 x = path.rstrip(seps) 276 y =_getvolumepathname(path).rstrip(seps) 277 return x.casefold() == y.casefold() 278 else: 279 return False 280 281 282# Expand paths beginning with '~' or '~user'. 283# '~' means $HOME; '~user' means that user's home directory. 284# If the path doesn't begin with '~', or if the user or $HOME is unknown, 285# the path is returned unchanged (leaving error reporting to whatever 286# function is called with the expanded path as argument). 287# See also module 'glob' for expansion of *, ? and [...] in pathnames. 288# (A function should also be defined to do full *sh-style environment 289# variable expansion.) 290 291def expanduser(path): 292 """Expand ~ and ~user constructs. 293 294 If user or $HOME is unknown, do nothing.""" 295 path = os.fspath(path) 296 if isinstance(path, bytes): 297 tilde = b'~' 298 else: 299 tilde = '~' 300 if not path.startswith(tilde): 301 return path 302 i, n = 1, len(path) 303 while i < n and path[i] not in _get_bothseps(path): 304 i += 1 305 306 if 'USERPROFILE' in os.environ: 307 userhome = os.environ['USERPROFILE'] 308 elif not 'HOMEPATH' in os.environ: 309 return path 310 else: 311 try: 312 drive = os.environ['HOMEDRIVE'] 313 except KeyError: 314 drive = '' 315 userhome = join(drive, os.environ['HOMEPATH']) 316 317 if i != 1: #~user 318 target_user = path[1:i] 319 if isinstance(target_user, bytes): 320 target_user = os.fsdecode(target_user) 321 current_user = os.environ.get('USERNAME') 322 323 if target_user != current_user: 324 # Try to guess user home directory. By default all user 325 # profile directories are located in the same place and are 326 # named by corresponding usernames. If userhome isn't a 327 # normal profile directory, this guess is likely wrong, 328 # so we bail out. 329 if current_user != basename(userhome): 330 return path 331 userhome = join(dirname(userhome), target_user) 332 333 if isinstance(path, bytes): 334 userhome = os.fsencode(userhome) 335 336 return userhome + path[i:] 337 338 339# Expand paths containing shell variable substitutions. 340# The following rules apply: 341# - no expansion within single quotes 342# - '$$' is translated into '$' 343# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% 344# - ${varname} is accepted. 345# - $varname is accepted. 346# - %varname% is accepted. 347# - varnames can be made out of letters, digits and the characters '_-' 348# (though is not verified in the ${varname} and %varname% cases) 349# XXX With COMMAND.COM you can use any characters in a variable name, 350# XXX except '^|<>='. 351 352def expandvars(path): 353 """Expand shell variables of the forms $var, ${var} and %var%. 354 355 Unknown variables are left unchanged.""" 356 path = os.fspath(path) 357 if isinstance(path, bytes): 358 if b'$' not in path and b'%' not in path: 359 return path 360 import string 361 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii') 362 quote = b'\'' 363 percent = b'%' 364 brace = b'{' 365 rbrace = b'}' 366 dollar = b'$' 367 environ = getattr(os, 'environb', None) 368 else: 369 if '$' not in path and '%' not in path: 370 return path 371 import string 372 varchars = string.ascii_letters + string.digits + '_-' 373 quote = '\'' 374 percent = '%' 375 brace = '{' 376 rbrace = '}' 377 dollar = '$' 378 environ = os.environ 379 res = path[:0] 380 index = 0 381 pathlen = len(path) 382 while index < pathlen: 383 c = path[index:index+1] 384 if c == quote: # no expansion within single quotes 385 path = path[index + 1:] 386 pathlen = len(path) 387 try: 388 index = path.index(c) 389 res += c + path[:index + 1] 390 except ValueError: 391 res += c + path 392 index = pathlen - 1 393 elif c == percent: # variable or '%' 394 if path[index + 1:index + 2] == percent: 395 res += c 396 index += 1 397 else: 398 path = path[index+1:] 399 pathlen = len(path) 400 try: 401 index = path.index(percent) 402 except ValueError: 403 res += percent + path 404 index = pathlen - 1 405 else: 406 var = path[:index] 407 try: 408 if environ is None: 409 value = os.fsencode(os.environ[os.fsdecode(var)]) 410 else: 411 value = environ[var] 412 except KeyError: 413 value = percent + var + percent 414 res += value 415 elif c == dollar: # variable or '$$' 416 if path[index + 1:index + 2] == dollar: 417 res += c 418 index += 1 419 elif path[index + 1:index + 2] == brace: 420 path = path[index+2:] 421 pathlen = len(path) 422 try: 423 index = path.index(rbrace) 424 except ValueError: 425 res += dollar + brace + path 426 index = pathlen - 1 427 else: 428 var = path[:index] 429 try: 430 if environ is None: 431 value = os.fsencode(os.environ[os.fsdecode(var)]) 432 else: 433 value = environ[var] 434 except KeyError: 435 value = dollar + brace + var + rbrace 436 res += value 437 else: 438 var = path[:0] 439 index += 1 440 c = path[index:index + 1] 441 while c and c in varchars: 442 var += c 443 index += 1 444 c = path[index:index + 1] 445 try: 446 if environ is None: 447 value = os.fsencode(os.environ[os.fsdecode(var)]) 448 else: 449 value = environ[var] 450 except KeyError: 451 value = dollar + var 452 res += value 453 if c: 454 index -= 1 455 else: 456 res += c 457 index += 1 458 return res 459 460 461# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. 462# Previously, this function also truncated pathnames to 8+3 format, 463# but as this module is called "ntpath", that's obviously wrong! 464try: 465 from nt import _path_normpath 466 467except ImportError: 468 def normpath(path): 469 """Normalize path, eliminating double slashes, etc.""" 470 path = os.fspath(path) 471 if isinstance(path, bytes): 472 sep = b'\\' 473 altsep = b'/' 474 curdir = b'.' 475 pardir = b'..' 476 special_prefixes = (b'\\\\.\\', b'\\\\?\\') 477 else: 478 sep = '\\' 479 altsep = '/' 480 curdir = '.' 481 pardir = '..' 482 special_prefixes = ('\\\\.\\', '\\\\?\\') 483 if path.startswith(special_prefixes): 484 # in the case of paths with these prefixes: 485 # \\.\ -> device names 486 # \\?\ -> literal paths 487 # do not do any normalization, but return the path 488 # unchanged apart from the call to os.fspath() 489 return path 490 path = path.replace(altsep, sep) 491 prefix, path = splitdrive(path) 492 493 # collapse initial backslashes 494 if path.startswith(sep): 495 prefix += sep 496 path = path.lstrip(sep) 497 498 comps = path.split(sep) 499 i = 0 500 while i < len(comps): 501 if not comps[i] or comps[i] == curdir: 502 del comps[i] 503 elif comps[i] == pardir: 504 if i > 0 and comps[i-1] != pardir: 505 del comps[i-1:i+1] 506 i -= 1 507 elif i == 0 and prefix.endswith(sep): 508 del comps[i] 509 else: 510 i += 1 511 else: 512 i += 1 513 # If the path is now empty, substitute '.' 514 if not prefix and not comps: 515 comps.append(curdir) 516 return prefix + sep.join(comps) 517 518else: 519 def normpath(path): 520 """Normalize path, eliminating double slashes, etc.""" 521 path = os.fspath(path) 522 if isinstance(path, bytes): 523 return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." 524 return _path_normpath(path) or "." 525 526 527def _abspath_fallback(path): 528 """Return the absolute version of a path as a fallback function in case 529 `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for 530 more. 531 532 """ 533 534 path = os.fspath(path) 535 if not isabs(path): 536 if isinstance(path, bytes): 537 cwd = os.getcwdb() 538 else: 539 cwd = os.getcwd() 540 path = join(cwd, path) 541 return normpath(path) 542 543# Return an absolute path. 544try: 545 from nt import _getfullpathname 546 547except ImportError: # not running on Windows - mock up something sensible 548 abspath = _abspath_fallback 549 550else: # use native Windows method on Windows 551 def abspath(path): 552 """Return the absolute version of a path.""" 553 try: 554 return normpath(_getfullpathname(path)) 555 except (OSError, ValueError): 556 return _abspath_fallback(path) 557 558try: 559 from nt import _getfinalpathname, readlink as _nt_readlink 560except ImportError: 561 # realpath is a no-op on systems without _getfinalpathname support. 562 realpath = abspath 563else: 564 def _readlink_deep(path): 565 # These error codes indicate that we should stop reading links and 566 # return the path we currently have. 567 # 1: ERROR_INVALID_FUNCTION 568 # 2: ERROR_FILE_NOT_FOUND 569 # 3: ERROR_DIRECTORY_NOT_FOUND 570 # 5: ERROR_ACCESS_DENIED 571 # 21: ERROR_NOT_READY (implies drive with no media) 572 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file) 573 # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points) 574 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable) 575 # 87: ERROR_INVALID_PARAMETER 576 # 4390: ERROR_NOT_A_REPARSE_POINT 577 # 4392: ERROR_INVALID_REPARSE_DATA 578 # 4393: ERROR_REPARSE_TAG_INVALID 579 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393 580 581 seen = set() 582 while normcase(path) not in seen: 583 seen.add(normcase(path)) 584 try: 585 old_path = path 586 path = _nt_readlink(path) 587 # Links may be relative, so resolve them against their 588 # own location 589 if not isabs(path): 590 # If it's something other than a symlink, we don't know 591 # what it's actually going to be resolved against, so 592 # just return the old path. 593 if not islink(old_path): 594 path = old_path 595 break 596 path = normpath(join(dirname(old_path), path)) 597 except OSError as ex: 598 if ex.winerror in allowed_winerror: 599 break 600 raise 601 except ValueError: 602 # Stop on reparse points that are not symlinks 603 break 604 return path 605 606 def _getfinalpathname_nonstrict(path): 607 # These error codes indicate that we should stop resolving the path 608 # and return the value we currently have. 609 # 1: ERROR_INVALID_FUNCTION 610 # 2: ERROR_FILE_NOT_FOUND 611 # 3: ERROR_DIRECTORY_NOT_FOUND 612 # 5: ERROR_ACCESS_DENIED 613 # 21: ERROR_NOT_READY (implies drive with no media) 614 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file) 615 # 50: ERROR_NOT_SUPPORTED 616 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable) 617 # 87: ERROR_INVALID_PARAMETER 618 # 123: ERROR_INVALID_NAME 619 # 1920: ERROR_CANT_ACCESS_FILE 620 # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink) 621 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 123, 1920, 1921 622 623 # Non-strict algorithm is to find as much of the target directory 624 # as we can and join the rest. 625 tail = '' 626 while path: 627 try: 628 path = _getfinalpathname(path) 629 return join(path, tail) if tail else path 630 except OSError as ex: 631 if ex.winerror not in allowed_winerror: 632 raise 633 try: 634 # The OS could not resolve this path fully, so we attempt 635 # to follow the link ourselves. If we succeed, join the tail 636 # and return. 637 new_path = _readlink_deep(path) 638 if new_path != path: 639 return join(new_path, tail) if tail else new_path 640 except OSError: 641 # If we fail to readlink(), let's keep traversing 642 pass 643 path, name = split(path) 644 # TODO (bpo-38186): Request the real file name from the directory 645 # entry using FindFirstFileW. For now, we will return the path 646 # as best we have it 647 if path and not name: 648 return path + tail 649 tail = join(name, tail) if tail else name 650 return tail 651 652 def realpath(path, *, strict=False): 653 path = normpath(path) 654 if isinstance(path, bytes): 655 prefix = b'\\\\?\\' 656 unc_prefix = b'\\\\?\\UNC\\' 657 new_unc_prefix = b'\\\\' 658 cwd = os.getcwdb() 659 # bpo-38081: Special case for realpath(b'nul') 660 if normcase(path) == normcase(os.fsencode(devnull)): 661 return b'\\\\.\\NUL' 662 else: 663 prefix = '\\\\?\\' 664 unc_prefix = '\\\\?\\UNC\\' 665 new_unc_prefix = '\\\\' 666 cwd = os.getcwd() 667 # bpo-38081: Special case for realpath('nul') 668 if normcase(path) == normcase(devnull): 669 return '\\\\.\\NUL' 670 had_prefix = path.startswith(prefix) 671 if not had_prefix and not isabs(path): 672 path = join(cwd, path) 673 try: 674 path = _getfinalpathname(path) 675 initial_winerror = 0 676 except OSError as ex: 677 if strict: 678 raise 679 initial_winerror = ex.winerror 680 path = _getfinalpathname_nonstrict(path) 681 # The path returned by _getfinalpathname will always start with \\?\ - 682 # strip off that prefix unless it was already provided on the original 683 # path. 684 if not had_prefix and path.startswith(prefix): 685 # For UNC paths, the prefix will actually be \\?\UNC\ 686 # Handle that case as well. 687 if path.startswith(unc_prefix): 688 spath = new_unc_prefix + path[len(unc_prefix):] 689 else: 690 spath = path[len(prefix):] 691 # Ensure that the non-prefixed path resolves to the same path 692 try: 693 if _getfinalpathname(spath) == path: 694 path = spath 695 except OSError as ex: 696 # If the path does not exist and originally did not exist, then 697 # strip the prefix anyway. 698 if ex.winerror == initial_winerror: 699 path = spath 700 return path 701 702 703# Win9x family and earlier have no Unicode filename support. 704supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and 705 sys.getwindowsversion()[3] >= 2) 706 707def relpath(path, start=None): 708 """Return a relative version of a path""" 709 path = os.fspath(path) 710 if isinstance(path, bytes): 711 sep = b'\\' 712 curdir = b'.' 713 pardir = b'..' 714 else: 715 sep = '\\' 716 curdir = '.' 717 pardir = '..' 718 719 if start is None: 720 start = curdir 721 722 if not path: 723 raise ValueError("no path specified") 724 725 start = os.fspath(start) 726 try: 727 start_abs = abspath(normpath(start)) 728 path_abs = abspath(normpath(path)) 729 start_drive, start_rest = splitdrive(start_abs) 730 path_drive, path_rest = splitdrive(path_abs) 731 if normcase(start_drive) != normcase(path_drive): 732 raise ValueError("path is on mount %r, start on mount %r" % ( 733 path_drive, start_drive)) 734 735 start_list = [x for x in start_rest.split(sep) if x] 736 path_list = [x for x in path_rest.split(sep) if x] 737 # Work out how much of the filepath is shared by start and path. 738 i = 0 739 for e1, e2 in zip(start_list, path_list): 740 if normcase(e1) != normcase(e2): 741 break 742 i += 1 743 744 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 745 if not rel_list: 746 return curdir 747 return join(*rel_list) 748 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning): 749 genericpath._check_arg_types('relpath', path, start) 750 raise 751 752 753# Return the longest common sub-path of the sequence of paths given as input. 754# The function is case-insensitive and 'separator-insensitive', i.e. if the 755# only difference between two paths is the use of '\' versus '/' as separator, 756# they are deemed to be equal. 757# 758# However, the returned path will have the standard '\' separator (even if the 759# given paths had the alternative '/' separator) and will have the case of the 760# first path given in the sequence. Additionally, any trailing separator is 761# stripped from the returned path. 762 763def commonpath(paths): 764 """Given a sequence of path names, returns the longest common sub-path.""" 765 766 if not paths: 767 raise ValueError('commonpath() arg is an empty sequence') 768 769 paths = tuple(map(os.fspath, paths)) 770 if isinstance(paths[0], bytes): 771 sep = b'\\' 772 altsep = b'/' 773 curdir = b'.' 774 else: 775 sep = '\\' 776 altsep = '/' 777 curdir = '.' 778 779 try: 780 drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths] 781 split_paths = [p.split(sep) for d, p in drivesplits] 782 783 try: 784 isabs, = set(p[:1] == sep for d, p in drivesplits) 785 except ValueError: 786 raise ValueError("Can't mix absolute and relative paths") from None 787 788 # Check that all drive letters or UNC paths match. The check is made only 789 # now otherwise type errors for mixing strings and bytes would not be 790 # caught. 791 if len(set(d for d, p in drivesplits)) != 1: 792 raise ValueError("Paths don't have the same drive") 793 794 drive, path = splitdrive(paths[0].replace(altsep, sep)) 795 common = path.split(sep) 796 common = [c for c in common if c and c != curdir] 797 798 split_paths = [[c for c in s if c and c != curdir] for s in split_paths] 799 s1 = min(split_paths) 800 s2 = max(split_paths) 801 for i, c in enumerate(s1): 802 if c != s2[i]: 803 common = common[:i] 804 break 805 else: 806 common = common[:len(s1)] 807 808 prefix = drive + sep if isabs else drive 809 return prefix + sep.join(common) 810 except (TypeError, AttributeError): 811 genericpath._check_arg_types('commonpath', *paths) 812 raise 813 814 815try: 816 # The genericpath.isdir implementation uses os.stat and checks the mode 817 # attribute to tell whether or not the path is a directory. 818 # This is overkill on Windows - just pass the path to GetFileAttributes 819 # and check the attribute from there. 820 from nt import _isdir as isdir 821except ImportError: 822 # Use genericpath.isdir as imported above. 823 pass 824