Permalink
Cannot retrieve contributors at this time
# 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) | |
try: | |
if isinstance(s, bytes): | |
return s.replace(b'/', b'\\').lower() | |
else: | |
return s.replace('/', '\\').lower() | |
except (TypeError, AttributeError): | |
if not isinstance(s, (bytes, str)): | |
raise TypeError("normcase() argument must be str or bytes, " | |
"not %r" % s.__class__.__name__) from None | |
raise | |
# 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) | |
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, 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: | |
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 'HOME' in os.environ: | |
userhome = os.environ['HOME'] | |
elif '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 | |
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) | |
# realpath is a no-op on systems without islink support | |
realpath = abspath | |
# 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 | |
# determine if two files are in fact the same file | |
try: | |
# GetFinalPathNameByHandle is available starting with Windows 6.0. | |
# Windows XP and non-Windows OS'es will mock _getfinalpathname. | |
if sys.getwindowsversion()[:2] >= (6, 0): | |
from nt import _getfinalpathname | |
else: | |
raise ImportError | |
except (AttributeError, ImportError): | |
# On Windows XP and earlier, two files are the same if their absolute | |
# pathnames are the same. | |
# Non-Windows operating systems fake this method with an XP | |
# approximation. | |
def _getfinalpathname(f): | |
return normcase(abspath(f)) | |
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 |