8000 Merge pull request #5262 from youknowone/ntpath · RustPython/RustPython@6e0b00d · GitHub
[go: up one dir, main page]

Skip to content

Commit 6e0b00d

Browse files
authored
Merge pull request #5262 from youknowone/ntpath
Update ntpath from CPython 3.12.3
2 parents c107a93 + 53b1b4d commit 6e0b00d

File tree

3 files changed

+339
-125
lines changed

3 files changed

+339
-125
lines changed

Lib/ntpath.py

Lines changed: 127 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424
from genericpath import *
2525

2626

27-
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
27+
__all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",
2828
"basename","dirname","commonprefix","getsize","getmtime",
2929
"getatime","getctime", "islink","exists","lexists","isdir","isfile",
3030
"ismount", "expanduser","expandvars","normpath","abspath",
3131
"curdir","pardir","sep","pathsep","defpath","altsep",
3232
"extsep","devnull","realpath","supports_unicode_filenames","relpath",
33-
"samefile", "sameopenfile", "samestat", "commonpath"]
33+
"samefile", "sameopenfile", "samestat", "commonpath", "isjunction"]
3434

3535
def _get_bothseps(path):
3636
if isinstance(path, bytes):
@@ -117,19 +117,21 @@ def join(path, *paths):
117117
try:
118118
if not paths:
119119
path[:0] + sep #23780: Ensure compatible data type even if p is null.
120-
result_drive, result_path = splitdrive(path)
120+
result_drive, result_root, result_path = splitroot(path)
121121
for p in map(os.fspath, paths):
122-
p_drive, p_path = splitdrive(p)
123-
if p_path and p_path[0] in seps:
122+
p_drive, p_root, p_path = splitroot(p)
123+
if p_root:
124124
# Second path is absolute
125125
if p_drive or not result_drive:
126126
result_drive = p_drive
127+
result_root = p_root
127128
result_path = p_path
128129
continue
129130
elif p_drive and p_drive != result_drive:
130131
if p_drive.lower() != result_drive.lower():
131132
# Different drives => ignore the first path entirely
132133
result_drive = p_drive
134+
result_root = p_root
133135
result_path = p_path
134136
continue
135137
# Same drive in different case
@@ -139,10 +141,10 @@ def join(path, *paths):
139141
result_path = result_path + sep
140142
result_path = result_path + p_path
141143
## add separator between UNC and non-absolute path
142-
if (result_path and result_path[0] not in seps and
143-
result_drive and result_drive[-1:] != colon):
144+
if (result_path and not result_root and
145+
result_drive and result_drive[-1:] not in colon + seps):
144146
return result_drive + sep + result_path
145-
return result_drive + result_path
147+
return result_drive + result_root + result_path
146148
except (TypeError, AttributeError, BytesWarning):
147149
genericpath._check_arg_types('join', path, *paths)
148150
raise
@@ -169,35 +171,61 @@ def splitdrive(p):
169171
170172
Paths cannot contain both a drive letter and a UNC path.
171173
174+
"""
175+
drive, root, tail = splitroot(p)
176+
return drive, root + tail
177+
178+
179+
def splitroot(p):
180+
"""Split a pathname into drive, root and tail. The drive is defined
181+
exactly as in splitdrive(). On Windows, the root may be a single path
182+
separator or an empty string. The tail contains anything after the root.
183+
For example:
184+
185+
splitroot('//server/share/') == ('//server/share', '/', '')
186+
splitroot('C:/Users/Barney') == ('C:', '/', 'Users/Barney')
187+
splitroot('C:///spam///ham') == ('C:', '/', '//spam///ham')
188+
splitroot('Windows/notepad') == ('', '', 'Windows/notepad')
172189
"""
173190
p = os.fspath(p)
174-
if len(p) >= 2:
175-
if isinstance(p, bytes):
176-
sep = b'\\'
177-
altsep = b'/'
178-
colon = b':'
179-
unc_prefix = b'\\\\?\\UNC\\'
180-
else:
181-
sep = '\\'
182-
altsep = '/'
183-
colon = ':'
184-
unc_prefix = '\\\\?\\UNC\\'
185-
normp = p.replace(altsep, sep)
186-
if normp[0:2] == sep * 2:
191+
if isinstance(p, bytes):
192+
sep = b'\\'
193+
altsep = b'/'
194+
colon = b':'
195+
unc_prefix = b'\\\\?\\UNC\\'
196+
empty = b''
197+
else:
198+
sep = '\\'
199+
altsep = '/'
200+
colon = ':'
201+
unc_prefix = '\\\\?\\UNC\\'
202+
empty = ''
203+
normp = p.replace(altsep, sep)
204+
if normp[:1] == sep:
205+
if normp[1:2] == sep:
187206
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
188207
# Device drives, e.g. \\.\device or \\?\device
189208
start = 8 if normp[:8].upper() == unc_prefix else 2
190209
index = normp.find(sep, start)
191210
if index == -1:
192-
return p, p[:0]
211+
return p, empty, empty
193212
index2 = normp.find(sep, index + 1)
194213
if index2 == -1:
195-
return p, p[:0]
196-
return p[:index2], p[index2:]
197-
if normp[1:2] == colon:
198-
# Drive-letter drives, e.g. X:
199-
return p[:2], p[2:]
200-
return p[:0], p
214+
return p, empty, empty
215+
return p[:index2], p[index2:index2 + 1], p[index2 + 1:]
216+
else:
217+
# Relative path with root, e.g. \Windows
218+
return empty, p[:1], p[1:]
219+
elif normp[1:2] == colon:
220+
if normp[2:3] == sep:
221+
# Absolute drive-letter path, e.g. X:\Windows
222+
return p[:2], p[2:3], p[3:]
223+
else:
224+
# Relative path with drive, e.g. X:Windows
225+
return p[:2], empty, p[2:]
226+
else:
227+
# Relative path, e.g. Windows
228+
return empty, empty, p
201229

202230

203231
# Split a path in head (everything up to the last '/') and tail (the
@@ -212,15 +240,13 @@ def split(p):
212240
Either part may be empty."""
213241
p = os.fspath(p)
214242
seps = _get_bothseps(p)
215-
d, p = splitdrive(p)
243+
d, r, p = splitroot(p)
216244
# set i to index beyond p's last slash
217245
i = len(p)
218246
while i and p[i-1] not in seps:
219247
i -= 1
220248
head, tail = p[:i], p[i:] # now tail has no slashes
221-
# remove trailing slashes from head, unless it's all slashes
222-
head = head.rstrip(seps) or head
223-
return d + head, tail
249+
return d + r + head.rstrip(seps), tail
224250

