Issue #19456: ntpath.join() now joins relative paths correctly when a drive

is present.
This commit is contained in:
Serhiy Storchaka 2014-01-27 23:15:14 +02:00
parent 99e033b02e
commit c369c2c688
3 changed files with 66 additions and 106 deletions

View File

@ -104,82 +104,36 @@ def isabs(s):
# Join two (or more) paths. # Join two (or more) paths.
def join(path, *paths):
def join(a, *p): sep = _get_sep(path)
"""Join two or more pathname components, inserting "\\" as needed. seps = _get_bothseps(path)
If any component is an absolute path, all previous path components colon = _get_colon(path)
will be discarded.""" result_drive, result_path = splitdrive(path)
sep = _get_sep(a) for p in paths:
seps = _get_bothseps(a) p_drive, p_path = splitdrive(p)
colon = _get_colon(a) if p_path and p_path[0] in seps:
path = a # Second path is absolute
for b in p: if p_drive or not result_drive:
b_wins = 0 # set to 1 iff b makes path irrelevant result_drive = p_drive
if not path: result_path = p_path
b_wins = 1 continue
elif p_drive and p_drive != result_drive:
elif isabs(b): if p_drive.lower() != result_drive.lower():
# This probably wipes out path so far. However, it's more # Different drives => ignore the first path entirely
# complicated if path begins with a drive letter. You get a+b result_drive = p_drive
# (minus redundant slashes) in these four cases: result_path = p_path
# 1. join('c:', '/a') == 'c:/a' continue
# 2. join('//computer/share', '/a') == '//computer/share/a' # Same drive in different case
# 3. join('c:/', '/a') == 'c:/a' result_drive = p_drive
# 4. join('//computer/share/', '/a') == '//computer/share/a' # Second path is relative to the first
# But b wins in all of these cases: if result_path and result_path[-1] not in seps:
# 5. join('c:/a', '/b') == '/b' result_path = result_path + sep
# 6. join('//computer/share/a', '/b') == '/b' result_path = result_path + p_path
# 7. join('c:', 'd:/') == 'd:/' ## add separator between UNC and non-absolute path
# 8. join('c:', '//computer/share/') == '//computer/share/' if (result_path and result_path[0] not in seps and
# 9. join('//computer/share', 'd:/') == 'd:/' result_drive and result_drive[-1:] != colon):
# 10. join('//computer/share', '//computer/share/') == '//computer/share/' return result_drive + sep + result_path
# 11. join('c:/', 'd:/') == 'd:/' return result_drive + result_path
# 12. join('c:/', '//computer/share/') == '//computer/share/'
# 13. join('//computer/share/', 'd:/') == 'd:/'
# 14. join('//computer/share/', '//computer/share/') == '//computer/share/'
b_prefix, b_rest = splitdrive(b)
# if b has a prefix, it always wins.
if b_prefix:
b_wins = 1
else:
# b doesn't have a prefix.
# but isabs(b) returned true.
# and therefore b_rest[0] must be a slash.
# (but let's check that.)
assert(b_rest and b_rest[0] in seps)
# so, b still wins if path has a rest that's more than a sep.
# you get a+b if path_rest is empty or only has a sep.
# (see cases 1-4 for times when b loses.)
path_rest = splitdrive(path)[1]
b_wins = path_rest and path_rest not in seps
if b_wins:
path = b
else:
# Join, and ensure there's a separator.
assert len(path) > 0
if path[-1:] in seps:
if b and b[:1] in seps:
path += b[1:]
else:
path += b
elif path[-1:] == colon:
path += b
elif b:
if b[:1] in seps:
path += b
else:
path += sep + b
else:
# path is not empty and does not end with a backslash,
# but b is empty; since, e.g., split('a/') produces
# ('a', ''), it's best if join() adds a backslash in
# this case.
path += sep
return path
# Split a path in a drive specification (a drive letter followed by a # Split a path in a drive specification (a drive letter followed by a

View File

