| # 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 |