225251

226252
# Split a path in root and extension.
@@ -250,18 +276,23 @@ def dirname(p):
250276
"""Returns the directory component of a pathname"""
251277
return split(p)[0]
252278

253-
# Is a path a symbolic link?
254-
# This will always return false on systems where os.lstat doesn't exist.
255279

256-
def islink(path):
257-
"""Test whether a path is a symbolic link.
258-
This will always return false for Windows prior to 6.0.
259-
"""
260-
try:
261-
st = os.lstat(path)
262-
except (OSError, ValueError, AttributeError):
280+
# Is a path a junction?
281+
282+
if hasattr(os.stat_result, 'st_reparse_tag'):
283+
def isjunction(path):
284+
"""Test whether a path is a junction"""
285+
try:
286+
st = os.lstat(path)
287+
except (OSError, ValueError, AttributeError):
288+
return False
289+
return bool(st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)
290+
else:
291+
def isjunction(path):
292+
"""Test whether a path is a junction"""
293+
os.fspath(path)
263294
return False
264-
return stat.S_ISLNK(st.st_mode)
295+
265296

266297
# Being true for dangling symbolic links is also useful.
267298

@@ -293,10 +324,10 @@ def ismount(path):
293324
path = os.fspath(path)
294325
seps = _get_bothseps(path)
295326
path = abspath(path)
296-
root, rest = splitdrive(path)
297-
if root and root[0] in seps:
298-
return (not rest) or (rest in seps)
299-
if rest and rest in seps:
327+
drive, root, rest = splitroot(path)
328+
if drive and drive[0] in seps:
329+
return not rest
330+
if root and not rest:
300331
return True
301332

302333
if _getvolumepathname:
@@ -507,13 +538,8 @@ def normpath(path):
507538
curdir = '.'
508539
pardir = '..'
509540
path = path.replace(altsep, sep)
510-
prefix, path = splitdrive(path)
511-
512-
10000 # collapse initial backslashes
513-
if path.startswith(sep):
514-
prefix += sep
515-
path = path.lstrip(sep)
516-
541+
drive, root, path = splitroot(path)
542+
prefix = drive + root
517543
comps = path.split(sep)
518544
i = 0
519545
while i < len(comps):
@@ -523,7 +549,7 @@ def normpath(path):
523549
if i > 0 and comps[i-1] != pardir:
524550
del comps[i-1:i+1]
525551
i -= 1
526-
elif i == 0 and prefix.endswith(sep):
552+
elif i == 0 and root:
527553
del comps[i]
528554
else:
529555
i += 1
@@ -695,6 +721,14 @@ def realpath(path, *, strict=False):
695721
try:
696722
path = _getfinalpathname(path)
697723
initial_winerror = 0
724+
except ValueError as ex:
725+
# gh-106242: Raised for embedded null characters
726+
# In strict mode, we convert into an OSError.
727+
# Non-strict mode returns the path as-is, since we've already
728+
# made it absolute.
729+
if strict:
730+
raise OSError(str(ex)) from None
731+
path = normpath(path)
698732
except OSError as ex:
699733
if strict:
700734
raise
@@ -714,6 +748,10 @@ def realpath(path, *, strict=False):
714748
try:
715749
if _getfinalpathname(spath) == path:
716750
path 10000 = spath
751+
except ValueError as ex:
752+
# Unexpected, as an invalid path should not have gained a prefix
753+
# at any point, but we ignore this error just in case.
754+
pass
717755
except OSError as ex:
718756
# If the path does not exist and originally did not exist, then
719757
# strip the prefix anyway.
@@ -722,9 +760,8 @@ def realpath(path, *, strict=False):
722760
return path
723761

