123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794 |
- # Module 'ntpath' -- common operations on WinNT/Win95 pathnames
- """Common pathname manipulations, WindowsNT/95 version.
- Instead of importing this module directly, import os and refer to this
- module as os.path.
- """
- # strings representing various path-related bits and pieces
- # These are primarily for export; internally, they are hardcoded.
- # Should be set before imports for resolving cyclic dependency.
- curdir = '.'
- pardir = '..'
- extsep = '.'
- sep = '\\'
- pathsep = ';'
- altsep = '/'
- defpath = '.;C:\\bin'
- devnull = 'nul'
- import os
- import sys
- import stat
- import genericpath
- from genericpath import *
- __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
- "basename","dirname","commonprefix","getsize","getmtime",
- "getatime","getctime", "islink","exists","lexists","isdir","isfile",
- "ismount", "expanduser","expandvars","normpath","abspath",
- "curdir","pardir","sep","pathsep","defpath","altsep",
- "extsep","devnull","realpath","supports_unicode_filenames","relpath",
- "samefile", "sameopenfile", "samestat", "commonpath"]
- def _get_bothseps(path):
- if isinstance(path, bytes):
- return b'\\/'
- else:
- return '\\/'
- # Normalize the case of a pathname and map slashes to backslashes.
- # Other normalizations (such as optimizing '../' away) are not done
- # (this is done by normpath).
- def normcase(s):
- """Normalize case of pathname.
- Makes all characters lowercase and all slashes into backslashes."""
- s = os.fspath(s)
- if isinstance(s, bytes):
- return s.replace(b'/', b'\\').lower()
- else:
- return s.replace('/', '\\').lower()
- # Return whether a path is absolute.
- # Trivial in Posix, harder on Windows.
- # For Windows it is absolute if it starts with a slash or backslash (current
- # volume), or if a pathname after the volume-letter-and-colon or UNC-resource
- # starts with a slash or backslash.
- def isabs(s):
- """Test whether a path is absolute"""
- s = os.fspath(s)
- # Paths beginning with \\?\ are always absolute, but do not
- # necessarily contain a drive.
- if isinstance(s, bytes):
- if s.replace(b'/', b'\\').startswith(b'\\\\?\\'):
- return True
- else:
- if s.replace('/', '\\').startswith('\\\\?\\'):
- return True
- s = splitdrive(s)[1]
- return len(s) > 0 and s[0] in _get_bothseps(s)
- # Join two (or more) paths.
- def join(path, *paths):
- path = os.fspath(path)
- if isinstance(path, bytes):
- sep = b'\\'
- seps = b'\\/'
- colon = b':'
- else:
- sep = '\\'
- seps = '\\/'
- colon = ':'
- try:
- if not paths:
- path[:0] + sep #23780: Ensure compatible data type even if p is null.
- result_drive, result_path = splitdrive(path)
- for p in map(os.fspath, paths):
- p_drive, p_path = splitdrive(p)
- if p_path and p_path[0] in seps:
- # Second path is absolute
- if p_drive or not result_drive:
- result_drive = p_drive
- result_path = p_path
- continue
- elif p_drive and p_drive != result_drive:
- if p_drive.lower() != result_drive.lower():
- # Different drives => ignore the first path entirely
- result_drive = p_drive
- result_path = p_path
- continue
- # Same drive in different case
- result_drive = p_drive
- # Second path is relative to the first
- if result_path and result_path[-1] not in seps:
- result_path = result_path + sep
- result_path = result_path + p_path
- ## add separator between UNC and non-absolute path
- if (result_path and result_path[0] not in seps and
- result_drive and result_drive[-1:] != colon):
- return result_drive + sep + result_path
- return result_drive + result_path
- except (TypeError, AttributeError, BytesWarning):
- genericpath._check_arg_types('join', path, *paths)
- raise
- # Split a path in a drive specification (a drive letter followed by a
- # colon) and the path specification.
- # It is always true that drivespec + pathspec == p
- def splitdrive(p):
- """Split a pathname into drive/UNC sharepoint and relative path specifiers.
- Returns a 2-tuple (drive_or_unc, path); either part may be empty.
- If you assign
- result = splitdrive(p)
- It is always true that:
- result[0] + result[1] == p
- If the path contained a drive letter, drive_or_unc will contain everything
- up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")
- If the path contained a UNC path, the drive_or_unc will contain the host name
- and share up to but not including the fourth directory separator character.
- e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
- Paths cannot contain both a drive letter and a UNC path.
- """
- p = os.fspath(p)
- if len(p) >= 2:
- if isinstance(p, bytes):
- sep = b'\\'
- altsep = b'/'
- colon = b':'
- else:
- sep = '\\'
- altsep = '/'
- colon = ':'
- normp = p.replace(altsep, sep)
- if (normp[0:2] == sep*2) and (normp[2:3] != sep):
- # is a UNC path:
- # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
- # \\machine\mountpoint\directory\etc\...
- # directory ^^^^^^^^^^^^^^^
- index = normp.find(sep, 2)
- if index == -1:
- return p[:0], p
- index2 = normp.find(sep, index + 1)
- # a UNC path can't have two slashes in a row
- # (after the initial two)
- if index2 == index + 1:
- return p[:0], p
- if index2 == -1:
- index2 = len(p)
- return p[:index2], p[index2:]
- if normp[1:2] == colon:
- return p[:2], p[2:]
- return p[:0], p
- # Split a path in head (everything up to the last '/') and tail (the
- # rest). After the trailing '/' is stripped, the invariant
- # join(head, tail) == p holds.
- # The resulting head won't end in '/' unless it is the root.
- def split(p):
- """Split a pathname.
- Return tuple (head, tail) where tail is everything after the final slash.
- Either part may be empty."""
- p = os.fspath(p)
- seps = _get_bothseps(p)
- d, p = splitdrive(p)
- # set i to index beyond p's last slash
- i = len(p)
- while i and p[i-1] not in seps:
- i -= 1
- head, tail = p[:i], p[i:] # now tail has no slashes
- # remove trailing slashes from head, unless it's all slashes
- head = head.rstrip(seps) or head
- return d + head, tail
- # Split a path in root and extension.
- # The extension is everything starting at the last dot in the last
- # pathname component; the root is everything before that.
- # It is always true that root + ext == p.
- def splitext(p):
- p = os.fspath(p)
- if isinstance(p, bytes):
- return genericpath._splitext(p, b'\\', b'/', b'.')
- else:
- return genericpath._splitext(p, '\\', '/', '.')
- splitext.__doc__ = genericpath._splitext.__doc__
- # Return the tail (basename) part of a path.
- def basename(p):
- """Returns the final component of a pathname"""
- return split(p)[1]
- # Return the head (dirname) part of a path.
- def dirname(p):
- """Returns the directory component of a pathname"""
- return split(p)[0]
- # Is a path a symbolic link?
- # This will always return false on systems where os.lstat doesn't exist.
- def islink(path):
- """Test whether a path is a symbolic link.
- This will always return false for Windows prior to 6.0.
- """
- try:
- st = os.lstat(path)
- except (OSError, ValueError, AttributeError):
- return False
- return stat.S_ISLNK(st.st_mode)
- # Being true for dangling symbolic links is also useful.
- def lexists(path):
- """Test whether a path exists. Returns True for broken symbolic links"""
- try:
- st = os.lstat(path)
- except (OSError, ValueError):
- return False
- return True
- # Is a path a mount point?
- # Any drive letter root (eg c:\)
- # Any share UNC (eg \\server\share)
- # Any volume mounted on a filesystem folder
- #
- # No one method detects all three situations. Historically we've lexically
- # detected drive letter roots and share UNCs. The canonical approach to
- # detecting mounted volumes (querying the reparse tag) fails for the most
- # common case: drive letter roots. The alternative which uses GetVolumePathName
- # fails if the drive letter is the result of a SUBST.
- try:
- from nt import _getvolumepathname
- except ImportError:
- _getvolumepathname = None
- def ismount(path):
- """Test whether a path is a mount point (a drive root, the root of a
- share, or a mounted volume)"""
- path = os.fspath(path)
- seps = _get_bothseps(path)
- path = abspath(path)
- root, rest = splitdrive(path)
- if root and root[0] in seps:
- return (not rest) or (rest in seps)
- if rest in seps:
- return True
- if _getvolumepathname:
- return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps)
- else:
- return False
- # Expand paths beginning with '~' or '~user'.
- # '~' means $HOME; '~user' means that user's home directory.
- # If the path doesn't begin with '~', or if the user or $HOME is unknown,
- # the path is returned unchanged (leaving error reporting to whatever
- # function is called with the expanded path as argument).
- # See also module 'glob' for expansion of *, ? and [...] in pathnames.
- # (A function should also be defined to do full *sh-style environment
- # variable expansion.)
- def expanduser(path):
- """Expand ~ and ~user constructs.
- If user or $HOME is unknown, do nothing."""
- path = os.fspath(path)
- if isinstance(path, bytes):
- tilde = b'~'
- else:
- tilde = '~'
- if not path.startswith(tilde):
- return path
- i, n = 1, len(path)
- while i < n and path[i] not in _get_bothseps(path):
- i += 1
- if 'USERPROFILE' in os.environ:
- userhome = os.environ['USERPROFILE']
- elif not 'HOMEPATH' in os.environ:
- return path
- else:
- try:
- drive = os.environ['HOMEDRIVE']
- except KeyError:
- drive = ''
- userhome = join(drive, os.environ['HOMEPATH'])
- if isinstance(path, bytes):
- userhome = os.fsencode(userhome)
- if i != 1: #~user
- userhome = join(dirname(userhome), path[1:i])
- return userhome + path[i:]
- # Expand paths containing shell variable substitutions.
- # The following rules apply:
- # - no expansion within single quotes
- # - '$$' is translated into '$'
- # - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
- # - ${varname} is accepted.
- # - $varname is accepted.
- # - %varname% is accepted.
- # - varnames can be made out of letters, digits and the characters '_-'
- # (though is not verified in the ${varname} and %varname% cases)
- # XXX With COMMAND.COM you can use any characters in a variable name,
- # XXX except '^|<>='.
- def expandvars(path):
- """Expand shell variables of the forms $var, ${var} and %var%.
- Unknown variables are left unchanged."""
- path = os.fspath(path)
- if isinstance(path, bytes):
- if b'$' not in path and b'%' not in path:
- return path
- import string
- varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
- quote = b'\''
- percent = b'%'
- brace = b'{'
- rbrace = b'}'
- dollar = b'$'
- environ = getattr(os, 'environb', None)
- else:
- if '$' not in path and '%' not in path:
- return path
- import string
- varchars = string.ascii_letters + string.digits + '_-'
- quote = '\''
- percent = '%'
- brace = '{'
- rbrace = '}'
- dollar = '$'
- environ = os.environ
- res = path[:0]
- index = 0
- pathlen = len(path)
- while index < pathlen:
- c = path[index:index+1]
- if c == quote: # no expansion within single quotes
- path = path[index + 1:]
- pathlen = len(path)
- try:
- index = path.index(c)
- res += c + path[:index + 1]
- except ValueError:
- res += c + path
- index = pathlen - 1
- elif c == percent: # variable or '%'
- if path[index + 1:index + 2] == percent:
- res += c
- index += 1
- else:
- path = path[index+1:]
- pathlen = len(path)
- try:
- index = path.index(percent)
- except ValueError:
- res += percent + path
- index = pathlen - 1
- else:
- var = path[:index]
- try:
- if environ is None:
- value = os.fsencode(os.environ[os.fsdecode(var)])
- else:
- value = environ[var]
- except KeyError:
- value = percent + var + percent
- res += value
- elif c == dollar: # variable or '$$'
- if path[index + 1:index + 2] == dollar:
- res += c
- index += 1
- elif path[index + 1:index + 2] == brace:
- path = path[index+2:]
- pathlen = len(path)
- try:
- index = path.index(rbrace)
- except ValueError:
- res += dollar + brace + path
- index = pathlen - 1
- else:
- var = path[:index]
- try:
- if environ is None:
- value = os.fsencode(os.environ[os.fsdecode(var)])
- else:
- value = environ[var]
- except KeyError:
- value = dollar + brace + var + rbrace
- res += value
- else:
- var = path[:0]
- index += 1
- c = path[index:index + 1]
- while c and c in varchars:
- var += c
- index += 1
- c = path[index:index + 1]
- try:
- if environ is None:
- value = os.fsencode(os.environ[os.fsdecode(var)])
- else:
- value = environ[var]
- except KeyError:
- value = dollar + var
- res += value
- if c:
- index -= 1
- else:
- res += c
- index += 1
- return res
- # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
- # Previously, this function also truncated pathnames to 8+3 format,
- # but as this module is called "ntpath", that's obviously wrong!
- def normpath(path):
- """Normalize path, eliminating double slashes, etc."""
- path = os.fspath(path)
- if isinstance(path, bytes):
- sep = b'\\'
- altsep = b'/'
- curdir = b'.'
- pardir = b'..'
- special_prefixes = (b'\\\\.\\', b'\\\\?\\')
- else:
- sep = '\\'
- altsep = '/'
- curdir = '.'
- pardir = '..'
- special_prefixes = ('\\\\.\\', '\\\\?\\')
- if path.startswith(special_prefixes):
- # in the case of paths with these prefixes:
- # \\.\ -> device names
- # \\?\ -> literal paths
- # do not do any normalization, but return the path
- # unchanged apart from the call to os.fspath()
- return path
- path = path.replace(altsep, sep)
- prefix, path = splitdrive(path)
- # collapse initial backslashes
- if path.startswith(sep):
- prefix += sep
- path = path.lstrip(sep)
- comps = path.split(sep)
- i = 0
- while i < len(comps):
- if not comps[i] or comps[i] == curdir:
- del comps[i]
- elif comps[i] == pardir:
- if i > 0 and comps[i-1] != pardir:
- del comps[i-1:i+1]
- i -= 1
- elif i == 0 and prefix.endswith(sep):
- del comps[i]
- else:
- i += 1
- else:
- i += 1
- # If the path is now empty, substitute '.'
- if not prefix and not comps:
- comps.append(curdir)
- return prefix + sep.join(comps)
- def _abspath_fallback(path):
- """Return the absolute version of a path as a fallback function in case
- `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
- more.
- """
- path = os.fspath(path)
- if not isabs(path):
- if isinstance(path, bytes):
- cwd = os.getcwdb()
- else:
- cwd = os.getcwd()
- path = join(cwd, path)
- return normpath(path)
- # Return an absolute path.
- try:
- from nt import _getfullpathname
- except ImportError: # not running on Windows - mock up something sensible
- abspath = _abspath_fallback
- else: # use native Windows method on Windows
- def abspath(path):
- """Return the absolute version of a path."""
- try:
- return normpath(_getfullpathname(path))
- except (OSError, ValueError):
- return _abspath_fallback(path)
- try:
- from nt import _getfinalpathname, readlink as _nt_readlink
- except ImportError:
- # realpath is a no-op on systems without _getfinalpathname support.
- realpath = abspath
- else:
- def _readlink_deep(path):
- # These error codes indicate that we should stop reading links and
- # return the path we currently have.
- # 1: ERROR_INVALID_FUNCTION
- # 2: ERROR_FILE_NOT_FOUND
- # 3: ERROR_DIRECTORY_NOT_FOUND
- # 5: ERROR_ACCESS_DENIED
- # 21: ERROR_NOT_READY (implies drive with no media)
- # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
- # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points)
- # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
- # 87: ERROR_INVALID_PARAMETER
- # 4390: ERROR_NOT_A_REPARSE_POINT
- # 4392: ERROR_INVALID_REPARSE_DATA
- # 4393: ERROR_REPARSE_TAG_INVALID
- allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393
- seen = set()
- while normcase(path) not in seen:
- seen.add(normcase(path))
- try:
- old_path = path
- path = _nt_readlink(path)
- # Links may be relative, so resolve them against their
- # own location
- if not isabs(path):
- # If it's something other than a symlink, we don't know
- # what it's actually going to be resolved against, so
- # just return the old path.
- if not islink(old_path):
- path = old_path
- break
- path = normpath(join(dirname(old_path), path))
- except OSError as ex:
- if ex.winerror in allowed_winerror:
- break
- raise
- except ValueError:
- # Stop on reparse points that are not symlinks
- break
- return path
- def _getfinalpathname_nonstrict(path):
- # These error codes indicate that we should stop resolving the path
- # and return the value we currently have.
- # 1: ERROR_INVALID_FUNCTION
- # 2: ERROR_FILE_NOT_FOUND
- # 3: ERROR_DIRECTORY_NOT_FOUND
- # 5: ERROR_ACCESS_DENIED
- # 21: ERROR_NOT_READY (implies drive with no media)
- # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
- # 50: ERROR_NOT_SUPPORTED
- # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
- # 87: ERROR_INVALID_PARAMETER
- # 123: ERROR_INVALID_NAME
- # 1920: ERROR_CANT_ACCESS_FILE
- # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)
- allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 123, 1920, 1921
- # Non-strict algorithm is to find as much of the target directory
- # as we can and join the rest.
- tail = ''
- while path:
- try:
- path = _getfinalpathname(path)
- return join(path, tail) if tail else path
- except OSError as ex:
- if ex.winerror not in allowed_winerror:
- raise
- try:
- # The OS could not resolve this path fully, so we attempt
- # to follow the link ourselves. If we succeed, join the tail
- # and return.
- new_path = _readlink_deep(path)
- if new_path != path:
- return join(new_path, tail) if tail else new_path
- except OSError:
- # If we fail to readlink(), let's keep traversing
- pass
- path, name = split(path)
- # TODO (bpo-38186): Request the real file name from the directory
- # entry using FindFirstFileW. For now, we will return the path
- # as best we have it
- if path and not name:
- return path + tail
- tail = join(name, tail) if tail else name
- return tail
- def realpath(path):
- path = normpath(path)
- if isinstance(path, bytes):
- prefix = b'\\\\?\\'
- unc_prefix = b'\\\\?\\UNC\\'
- new_unc_prefix = b'\\\\'
- cwd = os.getcwdb()
- # bpo-38081: Special case for realpath(b'nul')
- if normcase(path) == normcase(os.fsencode(devnull)):
- return b'\\\\.\\NUL'
- else:
- prefix = '\\\\?\\'
- unc_prefix = '\\\\?\\UNC\\'
- new_unc_prefix = '\\\\'
- cwd = os.getcwd()
- # bpo-38081: Special case for realpath('nul')
- if normcase(path) == normcase(devnull):
- return '\\\\.\\NUL'
- had_prefix = path.startswith(prefix)
- if not had_prefix and not isabs(path):
- path = join(cwd, path)
- try:
- path = _getfinalpathname(path)
- initial_winerror = 0
- except OSError as ex:
- initial_winerror = ex.winerror
- path = _getfinalpathname_nonstrict(path)
- # The path returned by _getfinalpathname will always start with \\?\ -
- # strip off that prefix unless it was already provided on the original
- # path.
- if not had_prefix and path.startswith(prefix):
- # For UNC paths, the prefix will actually be \\?\UNC\
- # Handle that case as well.
- if path.startswith(unc_prefix):
- spath = new_unc_prefix + path[len(unc_prefix):]
- else:
- spath = path[len(prefix):]
- # Ensure that the non-prefixed path resolves to the same path
- try:
- if _getfinalpathname(spath) == path:
- path = spath
- except OSError as ex:
- # If the path does not exist and originally did not exist, then
- # strip the prefix anyway.
- if ex.winerror == initial_winerror:
- path = spath
- return path
- # Win9x family and earlier have no Unicode filename support.
- supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
- sys.getwindowsversion()[3] >= 2)
- def relpath(path, start=None):
- """Return a relative version of a path"""
- path = os.fspath(path)
- if isinstance(path, bytes):
- sep = b'\\'
- curdir = b'.'
- pardir = b'..'
- else:
- sep = '\\'
- curdir = '.'
- pardir = '..'
- if start is None:
- start = curdir
- if not path:
- raise ValueError("no path specified")
- start = os.fspath(start)
- try:
- start_abs = abspath(normpath(start))
- path_abs = abspath(normpath(path))
- start_drive, start_rest = splitdrive(start_abs)
- path_drive, path_rest = splitdrive(path_abs)
- if normcase(start_drive) != normcase(path_drive):
- raise ValueError("path is on mount %r, start on mount %r" % (
- path_drive, start_drive))
- start_list = [x for x in start_rest.split(sep) if x]
- path_list = [x for x in path_rest.split(sep) if x]
- # Work out how much of the filepath is shared by start and path.
- i = 0
- for e1, e2 in zip(start_list, path_list):
- if normcase(e1) != normcase(e2):
- break
- i += 1
- rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
- if not rel_list:
- return curdir
- return join(*rel_list)
- except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
- genericpath._check_arg_types('relpath', path, start)
- raise
- # Return the longest common sub-path of the sequence of paths given as input.
- # The function is case-insensitive and 'separator-insensitive', i.e. if the
- # only difference between two paths is the use of '\' versus '/' as separator,
- # they are deemed to be equal.
- #
- # However, the returned path will have the standard '\' separator (even if the
- # given paths had the alternative '/' separator) and will have the case of the
- # first path given in the sequence. Additionally, any trailing separator is
- # stripped from the returned path.
- def commonpath(paths):
- """Given a sequence of path names, returns the longest common sub-path."""
- if not paths:
- raise ValueError('commonpath() arg is an empty sequence')
- paths = tuple(map(os.fspath, paths))
- if isinstance(paths[0], bytes):
- sep = b'\\'
- altsep = b'/'
- curdir = b'.'
- else:
- sep = '\\'
- altsep = '/'
- curdir = '.'
- try:
- drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
- split_paths = [p.split(sep) for d, p in drivesplits]
- try:
- isabs, = set(p[:1] == sep for d, p in drivesplits)
- except ValueError:
- raise ValueError("Can't mix absolute and relative paths") from None
- # Check that all drive letters or UNC paths match. The check is made only
- # now otherwise type errors for mixing strings and bytes would not be
- # caught.
- if len(set(d for d, p in drivesplits)) != 1:
- raise ValueError("Paths don't have the same drive")
- drive, path = splitdrive(paths[0].replace(altsep, sep))
- common = path.split(sep)
- common = [c for c in common if c and c != curdir]
- split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
- s1 = min(split_paths)
- s2 = max(split_paths)
- for i, c in enumerate(s1):
- if c != s2[i]:
- common = common[:i]
- break
- else:
- common = common[:len(s1)]
- prefix = drive + sep if isabs else drive
- return prefix + sep.join(common)
- except (TypeError, AttributeError):
- genericpath._check_arg_types('commonpath', *paths)
- raise
- try:
- # The genericpath.isdir implementation uses os.stat and checks the mode
- # attribute to tell whether or not the path is a directory.
- # This is overkill on Windows - just pass the path to GetFileAttributes
- # and check the attribute from there.
- from nt import _isdir as isdir
- except ImportError:
- # Use genericpath.isdir as imported above.
- pass
|