diff options
author | orivej <[email protected]> | 2022-02-10 16:44:49 +0300 |
---|---|---|
committer | Daniil Cherednik <[email protected]> | 2022-02-10 16:44:49 +0300 |
commit | 718c552901d703c502ccbefdfc3c9028d608b947 (patch) | |
tree | 46534a98bbefcd7b1f3faa5b52c138ab27db75b7 /contrib/tools/python3/src/Lib/posixpath.py | |
parent | e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (diff) |
Restoring authorship annotation for <[email protected]>. Commit 1 of 2.
Diffstat (limited to 'contrib/tools/python3/src/Lib/posixpath.py')
-rw-r--r-- | contrib/tools/python3/src/Lib/posixpath.py | 1034 |
1 files changed, 517 insertions, 517 deletions
diff --git a/contrib/tools/python3/src/Lib/posixpath.py b/contrib/tools/python3/src/Lib/posixpath.py index af2814bdb05..ef449ce46ed 100644 --- a/contrib/tools/python3/src/Lib/posixpath.py +++ b/contrib/tools/python3/src/Lib/posixpath.py @@ -1,526 +1,526 @@ -"""Common operations on Posix pathnames. - -Instead of importing this module directly, import os and refer to -this module as os.path. The "os.path" name is an alias for this +"""Common operations on Posix pathnames. + +Instead of importing this module directly, import os and refer to +this module as os.path. The "os.path" name is an alias for this module on Posix systems; on other systems (e.g. Windows), -os.path provides the same operations in a manner specific to that +os.path provides the same operations in a manner specific to that platform, and is an alias to another module (e.g. ntpath). - -Some of this can actually be useful on non-Posix systems too, e.g. -for manipulation of the pathname component of URLs. -""" - -# 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 = ':' + +Some of this can actually be useful on non-Posix systems too, e.g. +for manipulation of the pathname component of URLs. +""" + +# 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 = ':' defpath = '/bin:/usr/bin' -altsep = None -devnull = '/dev/null' - -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", - "samefile","sameopenfile","samestat", - "curdir","pardir","sep","pathsep","defpath","altsep","extsep", - "devnull","realpath","supports_unicode_filenames","relpath", - "commonpath"] - - -def _get_sep(path): - if isinstance(path, bytes): - return b'/' - else: - return '/' - -# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac. -# On MS-DOS this may also turn slashes into backslashes; however, other -# normalizations (such as optimizing '../' away) are not allowed -# (another function should be defined to do that). - -def normcase(s): - """Normalize case of pathname. Has no effect under Posix""" +altsep = None +devnull = '/dev/null' + +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", + "samefile","sameopenfile","samestat", + "curdir","pardir","sep","pathsep","defpath","altsep","extsep", + "devnull","realpath","supports_unicode_filenames","relpath", + "commonpath"] + + +def _get_sep(path): + if isinstance(path, bytes): + return b'/' + else: + return '/' + +# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac. +# On MS-DOS this may also turn slashes into backslashes; however, other +# normalizations (such as optimizing '../' away) are not allowed +# (another function should be defined to do that). + +def normcase(s): + """Normalize case of pathname. Has no effect under Posix""" return os.fspath(s) - - -# Return whether a path is absolute. -# Trivial in Posix, harder on the Mac or MS-DOS. - -def isabs(s): - """Test whether a path is absolute""" - s = os.fspath(s) - sep = _get_sep(s) - return s.startswith(sep) - - -# Join pathnames. -# Ignore the previous parts if a part is absolute. -# Insert a '/' unless the first part is empty or already ends in '/'. - -def join(a, *p): - """Join two or more pathname components, inserting '/' as needed. - If any component is an absolute path, all previous path components - will be discarded. An empty last part will result in a path that - ends with a separator.""" - a = os.fspath(a) - sep = _get_sep(a) - path = a - try: - if not p: - path[:0] + sep #23780: Ensure compatible data type even if p is null. - for b in map(os.fspath, p): - if b.startswith(sep): - path = b - elif not path or path.endswith(sep): - path += b - else: - path += sep + b - except (TypeError, AttributeError, BytesWarning): - genericpath._check_arg_types('join', a, *p) - raise - return path - - -# Split a path in head (everything up to the last '/') and tail (the -# rest). If the path ends in '/', tail will be empty. If there is no -# '/' in the path, head will be empty. -# Trailing '/'es are stripped from head unless it is the root. - -def split(p): - """Split a pathname. Returns tuple "(head, tail)" where "tail" is - everything after the final slash. Either part may be empty.""" - p = os.fspath(p) - sep = _get_sep(p) - i = p.rfind(sep) + 1 - head, tail = p[:i], p[i:] - if head and head != sep*len(head): - head = head.rstrip(sep) - return 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): - sep = b'/' - extsep = b'.' - else: - sep = '/' - extsep = '.' - return genericpath._splitext(p, sep, None, extsep) -splitext.__doc__ = genericpath._splitext.__doc__ - -# Split a pathname into a drive specification and the rest of the -# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty. - -def splitdrive(p): - """Split a pathname into drive and path. On Posix, drive is always - empty.""" - p = os.fspath(p) - return p[:0], p - - -# Return the tail (basename) part of a path, same as split(path)[1]. - -def basename(p): - """Returns the final component of a pathname""" - p = os.fspath(p) - sep = _get_sep(p) - i = p.rfind(sep) + 1 - return p[i:] - - -# Return the head (dirname) part of a path, same as split(path)[0]. - -def dirname(p): - """Returns the directory component of a pathname""" - p = os.fspath(p) - sep = _get_sep(p) - i = p.rfind(sep) + 1 - head = p[:i] - if head and head != sep*len(head): - head = head.rstrip(sep) - return head - - -# 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""" - try: - st = os.lstat(path) + + +# Return whether a path is absolute. +# Trivial in Posix, harder on the Mac or MS-DOS. + +def isabs(s): + """Test whether a path is absolute""" + s = os.fspath(s) + sep = _get_sep(s) + return s.startswith(sep) + + +# Join pathnames. +# Ignore the previous parts if a part is absolute. +# Insert a '/' unless the first part is empty or already ends in '/'. + +def join(a, *p): + """Join two or more pathname components, inserting '/' as needed. + If any component is an absolute path, all previous path components + will be discarded. An empty last part will result in a path that + ends with a separator.""" + a = os.fspath(a) + sep = _get_sep(a) + path = a + try: + if not p: + path[:0] + sep #23780: Ensure compatible data type even if p is null. + for b in map(os.fspath, p): + if b.startswith(sep): + path = b + elif not path or path.endswith(sep): + path += b + else: + path += sep + b + except (TypeError, AttributeError, BytesWarning): + genericpath._check_arg_types('join', a, *p) + raise + return path + + +# Split a path in head (everything up to the last '/') and tail (the +# rest). If the path ends in '/', tail will be empty. If there is no +# '/' in the path, head will be empty. +# Trailing '/'es are stripped from head unless it is the root. + +def split(p): + """Split a pathname. Returns tuple "(head, tail)" where "tail" is + everything after the final slash. Either part may be empty.""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head, tail = p[:i], p[i:] + if head and head != sep*len(head): + head = head.rstrip(sep) + return 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): + sep = b'/' + extsep = b'.' + else: + sep = '/' + extsep = '.' + return genericpath._splitext(p, sep, None, extsep) +splitext.__doc__ = genericpath._splitext.__doc__ + +# Split a pathname into a drive specification and the rest of the +# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty. + +def splitdrive(p): + """Split a pathname into drive and path. On Posix, drive is always + empty.""" + p = os.fspath(p) + return p[:0], p + + +# Return the tail (basename) part of a path, same as split(path)[1]. + +def basename(p): + """Returns the final component of a pathname""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + return p[i:] + + +# Return the head (dirname) part of a path, same as split(path)[0]. + +def dirname(p): + """Returns the directory component of a pathname""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head = p[:i] + if head and head != sep*len(head): + head = head.rstrip(sep) + return head + + +# 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""" + 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: - os.lstat(path) + 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: + os.lstat(path) except (OSError, ValueError): - return False - return True - - -# Is a path a mount point? -# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?) - -def ismount(path): - """Test whether a path is a mount point""" - try: - s1 = os.lstat(path) + return False + return True + + +# Is a path a mount point? +# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?) + +def ismount(path): + """Test whether a path is a mount point""" + try: + s1 = os.lstat(path) except (OSError, ValueError): - # It doesn't exist -- so not a mount point. :-) - return False - else: - # A symlink can never be a mount point - if stat.S_ISLNK(s1.st_mode): - return False - - if isinstance(path, bytes): - parent = join(path, b'..') - else: - parent = join(path, '..') - parent = realpath(parent) - try: - s2 = os.lstat(parent) + # It doesn't exist -- so not a mount point. :-) + return False + else: + # A symlink can never be a mount point + if stat.S_ISLNK(s1.st_mode): + return False + + if isinstance(path, bytes): + parent = join(path, b'..') + else: + parent = join(path, '..') + parent = realpath(parent) + try: + s2 = os.lstat(parent) except (OSError, ValueError): - return False - - dev1 = s1.st_dev - dev2 = s2.st_dev - if dev1 != dev2: - return True # path/.. on a different device as path - ino1 = s1.st_ino - ino2 = s2.st_ino - if ino1 == ino2: - return True # path/.. is the same i-node as path - 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 constructions. 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 - sep = _get_sep(path) - i = path.find(sep, 1) - if i < 0: - i = len(path) - if i == 1: - if 'HOME' not in os.environ: - import pwd - try: - userhome = pwd.getpwuid(os.getuid()).pw_dir - except KeyError: - # bpo-10496: if the current user identifier doesn't exist in the - # password database, return the path unchanged - return path - else: - userhome = os.environ['HOME'] - else: - import pwd - name = path[1:i] - if isinstance(name, bytes): - name = str(name, 'ASCII') - try: - pwent = pwd.getpwnam(name) - except KeyError: - # bpo-10496: if the user name from the path doesn't exist in the - # password database, return the path unchanged - return path - userhome = pwent.pw_dir - if isinstance(path, bytes): - userhome = os.fsencode(userhome) - root = b'/' - else: - root = '/' - userhome = userhome.rstrip(root) - return (userhome + path[i:]) or root - - -# Expand paths containing shell variable substitutions. -# This expands the forms $variable and ${variable} only. -# Non-existent variables are left unchanged. - -_varprog = None -_varprogb = None - -def expandvars(path): - """Expand shell variables of form $var and ${var}. Unknown variables - are left unchanged.""" - path = os.fspath(path) - global _varprog, _varprogb - if isinstance(path, bytes): - if b'$' not in path: - return path - if not _varprogb: - import re - _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII) - search = _varprogb.search - start = b'{' - end = b'}' - environ = getattr(os, 'environb', None) - else: - if '$' not in path: - return path - if not _varprog: - import re - _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII) - search = _varprog.search - start = '{' - end = '}' - environ = os.environ - i = 0 - while True: - m = search(path, i) - if not m: - break - i, j = m.span(0) - name = m.group(1) - if name.startswith(start) and name.endswith(end): - name = name[1:-1] - try: - if environ is None: - value = os.fsencode(os.environ[os.fsdecode(name)]) - else: - value = environ[name] - except KeyError: - i = j - else: - tail = path[j:] - path = path[:i] + value - i = len(path) - path += tail - return path - - -# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. -# It should be understood that this may change the meaning of the path -# if it contains symbolic links! - -def normpath(path): - """Normalize path, eliminating double slashes, etc.""" - path = os.fspath(path) - if isinstance(path, bytes): - sep = b'/' - empty = b'' - dot = b'.' - dotdot = b'..' - else: - sep = '/' - empty = '' - dot = '.' - dotdot = '..' - if path == empty: - return dot - initial_slashes = path.startswith(sep) - # POSIX allows one or two initial slashes, but treats three or more - # as single slash. + return False + + dev1 = s1.st_dev + dev2 = s2.st_dev + if dev1 != dev2: + return True # path/.. on a different device as path + ino1 = s1.st_ino + ino2 = s2.st_ino + if ino1 == ino2: + return True # path/.. is the same i-node as path + 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 constructions. 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 + sep = _get_sep(path) + i = path.find(sep, 1) + if i < 0: + i = len(path) + if i == 1: + if 'HOME' not in os.environ: + import pwd + try: + userhome = pwd.getpwuid(os.getuid()).pw_dir + except KeyError: + # bpo-10496: if the current user identifier doesn't exist in the + # password database, return the path unchanged + return path + else: + userhome = os.environ['HOME'] + else: + import pwd + name = path[1:i] + if isinstance(name, bytes): + name = str(name, 'ASCII') + try: + pwent = pwd.getpwnam(name) + except KeyError: + # bpo-10496: if the user name from the path doesn't exist in the + # password database, return the path unchanged + return path + userhome = pwent.pw_dir + if isinstance(path, bytes): + userhome = os.fsencode(userhome) + root = b'/' + else: + root = '/' + userhome = userhome.rstrip(root) + return (userhome + path[i:]) or root + + +# Expand paths containing shell variable substitutions. +# This expands the forms $variable and ${variable} only. +# Non-existent variables are left unchanged. + +_varprog = None +_varprogb = None + +def expandvars(path): + """Expand shell variables of form $var and ${var}. Unknown variables + are left unchanged.""" + path = os.fspath(path) + global _varprog, _varprogb + if isinstance(path, bytes): + if b'$' not in path: + return path + if not _varprogb: + import re + _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII) + search = _varprogb.search + start = b'{' + end = b'}' + environ = getattr(os, 'environb', None) + else: + if '$' not in path: + return path + if not _varprog: + import re + _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII) + search = _varprog.search + start = '{' + end = '}' + environ = os.environ + i = 0 + while True: + m = search(path, i) + if not m: + break + i, j = m.span(0) + name = m.group(1) + if name.startswith(start) and name.endswith(end): + name = name[1:-1] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(name)]) + else: + value = environ[name] + except KeyError: + i = j + else: + tail = path[j:] + path = path[:i] + value + i = len(path) + path += tail + return path + + +# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. +# It should be understood that this may change the meaning of the path +# if it contains symbolic links! + +def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'/' + empty = b'' + dot = b'.' + dotdot = b'..' + else: + sep = '/' + empty = '' + dot = '.' + dotdot = '..' + if path == empty: + return dot + initial_slashes = path.startswith(sep) + # POSIX allows one or two initial slashes, but treats three or more + # as single slash. # (see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13) - if (initial_slashes and - path.startswith(sep*2) and not path.startswith(sep*3)): - initial_slashes = 2 - comps = path.split(sep) - new_comps = [] - for comp in comps: - if comp in (empty, dot): - continue - if (comp != dotdot or (not initial_slashes and not new_comps) or - (new_comps and new_comps[-1] == dotdot)): - new_comps.append(comp) - elif new_comps: - new_comps.pop() - comps = new_comps - path = sep.join(comps) - if initial_slashes: - path = sep*initial_slashes + path - return path or dot - - -def abspath(path): - """Return an absolute path.""" - 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 a canonical path (i.e. the absolute location of a file on the -# filesystem). - -def realpath(filename): - """Return the canonical path of the specified filename, eliminating any -symbolic links encountered in the path.""" - filename = os.fspath(filename) - path, ok = _joinrealpath(filename[:0], filename, {}) - return abspath(path) - -# Join two paths, normalizing and eliminating any symbolic links -# encountered in the second path. -def _joinrealpath(path, rest, seen): - if isinstance(path, bytes): - sep = b'/' - curdir = b'.' - pardir = b'..' - else: - sep = '/' - curdir = '.' - pardir = '..' - - if isabs(rest): - rest = rest[1:] - path = sep - - while rest: - name, _, rest = rest.partition(sep) - if not name or name == curdir: - # current dir - continue - if name == pardir: - # parent dir - if path: - path, name = split(path) - if name == pardir: - path = join(path, pardir, pardir) - else: - path = pardir - continue - newpath = join(path, name) - if not islink(newpath): - path = newpath - continue - # Resolve the symbolic link - if newpath in seen: - # Already seen this path - path = seen[newpath] - if path is not None: - # use cached value - continue - # The symlink is not resolved, so we must have a symlink loop. - # Return already resolved part + rest of the path unchanged. - return join(newpath, rest), False - seen[newpath] = None # not resolved symlink - path, ok = _joinrealpath(path, os.readlink(newpath), seen) - if not ok: - return join(path, rest), False - seen[newpath] = path # resolved symlink - - return path, True - - -supports_unicode_filenames = (sys.platform == 'darwin') - -def relpath(path, start=None): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - - path = os.fspath(path) - if isinstance(path, bytes): - curdir = b'.' - sep = b'/' - pardir = b'..' - else: - curdir = '.' - sep = '/' - pardir = '..' - - if start is None: - start = curdir - else: - start = os.fspath(start) - - try: - start_list = [x for x in abspath(start).split(sep) if x] - path_list = [x for x in abspath(path).split(sep) if x] - # Work out how much of the filepath is shared by start and path. - i = len(commonprefix([start_list, path_list])) - - rel_list = [pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return curdir - return join(*rel_list) - except (TypeError, 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 paths are not normalized before comparing them (this is the -# responsibility of the caller). 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'/' - curdir = b'.' - else: - sep = '/' - curdir = '.' - - try: - split_paths = [path.split(sep) for path in paths] - - try: - isabs, = set(p[:1] == sep for p in paths) - except ValueError: - raise ValueError("Can't mix absolute and relative paths") from None - - 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) - common = s1 - for i, c in enumerate(s1): - if c != s2[i]: - common = s1[:i] - break - - prefix = sep if isabs else sep[:0] - return prefix + sep.join(common) - except (TypeError, AttributeError): - genericpath._check_arg_types('commonpath', *paths) - raise + if (initial_slashes and + path.startswith(sep*2) and not path.startswith(sep*3)): + initial_slashes = 2 + comps = path.split(sep) + new_comps = [] + for comp in comps: + if comp in (empty, dot): + continue + if (comp != dotdot or (not initial_slashes and not new_comps) or + (new_comps and new_comps[-1] == dotdot)): + new_comps.append(comp) + elif new_comps: + new_comps.pop() + comps = new_comps + path = sep.join(comps) + if initial_slashes: + path = sep*initial_slashes + path + return path or dot + + +def abspath(path): + """Return an absolute path.""" + 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 a canonical path (i.e. the absolute location of a file on the +# filesystem). + +def realpath(filename): + """Return the canonical path of the specified filename, eliminating any +symbolic links encountered in the path.""" + filename = os.fspath(filename) + path, ok = _joinrealpath(filename[:0], filename, {}) + return abspath(path) + +# Join two paths, normalizing and eliminating any symbolic links +# encountered in the second path. +def _joinrealpath(path, rest, seen): + if isinstance(path, bytes): + sep = b'/' + curdir = b'.' + pardir = b'..' + else: + sep = '/' + curdir = '.' + pardir = '..' + + if isabs(rest): + rest = rest[1:] + path = sep + + while rest: + name, _, rest = rest.partition(sep) + if not name or name == curdir: + # current dir + continue + if name == pardir: + # parent dir + if path: + path, name = split(path) + if name == pardir: + path = join(path, pardir, pardir) + else: + path = pardir + continue + newpath = join(path, name) + if not islink(newpath): + path = newpath + continue + # Resolve the symbolic link + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + # Return already resolved part + rest of the path unchanged. + return join(newpath, rest), False + seen[newpath] = None # not resolved symlink + path, ok = _joinrealpath(path, os.readlink(newpath), seen) + if not ok: + return join(path, rest), False + seen[newpath] = path # resolved symlink + + return path, True + + +supports_unicode_filenames = (sys.platform == 'darwin') + +def relpath(path, start=None): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + path = os.fspath(path) + if isinstance(path, bytes): + curdir = b'.' + sep = b'/' + pardir = b'..' + else: + curdir = '.' + sep = '/' + pardir = '..' + + if start is None: + start = curdir + else: + start = os.fspath(start) + + try: + start_list = [x for x in abspath(start).split(sep) if x] + path_list = [x for x in abspath(path).split(sep) if x] + # Work out how much of the filepath is shared by start and path. + i = len(commonprefix([start_list, path_list])) + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) + except (TypeError, 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 paths are not normalized before comparing them (this is the +# responsibility of the caller). 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'/' + curdir = b'.' + else: + sep = '/' + curdir = '.' + + try: + split_paths = [path.split(sep) for path in paths] + + try: + isabs, = set(p[:1] == sep for p in paths) + except ValueError: + raise ValueError("Can't mix absolute and relative paths") from None + + 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) + common = s1 + for i, c in enumerate(s1): + if c != s2[i]: + common = s1[:i] + break + + prefix = sep if isabs else sep[:0] + return prefix + sep.join(common) + except (TypeError, AttributeError): + genericpath._check_arg_types('commonpath', *paths) + raise |