codekingpro/portable-devtools
114k
1# Module 'ntpath' -- common operations on WinNT/Win95 pathnames2"""Common pathname manipulations, WindowsNT/95 version.3 4Instead of importing this module directly, import os and refer to this5module as os.path.6"""7 8# strings representing various path-related bits and pieces9# 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 os21import sys22import genericpath23from genericpath import *24 25__all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",26 "basename","dirname","commonprefix","getsize","getmtime",27 "getatime","getctime", "islink","exists","lexists","isdir","isfile",28 "ismount","isreserved","expanduser","expandvars","normpath",29 "abspath","curdir","pardir","sep","pathsep","defpath","altsep",30 "extsep","devnull","realpath","supports_unicode_filenames","relpath",31 "samefile", "sameopenfile", "samestat", "commonpath", "isjunction",32 "isdevdrive", "ALLOW_MISSING"]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 done42# (this is done by normpath).43 44try:45 from _winapi import (46 LCMapStringEx as _LCMapStringEx,47 LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT,48 LCMAP_LOWERCASE as _LCMAP_LOWERCASE)49 50 def normcase(s):51 """Normalize case of pathname.52 53 Makes all characters lowercase and all slashes into backslashes.54 """55 s = os.fspath(s)56 if not s:57 return s58 if isinstance(s, bytes):59 encoding = sys.getfilesystemencoding()60 s = s.decode(encoding, 'surrogateescape').replace('/', '\\')61 s = _LCMapStringEx(_LOCALE_NAME_INVARIANT,62 _LCMAP_LOWERCASE, s)63 return s.encode(encoding, 'surrogateescape')64 else:65 return _LCMapStringEx(_LOCALE_NAME_INVARIANT,66 _LCMAP_LOWERCASE,67 s.replace('/', '\\'))68except ImportError:69 def normcase(s):70 """Normalize case of pathname.71 72 Makes all characters lowercase and all slashes into backslashes.73 """74 s = os.fspath(s)75 if isinstance(s, bytes):76 return os.fsencode(os.fsdecode(s).replace('/', '\\').lower())77 return s.replace('/', '\\').lower()78 79 80def isabs(s):81 """Test whether a path is absolute"""82 s = os.fspath(s)83 if isinstance(s, bytes):84 sep = b'\\'85 altsep = b'/'86 colon_sep = b':\\'87 double_sep = b'\\\\'88 else:89 sep = '\\'90 altsep = '/'91 colon_sep = ':\\'92 double_sep = '\\\\'93 s = s[:3].replace(altsep, sep)94 # Absolute: UNC, device, and paths with a drive and root.95 return s.startswith(colon_sep, 1) or s.startswith(double_sep)96 97 98# Join two (or more) paths.99def join(path, *paths):100 path = os.fspath(path)101 if isinstance(path, bytes):102 sep = b'\\'103 seps = b'\\/'104 colon_seps = b':\\/'105 else:106 sep = '\\'107 seps = '\\/'108 colon_seps = ':\\/'109 try:110 result_drive, result_root, result_path = splitroot(path)111 for p in paths:112 p_drive, p_root, p_path = splitroot(p)113 if p_root:114 # Second path is absolute115 if p_drive or not result_drive:116 result_drive = p_drive117 result_root = p_root118 result_path = p_path119 continue120 elif p_drive and p_drive != result_drive:121 if p_drive.lower() != result_drive.lower():122 # Different drives => ignore the first path entirely123 result_drive = p_drive124 result_root = p_root125 result_path = p_path126 continue127 # Same drive in different case128 result_drive = p_drive129 # Second path is relative to the first130 if result_path and result_path[-1] not in seps:131 result_path = result_path + sep132 result_path = result_path + p_path133 ## add separator between UNC and non-absolute path134 if (result_path and not result_root and135 result_drive and result_drive[-1] not in colon_seps):136 return result_drive + sep + result_path137 return result_drive + result_root + result_path138 except (TypeError, AttributeError, BytesWarning):139 genericpath._check_arg_types('join', path, *paths)140 raise141 142 143# Split a path in a drive specification (a drive letter followed by a144# colon) and the path specification.145# It is always true that drivespec + pathspec == p146def splitdrive(p):147 """Split a pathname into drive/UNC sharepoint and relative path specifiers.148 Returns a 2-tuple (drive_or_unc, path); either part may be empty.149 150 If you assign151 result = splitdrive(p)152 It is always true that:153 result[0] + result[1] == p154 155 If the path contained a drive letter, drive_or_unc will contain everything156 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")157 158 If the path contained a UNC path, the drive_or_unc will contain the host name159 and share up to but not including the fourth directory separator character.160 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")161 162 Paths cannot contain both a drive letter and a UNC path.163 164 """165 drive, root, tail = splitroot(p)166 return drive, root + tail167 168 169try:170 from nt import _path_splitroot_ex as splitroot171except ImportError:172 def splitroot(p):173 """Split a pathname into drive, root and tail.174 175 The tail contains anything after the root."""176 p = os.fspath(p)177 if isinstance(p, bytes):178 sep = b'\\'179 altsep = b'/'180 colon = b':'181 unc_prefix = b'\\\\?\\UNC\\'182 empty = b''183 else:184 sep = '\\'185 altsep = '/'186 colon = ':'187 unc_prefix = '\\\\?\\UNC\\'188 empty = ''189 normp = p.replace(altsep, sep)190 if normp[:1] == sep:191 if normp[1:2] == sep:192 # UNC drives, e.g. \\server\share or \\?\UNC\server\share193 # Device drives, e.g. \\.\device or \\?\device194 start = 8 if normp[:8].upper() == unc_prefix else 2195 index = normp.find(sep, start)196 if index == -1:197 return p, empty, empty198 index2 = normp.find(sep, index + 1)199 if index2 == -1:200 return p, empty, empty201 return p[:index2], p[index2:index2 + 1], p[index2 + 1:]202 else:203 # Relative path with root, e.g. \Windows204 return empty, p[:1], p[1:]205 elif normp[1:2] == colon:206 if normp[2:3] == sep:207 # Absolute drive-letter path, e.g. X:\Windows208 return p[:2], p[2:3], p[3:]209 else:210 # Relative path with drive, e.g. X:Windows211 return p[:2], empty, p[2:]212 else:213 # Relative path, e.g. Windows214 return empty, empty, p215 216 217# Split a path in head (everything up to the last '/') and tail (the218# rest). After the trailing '/' is stripped, the invariant219# join(head, tail) == p holds.220# The resulting head won't end in '/' unless it is the root.221 222def split(p):223 """Split a pathname.224 225 Return tuple (head, tail) where tail is everything after the final slash.226 Either part may be empty."""227 p = os.fspath(p)228 seps = _get_bothseps(p)229 d, r, p = splitroot(p)230 # set i to index beyond p's last slash231 i = len(p)232 while i and p[i-1] not in seps:233 i -= 1234 head, tail = p[:i], p[i:] # now tail has no slashes235 return d + r + head.rstrip(seps), tail236 237 238# Split a path in root and extension.239# The extension is everything starting at the last dot in the last240# pathname component; the root is everything before that.241# It is always true that root + ext == p.242 243def splitext(p):244 p = os.fspath(p)245 if isinstance(p, bytes):246 return genericpath._splitext(p, b'\\', b'/', b'.')247 else:248 return genericpath._splitext(p, '\\', '/', '.')249splitext.__doc__ = genericpath._splitext.__doc__250 251 252# Return the tail (basename) part of a path.253 254def basename(p):255 """Returns the final component of a pathname"""256 return split(p)[1]257 258 259# Return the head (dirname) part of a path.260 261def dirname(p):262 """Returns the directory component of a pathname"""263 return split(p)[0]264 265 266# Is a path a mount point?267# Any drive letter root (eg c:\)268# Any share UNC (eg \\server\share)269# Any volume mounted on a filesystem folder270#271# No one method detects all three situations. Historically we've lexically272# detected drive letter roots and share UNCs. The canonical approach to273# detecting mounted volumes (querying the reparse tag) fails for the most274# common case: drive letter roots. The alternative which uses GetVolumePathName275# fails if the drive letter is the result of a SUBST.276try:277 from nt import _getvolumepathname278except ImportError:279 _getvolumepathname = None280def ismount(path):281 """Test whether a path is a mount point (a drive root, the root of a282 share, or a mounted volume)"""283 path = os.fspath(path)284 seps = _get_bothseps(path)285 path = abspath(path)286 drive, root, rest = splitroot(path)287 if drive and drive[0] in seps:288 return not rest289 if root and not rest:290 return True291 292 if _getvolumepathname:293 x = path.rstrip(seps)294 y =_getvolumepathname(path).rstrip(seps)295 return x.casefold() == y.casefold()296 else:297 return False298 299 300_reserved_chars = frozenset(301 {chr(i) for i in range(32)} |302 {'"', '*', ':', '<', '>', '?', '|', '/', '\\'}303)304 305_reserved_names = frozenset(306 {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} |307 {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} |308 {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'}309)310 311def isreserved(path):312 """Return true if the pathname is reserved by the system."""313 # Refer to "Naming Files, Paths, and Namespaces":314 # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file315 path = os.fsdecode(splitroot(path)[2]).replace(altsep, sep)316 return any(_isreservedname(name) for name in reversed(path.split(sep)))317 318def _isreservedname(name):319 """Return true if the filename is reserved by the system."""320 # Trailing dots and spaces are reserved.321 if name[-1:] in ('.', ' '):322 return name not in ('.', '..')323 # Wildcards, separators, colon, and pipe (*?"<>/\:|) are reserved.324 # ASCII control characters (0-31) are reserved.325 # Colon is reserved for file streams (e.g. "name:stream[:type]").326 if _reserved_chars.intersection(name):327 return True328 # DOS device names are reserved (e.g. "nul" or "nul .txt"). The rules329 # are complex and vary across Windows versions. On the side of330 # caution, return True for names that may not be reserved.331 return name.partition('.')[0].rstrip(' ').upper() in _reserved_names332 333 334# Expand paths beginning with '~' or '~user'.335# '~' means $HOME; '~user' means that user's home directory.336# If the path doesn't begin with '~', or if the user or $HOME is unknown,337# the path is returned unchanged (leaving error reporting to whatever338# function is called with the expanded path as argument).339# See also module 'glob' for expansion of *, ? and [...] in pathnames.340# (A function should also be defined to do full *sh-style environment341# variable expansion.)342 343def expanduser(path):344 """Expand ~ and ~user constructs.345 346 If user or $HOME is unknown, do nothing."""347 path = os.fspath(path)348 if isinstance(path, bytes):349 seps = b'\\/'350 tilde = b'~'351 else:352 seps = '\\/'353 tilde = '~'354 if not path.startswith(tilde):355 return path356 i, n = 1, len(path)357 while i < n and path[i] not in seps:358 i += 1359 360 if 'USERPROFILE' in os.environ:361 userhome = os.environ['USERPROFILE']362 elif 'HOMEPATH' not in os.environ:363 return path364 else:365 drive = os.environ.get('HOMEDRIVE', '')366 userhome = join(drive, os.environ['HOMEPATH'])367 368 if i != 1: #~user369 target_user = path[1:i]370 if isinstance(target_user, bytes):371 target_user = os.fsdecode(target_user)372 current_user = os.environ.get('USERNAME')373 374 if target_user != current_user:375 # Try to guess user home directory. By default all user376 # profile directories are located in the same place and are377 # named by corresponding usernames. If userhome isn't a378 # normal profile directory, this guess is likely wrong,379 # so we bail out.380 if current_user != basename(userhome):381 return path382 userhome = join(dirname(userhome), target_user)383 384 if isinstance(path, bytes):385 userhome = os.fsencode(userhome)386 387 return userhome + path[i:]388 389 390# Expand paths containing shell variable substitutions.391# The following rules apply:392# - no expansion within single quotes393# - '$$' is translated into '$'394# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%395# - ${varname} is accepted.396# - $varname is accepted.397# - %varname% is accepted.398# - varnames can be made out of letters, digits and the characters '_-'399# (though is not verified in the ${varname} and %varname% cases)400# XXX With COMMAND.COM you can use any characters in a variable name,401# XXX except '^|<>='.402 403_varpattern = r"'[^']*'?|%(%|[^%]*%?)|\$(\$|[-\w]+|\{[^}]*\}?)"404_varsub = None405_varsubb = None406 407def expandvars(path):408 """Expand shell variables of the forms $var, ${var} and %var%.409 410 Unknown variables are left unchanged."""411 path = os.fspath(path)412 global _varsub, _varsubb413 if isinstance(path, bytes):414 if b'$' not in path and b'%' not in path:415 return path416 if not _varsubb:417 import re418 _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub419 sub = _varsubb420 percent = b'%'421 brace = b'{'422 rbrace = b'}'423 dollar = b'$'424 environ = getattr(os, 'environb', None)425 else:426 if '$' not in path and '%' not in path:427 return path428 if not _varsub:429 import re430 _varsub = re.compile(_varpattern, re.ASCII).sub431 sub = _varsub432 percent = '%'433 brace = '{'434 rbrace = '}'435 dollar = '$'436 environ = os.environ437 438 def repl(m):439 lastindex = m.lastindex440 if lastindex is None:441 return m[0]442 name = m[lastindex]443 if lastindex == 1:444 if name == percent:445 return name446 if not name.endswith(percent):447 return m[0]448 name = name[:-1]449 else:450 if name == dollar:451 return name452 if name.startswith(brace):453 if not name.endswith(rbrace):454 return m[0]455 name = name[1:-1]456 457 try:458 if environ is None:459 return os.fsencode(os.environ[os.fsdecode(name)])460 else:461 return environ[name]462 except KeyError:463 return m[0]464 465 return sub(repl, path)466 467 468# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.469# Previously, this function also truncated pathnames to 8+3 format,470# but as this module is called "ntpath", that's obviously wrong!471try:472 from nt import _path_normpath as normpath473 474except ImportError:475 def normpath(path):476 """Normalize path, eliminating double slashes, etc."""477 path = os.fspath(path)478 if isinstance(path, bytes):479 sep = b'\\'480 altsep = b'/'481 curdir = b'.'482 pardir = b'..'483 else:484 sep = '\\'485 altsep = '/'486 curdir = '.'487 pardir = '..'488 path = path.replace(altsep, sep)489 drive, root, path = splitroot(path)490 prefix = drive + root491 comps = path.split(sep)492 i = 0493 while i < len(comps):494 if not comps[i] or comps[i] == curdir:495 del comps[i]496 elif comps[i] == pardir:497 if i > 0 and comps[i-1] != pardir:498 del comps[i-1:i+1]499 i -= 1500 elif i == 0 and root:501 del comps[i]502 else:503 i += 1504 else:505 i += 1506 # If the path is now empty, substitute '.'507 if not prefix and not comps:508 comps.append(curdir)509 return prefix + sep.join(comps)510 511 512# Return an absolute path.513try:514 from nt import _getfullpathname515 516except ImportError: # not running on Windows - mock up something sensible517 def abspath(path):518 """Return the absolute version of a path."""519 path = os.fspath(path)520 if not isabs(path):521 if isinstance(path, bytes):522 cwd = os.getcwdb()523 else:524 cwd = os.getcwd()525 path = join(cwd, path)526 return normpath(path)527 528else: # use native Windows method on Windows529 def abspath(path):530 """Return the absolute version of a path."""531 try:532 return _getfullpathname(normpath(path))533 except (OSError, ValueError):534 # See gh-75230, handle outside for cleaner traceback535 pass536 path = os.fspath(path)537 if not isabs(path):538 if isinstance(path, bytes):539 sep = b'\\'540 getcwd = os.getcwdb541 else:542 sep = '\\'543 getcwd = os.getcwd544 drive, root, path = splitroot(path)545 # Either drive or root can be nonempty, but not both.546 if drive or root:547 try:548 path = join(_getfullpathname(drive + root), path)549 except (OSError, ValueError):550 # Drive "\0:" cannot exist; use the root directory.551 path = drive + sep + path552 else:553 path = join(getcwd(), path)554 return normpath(path)555 556try:557 from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink558except ImportError:559 # realpath is a no-op on systems without _getfinalpathname support.560 def realpath(path, *, strict=False):561 return abspath(path)562else:563 def _readlink_deep(path, ignored_error=OSError):564 # These error codes indicate that we should stop reading links and565 # return the path we currently have.566 # 1: ERROR_INVALID_FUNCTION567 # 2: ERROR_FILE_NOT_FOUND568 # 3: ERROR_DIRECTORY_NOT_FOUND569 # 5: ERROR_ACCESS_DENIED570 # 21: ERROR_NOT_READY (implies drive with no media)571 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)572 # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points)573 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)574 # 87: ERROR_INVALID_PARAMETER575 # 4390: ERROR_NOT_A_REPARSE_POINT576 # 4392: ERROR_INVALID_REPARSE_DATA577 # 4393: ERROR_REPARSE_TAG_INVALID578 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393579 580 seen = set()581 while normcase(path) not in seen:582 seen.add(normcase(path))583 try:584 old_path = path585 path = _nt_readlink(path)586 # Links may be relative, so resolve them against their587 # own location588 if not isabs(path):589 # If it's something other than a symlink, we don't know590 # what it's actually going to be resolved against, so591 # just return the old path.592 if not islink(old_path):593 path = old_path594 break595 path = normpath(join(dirname(old_path), path))596 except ignored_error as ex:597 if ex.winerror in allowed_winerror:598 break599 raise600 except ValueError:601 # Stop on reparse points that are not symlinks602 break603 return path604 605 def _getfinalpathname_nonstrict(path, ignored_error=OSError):606 # These error codes indicate that we should stop resolving the path607 # and return the value we currently have.608 # 1: ERROR_INVALID_FUNCTION609 # 2: ERROR_FILE_NOT_FOUND610 # 3: ERROR_DIRECTORY_NOT_FOUND611 # 5: ERROR_ACCESS_DENIED612 # 21: ERROR_NOT_READY (implies drive with no media)613 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)614 # 50: ERROR_NOT_SUPPORTED615 # 53: ERROR_BAD_NETPATH616 # 65: ERROR_NETWORK_ACCESS_DENIED617 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)618 # 87: ERROR_INVALID_PARAMETER619 # 123: ERROR_INVALID_NAME620 # 161: ERROR_BAD_PATHNAME621 # 1005: ERROR_UNRECOGNIZED_VOLUME622 # 1920: ERROR_CANT_ACCESS_FILE623 # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)624 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1005, 1920, 1921625 626 # Non-strict algorithm is to find as much of the target directory627 # as we can and join the rest.628 tail = path[:0]629 while path:630 try:631 path = _getfinalpathname(path)632 return join(path, tail) if tail else path633 except ignored_error as ex:634 if ex.winerror not in allowed_winerror:635 raise636 try:637 # The OS could not resolve this path fully, so we attempt638 # to follow the link ourselves. If we succeed, join the tail639 # and return.640 new_path = _readlink_deep(path,641 ignored_error=ignored_error)642 if new_path != path:643 return join(new_path, tail) if tail else new_path644 except ignored_error:645 # If we fail to readlink(), let's keep traversing646 pass647 # If we get these errors, try to get the real name of the file without accessing it.648 if ex.winerror in (1, 5, 32, 50, 87, 1920, 1921):649 try:650 name = _findfirstfile(path)651 path, _ = split(path)652 except ignored_error:653 path, name = split(path)654 else:655 path, name = split(path)656 if path and not name:657 return path + tail658 tail = join(name, tail) if tail else name659 return tail660 661 def realpath(path, *, strict=False):662 path = normpath(path)663 if isinstance(path, bytes):664 prefix = b'\\\\?\\'665 unc_prefix = b'\\\\?\\UNC\\'666 new_unc_prefix = b'\\\\'667 cwd = os.getcwdb()668 # bpo-38081: Special case for realpath(b'nul')669 devnull = b'nul'670 if normcase(path) == devnull:671 return b'\\\\.\\NUL'672 else:673 prefix = '\\\\?\\'674 unc_prefix = '\\\\?\\UNC\\'675 new_unc_prefix = '\\\\'676 cwd = os.getcwd()677 # bpo-38081: Special case for realpath('nul')678 devnull = 'nul'679 if normcase(path) == devnull:680 return '\\\\.\\NUL'681 had_prefix = path.startswith(prefix)682 683 if strict is ALLOW_MISSING:684 ignored_error = FileNotFoundError685 strict = True686 elif strict:687 ignored_error = ()688 else:689 ignored_error = OSError690 691 if not had_prefix and not isabs(path):692 path = join(cwd, path)693 try:694 path = _getfinalpathname(path)695 initial_winerror = 0696 except ValueError as ex:697 # gh-106242: Raised for embedded null characters698 # In strict modes, we convert into an OSError.699 # Non-strict mode returns the path as-is, since we've already700 # made it absolute.701 if strict:702 raise OSError(str(ex)) from None703 path = normpath(path)704 except ignored_error as ex:705 initial_winerror = ex.winerror706 path = _getfinalpathname_nonstrict(path,707 ignored_error=ignored_error)708 # The path returned by _getfinalpathname will always start with \\?\ -709 # strip off that prefix unless it was already provided on the original710 # path.711 if not had_prefix and path.startswith(prefix):712 # For UNC paths, the prefix will actually be \\?\UNC\713 # Handle that case as well.714 if path.startswith(unc_prefix):715 spath = new_unc_prefix + path[len(unc_prefix):]716 else:717 spath = path[len(prefix):]718 # Ensure that the non-prefixed path resolves to the same path719 try:720 if _getfinalpathname(spath) == path:721 path = spath722 except ValueError as ex:723 # Unexpected, as an invalid path should not have gained a prefix724 # at any point, but we ignore this error just in case.725 pass726 except OSError as ex:727 # If the path does not exist and originally did not exist, then728 # strip the prefix anyway.729 if ex.winerror == initial_winerror:730 path = spath731 return path732 733 734# All supported version have Unicode filename support.735supports_unicode_filenames = True736 737def relpath(path, start=None):738 """Return a relative version of a path"""739 path = os.fspath(path)740 if not path:741 raise ValueError("no path specified")742 743 if isinstance(path, bytes):744 sep = b'\\'745 curdir = b'.'746 pardir = b'..'747 else:748 sep = '\\'749 curdir = '.'750 pardir = '..'751 752 if start is None:753 start = curdir754 else:755 start = os.fspath(start)756 757 try:758 start_abs = abspath(start)759 path_abs = abspath(path)760 start_drive, _, start_rest = splitroot(start_abs)761 path_drive, _, path_rest = splitroot(path_abs)762 if normcase(start_drive) != normcase(path_drive):763 raise ValueError("path is on mount %r, start on mount %r" % (764 path_drive, start_drive))765 766 start_list = start_rest.split(sep) if start_rest else []767 path_list = path_rest.split(sep) if path_rest else []768 # Work out how much of the filepath is shared by start and path.769 i = 0770 for e1, e2 in zip(start_list, path_list):771 if normcase(e1) != normcase(e2):772 break773 i += 1774 775 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]776 if not rel_list:777 return curdir778 return sep.join(rel_list)779 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):780 genericpath._check_arg_types('relpath', path, start)781 raise782 783 784# Return the longest common sub-path of the iterable of paths given as input.785# The function is case-insensitive and 'separator-insensitive', i.e. if the786# only difference between two paths is the use of '\' versus '/' as separator,787# they are deemed to be equal.788#789# However, the returned path will have the standard '\' separator (even if the790# given paths had the alternative '/' separator) and will have the case of the791# first path given in the iterable. Additionally, any trailing separator is792# stripped from the returned path.793 794def commonpath(paths):795 """Given an iterable of path names, returns the longest common sub-path."""796 paths = tuple(map(os.fspath, paths))797 if not paths:798 raise ValueError('commonpath() arg is an empty iterable')799 800 if isinstance(paths[0], bytes):801 sep = b'\\'802 altsep = b'/'803 curdir = b'.'804 else:805 sep = '\\'806 altsep = '/'807 curdir = '.'808 809 try:810 drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths]811 split_paths = [p.split(sep) for d, r, p in drivesplits]812 813 # Check that all drive letters or UNC paths match. The check is made only814 # now otherwise type errors for mixing strings and bytes would not be815 # caught.816 if len({d for d, r, p in drivesplits}) != 1:817 raise ValueError("Paths don't have the same drive")818 819 drive, root, path = splitroot(paths[0].replace(altsep, sep))820 if len({r for d, r, p in drivesplits}) != 1:821 if drive:822 raise ValueError("Can't mix absolute and relative paths")823 else:824 raise ValueError("Can't mix rooted and not-rooted paths")825 826 common = path.split(sep)827 common = [c for c in common if c and c != curdir]828 829 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]830 s1 = min(split_paths)831 s2 = max(split_paths)832 for i, c in enumerate(s1):833 if c != s2[i]:834 common = common[:i]835 break836 else:837 common = common[:len(s1)]838 839 return drive + root + sep.join(common)840 except (TypeError, AttributeError):841 genericpath._check_arg_types('commonpath', *paths)842 raise843 844 845try:846 # The isdir(), isfile(), islink(), exists() and lexists() implementations847 # in genericpath use os.stat(). This is overkill on Windows. Use simpler848 # builtin functions if they are available.849 from nt import _path_isdir as isdir850 from nt import _path_isfile as isfile851 from nt import _path_islink as islink852 from nt import _path_isjunction as isjunction853 from nt import _path_exists as exists854 from nt import _path_lexists as lexists855except ImportError:856 # Use genericpath.* as imported above857 pass858 859 860try:861 from nt import _path_isdevdrive862 def isdevdrive(path):863 """Determines whether the specified path is on a Windows Dev Drive."""864 try:865 return _path_isdevdrive(abspath(path))866 except OSError:867 return False868except ImportError:869 # Use genericpath.isdevdrive as imported above870 pass871 