724762

725-
# Win9x family and earlier have no Unicode filename support.
726-
supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
727-
sys.getwindowsversion()[3] >= 2)
763+
# All supported version have Unicode filename support.
764+
supports_unicode_filenames = True
728765

729766
def relpath(path, start=None):
730767
"""Return a relative version of a path"""
@@ -748,8 +785,8 @@ def relpath(path, start=None):
748785
try:
749786
start_abs = abspath(normpath(start))
750787
path_abs = abspath(normpath(path))
751-
start_drive, start_rest = splitdrive(start_abs)
752-
path_drive, path_rest = splitdrive(path_abs)
788+
start_drive, _, start_rest = splitroot(start_abs)
789+
path_drive, _, path_rest = splitroot(path_abs)
753790
if normcase(start_drive) != normcase(path_drive):
754791
raise ValueError("path is on mount %r, start on mount %r" % (
755792
path_drive, start_drive))
@@ -799,21 +836,19 @@ def commonpath(paths):
799836
curdir = '.'
800837

801838
try:
802-
drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
803-
split_paths = [p.split(sep) for d, p in drivesplits]
839+
drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths]
840+
split_paths = [p.split(sep) for d, r, p in drivesplits]
804841

805-
try:
806-
isabs, = set(p[:1] == sep for d, p in drivesplits)
807-
except ValueError:
808-
raise ValueError("Can't mix absolute and relative paths") from None
842+
if len({r for d, r, p in drivesplits}) != 1:
843+
raise ValueError("Can't mix absolute and relative paths")
809844

810845
# Check that all drive letters or UNC paths match. The check is made only
811846
# now otherwise type errors for mixing strings and bytes would not be
812847
# caught.
813-
if len(set(d for d, p in drivesplits)) != 1:
848+
if len({d for d, r, p in drivesplits}) != 1:
814849
raise ValueError("Paths don't have the same drive")
815850

816-
drive, path = splitdrive(paths[0].replace(altsep, sep))
851+
drive, root, path = splitroot(paths[0].replace(altsep, sep))
817852
common = path.split(sep)
818853
common = [c for c in common if c and c != curdir]
819854

@@ -827,19 +862,36 @@ def commonpath(paths):
827862
else:
828863
common = common[:len(s1)]
829864

830-
prefix = drive + sep if isabs else drive
831-
return prefix + sep.join(common)
865+
return drive + root + sep.join(common)
832866
except (TypeError, AttributeError):
833867
genericpath._check_arg_types('commonpath', *paths)
834868
raise
835869

836870

837871
try:
838-
# The genericpath.isdir implementation uses os.stat and checks the mode
839-
# attribute to tell whether or not the path is a directory.
840-
# This is overkill on Windows - just pass the path to GetFileAttributes
841-
# and check the attribute from there.
842-
from nt import _isdir as isdir
872+
# The isdir(), isfile(), islink() and exists() implementations in
873+
# genericpath use os.stat(). This is overkill on Windows. Use simpler
874+
# builtin functions if they are available.
875+
from nt import _path_isdir as isdir
876+
from nt import _path_isfile as isfile
877+
from nt import _path_islink as islink
878+
from nt import _path_exists as exists
843879
except ImportError:
844-
# Use genericpath.isdir as imported above.
880+
# Use genericpath.* as imported above
845881
pass
882+
883+
884+
try:
885+
from nt import _path_isdevdrive
886+
except ImportError:
887+
def isdevdrive(path):
888+
"""Determines whether the specified path is on a Windows Dev Drive."""
889+
# Never a Dev Drive
890+
return False
891+
else:
892+
def isdevdrive(path):
893+
"""Determines whether the specified path is on a Windows Dev Drive."""
894+
try:
895+
return _path_isdevdrive(abspath(path))
896+
except OSError:
897+
return False

0 commit comments

Comments
 (0)
0