Skip to content

Commit a3bc95f

Browse files
gh-85681: Make ntpath.abspath() platform independent
On Windows the path normalization strips trailing dots and spaces from the last component of the path, and a single trailing dot from other components. abspath() on other platforms now does the same, so relpath() and other functions based on it no longer depend on the platform.
1 parent 998b890 commit a3bc95f

3 files changed

Lines changed: 53 additions & 1 deletion

File tree

Lib/ntpath.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,26 @@ def normpath(path):
516516
from nt import _getfullpathname
517517

518518
except ImportError: # not running on Windows - mock up something sensible
519+
def _strip_dots_and_spaces(path):
520+
# As the Windows path normalization does.
521+
if isinstance(path, bytes):
522+
sep = b'\\'
523+
dot = b'.'
524+
dots_and_spaces = b'. '
525+
else:
526+
sep = '\\'
527+
dot = '.'
528+
dots_and_spaces = '. '
529+
drive, root, tail = splitroot(path)
530+
if not tail:
531+
return path
532+
comps = tail.split(sep)
533+
for i, comp in enumerate(comps[:-1]):
534+
if len(comp) > 1 and comp.endswith(dot) and not comp.endswith(dot*2):
535+
comps[i] = comp[:-1]
536+
comps[-1] = comps[-1].rstrip(dots_and_spaces)
537+
return drive + root + sep.join(comps)
538+
519539
def abspath(path):
520540
"""Return the absolute version of a path."""
521541
path = os.fspath(path)
@@ -525,7 +545,7 @@ def abspath(path):
525545
else:
526546
cwd = os.getcwd()
527547
path = join(cwd, path)
528-
return normpath(path)
548+
return _strip_dots_and_spaces(normpath(path))
529549

530550
else: # use native Windows method on Windows
531551
def abspath(path):

Lib/test/test_ntpath.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,35 @@ def test_expanduser(self):
12101210

12111211

12121212

1213+
def test_abspath_dots_and_spaces(self):
1214+
# gh-85681: trailing dots and spaces are stripped from the last
1215+
# component, and a single trailing dot from other components.
1216+
tester('ntpath.abspath("C:/spam. . .")', "C:\\spam")
1217+
tester('ntpath.abspath("C:/spam.")', "C:\\spam")
1218+
tester('ntpath.abspath("C:/spam ")', "C:\\spam")
1219+
tester('ntpath.abspath("C:/spam..")', "C:\\spam")
1220+
tester('ntpath.abspath("C:/.spam")', "C:\\.spam")
1221+
tester('ntpath.abspath("C:/spam./eggs")', "C:\\spam\\eggs")
1222+
tester('ntpath.abspath("C:/spam.b./eggs")', "C:\\spam.b\\eggs")
1223+
tester('ntpath.abspath("C:/spam ./eggs")', "C:\\spam \\eggs")
1224+
# A trailing dot preceded by a dot is not stripped.
1225+
tester('ntpath.abspath("C:/spam../eggs")', "C:\\spam..\\eggs")
1226+
tester('ntpath.abspath("C:/spam.../eggs")', "C:\\spam...\\eggs")
1227+
# Trailing dots and spaces are stripped even in extended paths.
1228+
tester('ntpath.abspath("\\\\?\\C:/spam. . .")', "\\\\?\\C:\\spam")
1229+
tester('ntpath.abspath("\\\\.\\C:/spam. . .")', "\\\\.\\C:\\spam")
1230+
tester('ntpath.abspath("//server/share/spam. ")',
1231+
"\\\\server\\share\\spam")
1232+
1233+
def test_relpath_dots_and_spaces(self):
1234+
# gh-85681: relpath() is based on abspath().
1235+
tester('ntpath.relpath("foo ", "foo")', ".")
1236+
tester('ntpath.relpath("foo.", "foo")', ".")
1237+
tester('ntpath.relpath("foo/bar ", "foo")', "bar")
1238+
tester('ntpath.relpath("foo/bar.", "foo")', "bar")
1239+
# "foo.." denotes "foo" as the last component, but not as other.
1240+
tester('ntpath.relpath("foo../bar", "foo..")', "..\\foo..\\bar")
1241+
12131242
@unittest.skipUnless(nt, "abspath requires 'nt' module")
12141243
def test_abspath(self):
12151244
tester('ntpath.abspath("C:\\")', "C:\\")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`ntpath.abspath` on non-Windows platforms now strips trailing dots and
2+
spaces like the Windows path normalization does. This makes
3+
:func:`ntpath.relpath` and other functions based on it platform independent.

0 commit comments

Comments
 (0)