Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-117114: Add isdevdrive to posixpath #117115

Merged
merged 11 commits into from
Mar 25, 2024
11 changes: 10 additions & 1 deletion Lib/genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
'getsize', 'isdir', 'isfile', 'islink', 'samefile', 'sameopenfile',
'samestat']
'samestat','lexists']


# Does a path exist?
Expand All @@ -22,6 +22,15 @@ def exists(path):
return True


# 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

# This follows symbolic links, so both islink() and isdir() can be true
# for the same path on systems that support symlinks
def isfile(path):
Expand Down
79 changes: 35 additions & 44 deletions Lib/ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"ismount","isreserved","expanduser","expandvars","normpath",
"abspath","curdir","pardir","sep","pathsep","defpath","altsep",
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction"]
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction",
"isdevdrive"]

def _get_bothseps(path):
if isinstance(path, bytes):
Expand Down Expand Up @@ -164,8 +165,34 @@ def splitdrive(p):
Paths cannot contain both a drive letter and a UNC path.

"""
drive, root, tail = splitroot(p)
return drive, root + tail
p = os.fspath(p)
if isinstance(p, bytes):
sep = b'\\'
altsep = b'/'
colon = b':'
unc_prefix = b'\\\\?\\UNC\\'
else:
sep = '\\'
altsep = '/'
colon = ':'
unc_prefix = '\\\\?\\UNC\\'
normp = p.replace(altsep, sep)
if normp[:1] != sep:
if normp[1:2] == colon:
# Drive-letter drives, e.g. X:
return p[:2], p[2:]
elif normp[1:2] == sep:
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
# Device drives, e.g. \\.\device or \\?\device
start = 8 if normp[:8].upper() == unc_prefix else 2
index = normp.find(sep, start)
if index == -1:
return p, p[:0]
index2 = normp.find(sep, index + 1)
if index2 == -1:
return p, p[:0]
return p[:index2], p[index2:]
return p[:0], p


def splitroot(p):
Expand All @@ -179,45 +206,19 @@ def splitroot(p):
splitroot('C:///spam///ham') == ('C:', '/', '//spam///ham')
splitroot('Windows/notepad') == ('', '', 'Windows/notepad')
"""
p = os.fspath(p)
drive, p = splitdrive(p)
if isinstance(p, bytes):
nineteendo marked this conversation as resolved.
Show resolved Hide resolved
sep = b'\\'
altsep = b'/'
colon = b':'
unc_prefix = b'\\\\?\\UNC\\'
empty = b''
else:
sep = '\\'
altsep = '/'
colon = ':'
unc_prefix = '\\\\?\\UNC\\'
empty = ''
normp = p.replace(altsep, sep)
if normp[:1] == sep:
nineteendo marked this conversation as resolved.
Show resolved Hide resolved
if normp[1:2] == sep:
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
# Device drives, e.g. \\.\device or \\?\device
start = 8 if normp[:8].upper() == unc_prefix else 2
index = normp.find(sep, start)
if index == -1:
return p, empty, empty
index2 = normp.find(sep, index + 1)
if index2 == -1:
return p, empty, empty
return p[:index2], p[index2:index2 + 1], p[index2 + 1:]
else:
# Relative path with root, e.g. \Windows
return empty, p[:1], p[1:]
elif normp[1:2] == colon:
if normp[2:3] == sep:
# Absolute drive-letter path, e.g. X:\Windows
return p[:2], p[2:3], p[3:]
else:
# Relative path with drive, e.g. X:Windows
return p[:2], empty, p[2:]
else:
# Relative path, e.g. Windows
return empty, empty, p
# Absolute path, e.g. X:\Windows
return drive, p[:1], p[1:]
# Relative path, e.g. X:Windows
return drive, p[:0], p


# Split a path in head (everything up to the last '/') and tail (the
Expand Down Expand Up @@ -286,16 +287,6 @@ def isjunction(path):
return False


# 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)
Expand Down
31 changes: 11 additions & 20 deletions Lib/posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"samefile","sameopenfile","samestat",
"curdir","pardir","sep","pathsep","defpath","altsep","extsep",
"devnull","realpath","supports_unicode_filenames","relpath",
"commonpath", "isjunction"]
"commonpath", "isjunction","isreserved","isdevdrive"]


def _get_sep(path):
Expand Down Expand Up @@ -168,23 +168,14 @@ def splitroot(p):

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 split(p)[1]
nineteendo marked this conversation as resolved.
Show resolved Hide resolved


# 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
return split(p)[0]
nineteendo marked this conversation as resolved.
Show resolved Hide resolved


# Is a path a junction?
Expand All @@ -196,16 +187,16 @@ def isjunction(path):
return False


# Being true for dangling symbolic links is also useful.
def isreserved(path):
"""Return true if the pathname is reserved by the system.
Always returns False on posix"""
return False
nineteendo marked this conversation as resolved.
Show resolved Hide resolved

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

def isdevdrive(path):
"""Determines whether the specified path is on a Dev Drive.
Dev Drives are not a part of posix semantics"""
return False

# Is a path a mount point?
# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
Expand Down
9 changes: 5 additions & 4 deletions Lib/test/test_ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ def test_splitdrive(self):
tester('ntpath.splitdrive("//?/UNC/server/share/dir")',
("//?/UNC/server/share", "/dir"))

# gh-101363: match GetFullPathNameW() drive letter parsing behaviour
tester('ntpath.splitdrive(" :/foo")', (" :", "/foo"))
tester('ntpath.splitdrive("/:/foo")', ("", "/:/foo"))


def test_splitroot(self):
tester("ntpath.splitroot('')", ('', '', ''))
tester("ntpath.splitroot('foo')", ('', '', 'foo'))
Expand Down Expand Up @@ -210,10 +215,6 @@ def test_splitroot(self):
tester('ntpath.splitroot("//x")', ("//x", "", "")) # non-empty server & missing share
tester('ntpath.splitroot("//x/")', ("//x/", "", "")) # non-empty server & empty share

# gh-101363: match GetFullPathNameW() drive letter parsing behaviour
tester('ntpath.splitroot(" :/foo")', (" :", "/", "foo"))
tester('ntpath.splitroot("/:/foo")', ("", "/", ":/foo"))

def test_split(self):
tester('ntpath.split("c:\\foo\\bar")', ('c:\\foo', 'bar'))
tester('ntpath.split("\\\\conky\\mountpoint\\foo\\bar")',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added posix implementation for isreserved & isdevdrive.
Loading