@ -126,10 +126,7 @@ class TestNtpath(unittest.TestCase):
tester('ntpath.join("/a")', '/a') tester('ntpath.join("/a")', '/a')
tester('ntpath.join("\\a")', '\\a') tester('ntpath.join("\\a")', '\\a')
tester('ntpath.join("a:")', 'a:') tester('ntpath.join("a:")', 'a:')
tester('ntpath.join("a:", "b")', 'a:b')
tester('ntpath.join("a:", "/b")', 'a:/b')
tester('ntpath.join("a:", "\\b")', 'a:\\b') tester('ntpath.join("a:", "\\b")', 'a:\\b')
tester('ntpath.join("a", "/b")', '/b')
tester('ntpath.join("a", "\\b")', '\\b') tester('ntpath.join("a", "\\b")', '\\b')
tester('ntpath.join("a", "b", "c")', 'a\\b\\c') tester('ntpath.join("a", "b", "c")', 'a\\b\\c')
tester('ntpath.join("a\\", "b", "c")', 'a\\b\\c') tester('ntpath.join("a\\", "b", "c")', 'a\\b\\c')
@ -137,42 +134,48 @@ class TestNtpath(unittest.TestCase):
tester('ntpath.join("a", "b", "\\c")', '\\c') tester('ntpath.join("a", "b", "\\c")', '\\c')
tester('ntpath.join("d:\\", "\\pleep")', 'd:\\pleep') tester('ntpath.join("d:\\", "\\pleep")', 'd:\\pleep')
tester('ntpath.join("d:\\", "a", "b")', 'd:\\a\\b') tester('ntpath.join("d:\\", "a", "b")', 'd:\\a\\b')
tester("ntpath.join('c:', '/a')", 'c:/a')
tester("ntpath.join('c:/', '/a')", 'c:/a')
tester("ntpath.join('c:/a', '/b')", '/b')
tester("ntpath.join('c:', 'd:/')", 'd:/')
tester("ntpath.join('c:/', 'd:/')", 'd:/')
tester("ntpath.join('c:/', 'd:/a/b')", 'd:/a/b')
tester("ntpath.join('')", '')
tester("ntpath.join('', '', '', '', '')", '')
tester("ntpath.join('a')", 'a')
tester("ntpath.join('', 'a')", 'a') tester("ntpath.join('', 'a')", 'a')
tester("ntpath.join('', '', '', '', 'a')", 'a') tester("ntpath.join('', '', '', '', 'a')", 'a')
tester("ntpath.join('a', '')", 'a\\') tester("ntpath.join('a', '')", 'a\\')
tester("ntpath.join('a', '', '', '', '')", 'a\\') tester("ntpath.join('a', '', '', '', '')", 'a\\')
tester("ntpath.join('a\\', '')", 'a\\') tester("ntpath.join('a\\', '')", 'a\\')
tester("ntpath.join('a\\', '', '', '', '')", 'a\\') tester("ntpath.join('a\\', '', '', '', '')", 'a\\')
tester("ntpath.join('a/', '')", 'a/')
# from comment in ntpath.join tester("ntpath.join('a/b', 'x/y')", 'a/b\\x/y')
tester("ntpath.join('c:', '/a')", 'c:/a') tester("ntpath.join('/a/b', 'x/y')", '/a/b\\x/y')
tester("ntpath.join('//computer/share', '/a')", '//computer/share/a') tester("ntpath.join('/a/b/', 'x/y')", '/a/b/x/y')
tester("ntpath.join('c:/', '/a')", 'c:/a') tester("ntpath.join('c:', 'x/y')", 'c:x/y')
tester("ntpath.join('//computer/share/', '/a')", '//computer/share/a') tester("ntpath.join('c:a/b', 'x/y')", 'c:a/b\\x/y')
tester("ntpath.join('c:/a', '/b')", '/b') tester("ntpath.join('c:a/b/', 'x/y')", 'c:a/b/x/y')
tester("ntpath.join('//computer/share/a', '/b')", '/b') tester("ntpath.join('c:/', 'x/y')", 'c:/x/y')
tester("ntpath.join('c:', 'd:/')", 'd:/') tester("ntpath.join('c:/a/b', 'x/y')", 'c:/a/b\\x/y')
tester("ntpath.join('c:', '//computer/share/')", '//computer/share/') tester("ntpath.join('c:/a/b/', 'x/y')", 'c:/a/b/x/y')
tester("ntpath.join('//computer/share', 'd:/')", 'd:/') tester("ntpath.join('//computer/share', 'x/y')", '//computer/share\\x/y')
tester("ntpath.join('//computer/share', '//computer/share/')", '//computer/share/') tester("ntpath.join('//computer/share/', 'x/y')", '//computer/share/x/y')
tester("ntpath.join('c:/', 'd:/')", 'd:/') tester("ntpath.join('//computer/share/a/b', 'x/y')", '//computer/share/a/b\\x/y')
tester("ntpath.join('c:/', '//computer/share/')", '//computer/share/')
tester("ntpath.join('//computer/share/', 'd:/')", 'd:/')
tester("ntpath.join('//computer/share/', '//computer/share/')", '//computer/share/')
tester("ntpath.join('c:', '//computer/share/')", '//computer/share/') tester("ntpath.join('a/b', '/x/y')", '/x/y')
tester("ntpath.join('c:/', '//computer/share/')", '//computer/share/') tester("ntpath.join('/a/b', '/x/y')", '/x/y')
tester("ntpath.join('c:/', '//computer/share/a/b')", '//computer/share/a/b') tester("ntpath.join('c:', '/x/y')", 'c:/x/y')
tester("ntpath.join('c:a/b', '/x/y')", 'c:/x/y')
tester("ntpath.join('c:/', '/x/y')", 'c:/x/y')
tester("ntpath.join('c:/a/b', '/x/y')", 'c:/x/y')
tester("ntpath.join('//computer/share', '/x/y')", '//computer/share/x/y')
tester("ntpath.join('//computer/share/', '/x/y')", '//computer/share/x/y')
tester("ntpath.join('//computer/share/a', '/x/y')", '//computer/share/x/y')
tester("ntpath.join('c:', 'C:x/y')", 'C:x/y')
tester("ntpath.join('c:a/b', 'C:x/y')", 'C:a/b\\x/y')
tester("ntpath.join('c:/', 'C:x/y')", 'C:/x/y')
tester("ntpath.join('c:/a/b', 'C:x/y')", 'C:/a/b\\x/y')
for x in ('', 'a/b', '/a/b', 'c:', 'c:a/b', 'c:/', 'c:/a/b',
'//computer/share', '//computer/share/', '//computer/share/a/b'):
for y in ('d:', 'd:x/y', 'd:/', 'd:/x/y',
'//machine/common', '//machine/common/', '//machine/common/x/y'):
tester("ntpath.join(%r, %r)" % (x, y), y)
tester("ntpath.join('\\\\computer\\share\\', 'a', 'b')", '\\\\computer\\share\\a\\b') tester("ntpath.join('\\\\computer\\share\\', 'a', 'b')", '\\\\computer\\share\\a\\b')
tester("ntpath.join('\\\\computer\\share', 'a', 'b')", '\\\\computer\\share\\a\\b') tester("ntpath.join('\\\\computer\\share', 'a', 'b')", '\\\\computer\\share\\a\\b')

View File

@ -50,6 +50,9 @@ Core and Builtins
Library Library
------- -------
- Issue #19456: ntpath.join() now joins relative paths correctly when a drive
is present.
- Issue #19077: tempfile.TemporaryDirectory cleanup is now most likely - Issue #19077: tempfile.TemporaryDirectory cleanup is now most likely
successful when called during nulling out of modules during shutdown. successful when called during nulling out of modules during shutdown.
Misleading exception no longer raised when resource warning is emitted Misleading exception no longer raised when resource warning is emitted