Index: Lib/ntpath.py =================================================================== --- Lib/ntpath.py (revision 53961) +++ Lib/ntpath.py (working copy) @@ -15,7 +15,8 @@ "getatime","getctime", "islink","exists","lexists","isdir","isfile", "ismount","walk","expanduser","expandvars","normpath","abspath", "splitunc","curdir","pardir","sep","pathsep","defpath","altsep", - "extsep","devnull","realpath","supports_unicode_filenames"] + "extsep","devnull","realpath","supports_unicode_filenames", + "isrelative"] # strings representing various path-related bits and pieces curdir = '.' @@ -45,46 +46,59 @@ # Return whether a path is absolute. # Trivial in Posix, harder on the Mac or MS-DOS. -# For DOS it is absolute if it starts with a slash or backslash (current -# volume), or if a pathname after the volume letter and colon / UNC resource -# starts with a slash or backslash. +# On MS-DOS, paths cannot be divided into just 'relative' and 'absolute'. +# There are actually 4 different types of path: +# +# 1) Completely relative, e.g. foo\bar +# 2) Completely absolute, e.g. c:\foo\bar or \\server\share +# 3) Halfbreeds with no drive, e.g. \foo\bar +# 4) Halfbreeds relative to cwd on a drive, e.g. c:foo\bar def isabs(s): - """Test whether a path is absolute""" - s = splitdrive(s)[1] - return s != '' and s[:1] in '/\\' + """Test whether a path is absolute. + Note that, on Windows, this does not imply that the path is relative.""" + return ((s[1:2] == ':' and s[2:3] in '/\\') or + (s[0:1] in '/\\' and s[1:2] in '/\\')) +def isrelative(s): + """Return True iff the path is completely relative. + + Note that, on Windows, this does not imply that the path is absolute.""" + return s[1:2] != ':' and s[0:1] not in '/\\' + + # Join two (or more) paths. def join(a, *p): - """Join two or more pathname components, inserting "\\" as needed""" + """Join two or more pathname components, inserting "\\" as needed.""" path = a for b in p: - b_wins = 0 # set to 1 iff b makes path irrelevant - if path == "": - b_wins = 1 - - elif isabs(b): - # This probably wipes out path so far. However, it's more - # complicated if path begins with a drive letter: + if len(b) >= 2 and b[0] in '/\\' and b[1] in '/\\': + # Specifying a UNC. This wipes out path so far. E.g: + # join('anything', '//server/share') = '//server/share' + path = '' + elif os.path.splitdrive(b)[0]: + # Specifying a new drive. This wipes out path so far. E.g: + # join('c:', 'd:/') = 'd:/' + # join('c:/', 'd:/') = 'd:/' + # join('c:/', 'd:') = 'd:' + # join('c:', 'd:') = 'd:' + # join('/foo/', 'd:') = 'd:' + # join('foo/', 'd:') = 'd:' + path = '' + elif len(b) >= 1 and b[0] in '/\\': + # Adding a path relative to the root of a drive. + # Throw away everything apart from the drive spec (if present). + # E.g: # 1. join('c:', '/a') == 'c:/a' # 2. join('c:/', '/a') == 'c:/a' - # But # 3. join('c:/a', '/b') == '/b' - # 4. join('c:', 'd:/') = 'd:/' - # 5. join('c:/', 'd:/') = 'd:/' - if path[1:2] != ":" or b[1:2] == ":": - # Path doesn't start with a drive letter, or cases 4 and 5. - b_wins = 1 + # 4. join('/a', '/b') == '/b' + # 5. join('foo', '/b') == '/b' + path = splitdrive(path)[0] - # Else path has a drive letter, and b doesn't but is absolute. - elif len(path) > 3 or (len(path) == 3 and - path[-1] not in "/\\"): - # case 3 - b_wins = 1 - - if b_wins: + if path == '': path = b else: # Join, and ensure there's a separator. Index: Lib/test/test_ntpath.py =================================================================== --- Lib/test/test_ntpath.py (revision 53961) +++ Lib/test/test_ntpath.py (working copy) @@ -46,11 +46,43 @@ tester('ntpath.split("c:/")', ('c:/', '')) tester('ntpath.split("//conky/mountpoint/")', ('//conky/mountpoint', '')) +# Tests for isabs and isrelative: +# 0) Old tests tester('ntpath.isabs("c:\\")', 1) tester('ntpath.isabs("\\\\conky\\mountpoint\\")', 1) -tester('ntpath.isabs("\\foo")', 1) -tester('ntpath.isabs("\\foo\\bar")', 1) +tester('ntpath.isabs("\\foo")', 0) # CHANGED: Was 1 +tester('ntpath.isabs("\\foo\\bar")', 0) # CHANGED: Was 1 +#1) Completely relative, e.g. foo\bar +tester('ntpath.isrelative("foo\\bar")', True) +tester('ntpath.isrelative("foo/bar")', True) +tester('ntpath.isabs("foo\\bar")', False) +tester('ntpath.isabs("foo/bar")', False) + +#2) Completely absolute, e.g. c:\foo\bar ... +tester('ntpath.isrelative("c:\\foo\\bar")', False) +tester('ntpath.isrelative("c:/foo/bar")', False) +tester('ntpath.isabs("c:\\foo\\bar")', True) +tester('ntpath.isabs("c:/foo/bar")', True) + +# ... or \\server\share +tester('ntpath.isrelative("\\\\server\\\\share")', False) +tester('ntpath.isrelative("//server/share")', False) +tester('ntpath.isabs("\\\\server\\\\share")', True) +tester('ntpath.isabs("//server/share")', True) + +#3) Halfbreeds with no drive, e.g. \foo\bar +tester('ntpath.isrelative("\\foo\\bar")', False) +tester('ntpath.isrelative("/foo/bar")', False) +tester('ntpath.isabs("\\foo\\bar")', False) +tester('ntpath.isabs("/foo/bar")', False) + +#4) Halfbreeds relative to cwd on a drive, e.g. c:foo\bar +tester('ntpath.isrelative("c:foo\\bar")', False) +tester('ntpath.isrelative("c:foo/bar")', False) +tester('ntpath.isabs("c:foo\\bar")', False) +tester('ntpath.isabs("c:foo/bar")', False) + tester('ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])', "/home/swen") tester('ntpath.commonprefix(["\\home\\swen\\spam", "\\home\\swen\\eggs"])', @@ -58,6 +90,7 @@ tester('ntpath.commonprefix(["/home/swen/spam", "/home/swen/spam"])', "/home/swen/spam") +# Old join() tests tester('ntpath.join("")', '') tester('ntpath.join("", "", "")', '') tester('ntpath.join("a")', 'a') @@ -77,7 +110,7 @@ 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:/a', '/b')", 'c:/b') # CHANGED: Was '/b') tester("ntpath.join('c:', 'd:/')", 'd:/') tester("ntpath.join('c:/', 'd:/')", 'd:/') tester("ntpath.join('c:/', 'd:/a/b')", 'd:/a/b') @@ -92,6 +125,64 @@ tester("ntpath.join('a\\', '')", 'a\\') tester("ntpath.join('a\\', '', '', '', '')", 'a\\') +# New join() tests +# A note on test strategy: we take advantage of the fact that join +# works internally on pairs of paths, so we mostly only test pairs. +# (i.e. we expect r=join(a,b,c) to be identical to z=join(a,b) and +# r=join(z,c), so we only have to test join(a,b) and join(z,c) to +# cover this. And there are a lot less interesting combinations +# of 2 paths then there are combinations of 3 paths). +# +# Where possible we run each test twice, once with / and once with \\. +# +# Simple join +tester('ntpath.join("foo", "bar")', "foo\\bar") +tester('ntpath.join("c:", "bar")', "c:bar") +tester('ntpath.join("c:\\", "bar")', "c:\\bar") +tester('ntpath.join("c:/", "bar")', "c:/bar") +# join where 2nd arg has a drive +tester('ntpath.join("c:", "d:\\")', "d:\\") +tester('ntpath.join("c:", "d:/")', "d:/") +tester('ntpath.join("c:\\", "d:\\")', "d:\\") +tester('ntpath.join("c:/", "d:/")', "d:/") +tester('ntpath.join("c:\\", "d:")', "d:") +tester('ntpath.join("c:/", "d:")', "d:") +tester('ntpath.join("c:", "d:")', "d:") +tester('ntpath.join("\\foo\\", "d:")', "d:") +tester('ntpath.join("/foo/", "d:")', "d:") +tester('ntpath.join("foo\\", "d:")', "d:") +tester('ntpath.join("foo/", "d:")', "d:") +# join where 2nd arg has a slash +tester('ntpath.join("c:", "\\a")', "c:\\a") +tester('ntpath.join("c:", "/a")', "c:/a") +tester('ntpath.join("c:\\", "\\a")', "c:\\a") +tester('ntpath.join("c:/", "/a")', "c:/a") +tester('ntpath.join("c:\\a", "\\b")', "c:\\b") +tester('ntpath.join("c:/a", "/b")', "c:/b") +tester('ntpath.join("\\a", "\\b")', "\\b") +tester('ntpath.join("/a", "/b")', "/b") +tester('ntpath.join("foo", "\\b")', "\\b") +tester('ntpath.join("foo", "/b")', "/b") +# Join with a UNC +tester('ntpath.join("anything", "\\\\server\\share")', "\\\\server\\share") +tester('ntpath.join("anything", "//server/share")', "//server/share") +tester('ntpath.join("\\\\server\\share", "filename")', "\\\\server\\share\\filename") +tester('ntpath.join("//server/share", "filename")', "//server/share\\filename") +# Join to make a UNC +tester('ntpath.join("anything", "\\\\")', "\\\\") +tester('ntpath.join("anything", "//")', "//") +tester('ntpath.join("\\\\", "server")', "\\\\server") +tester('ntpath.join("//", "server")', "//server") +tester('ntpath.join("\\\\server", "share")', "\\\\server\\share") +tester('ntpath.join("//server", "share")', "//server\\share") +# Special case where 2nd arg is empty +tester('ntpath.join("a\\", "")', "a\\") +tester('ntpath.join("a", "")', "a\\") +# Join more than 2 path components: +tester('ntpath.join("foo", "bar", "baz")', "foo\\bar\\baz") +tester('ntpath.join("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")', + "1\\2\\3\\4\\5\\6\\7\\8\\9\\10") + tester("ntpath.normpath('A//////././//.//B')", r'A\B') tester("ntpath.normpath('A/./B')", r'A\B') tester("ntpath.normpath('A/foo/../B')", r'A\B') Index: Lib/macpath.py =================================================================== --- Lib/macpath.py (revision 53961) +++ Lib/macpath.py (working copy) @@ -9,7 +9,7 @@ "getatime","getctime", "islink","exists","lexists","isdir","isfile", "walk","expanduser","expandvars","normpath","abspath", "curdir","pardir","sep","pathsep","defpath","altsep","extsep", - "devnull","realpath","supports_unicode_filenames"] + "devnull","realpath","supports_unicode_filenames", "isrelative"] # strings representing various path-related bits and pieces curdir = ':' @@ -36,7 +36,11 @@ return ':' in s and s[0] != ':' +def isrelative(s): + """Test whether a path is relative""" + return not isabs(s) + def join(s, *p): path = s for t in p: Index: Lib/posixpath.py =================================================================== --- Lib/posixpath.py (revision 53961) +++ Lib/posixpath.py (working copy) @@ -20,7 +20,7 @@ "ismount","walk","expanduser","expandvars","normpath","abspath", "samefile","sameopenfile","samestat", "curdir","pardir","sep","pathsep","defpath","altsep","extsep", - "devnull","realpath","supports_unicode_filenames"] + "devnull","realpath","supports_unicode_filenames", "isrelative"] # strings representing various path-related bits and pieces curdir = '.' @@ -49,6 +49,9 @@ """Test whether a path is absolute""" return s.startswith('/') +def isrelative(s): + """Test whether a path is relative""" + return not isabs(s) # Join pathnames. # Ignore the previous parts if a part is absolute. Index: Lib/os2emxpath.py =================================================================== --- Lib/os2emxpath.py (revision 53961) +++ Lib/os2emxpath.py (working copy) @@ -9,14 +9,15 @@ import stat from genericpath import * from ntpath import (expanduser, expandvars, isabs, islink, splitdrive, - splitext, split, walk) + splitext, split, walk, isrelative) __all__ = ["normcase","isabs","join","splitdrive","split","splitext", "basename","dirname","commonprefix","getsize","getmtime", "getatime","getctime", "islink","exists","lexists","isdir","isfile", "ismount","walk","expanduser","expandvars","normpath","abspath", "splitunc","curdir","pardir","sep","pathsep","defpath","altsep", - "extsep","devnull","realpath","supports_unicode_filenames"] + "extsep","devnull","realpath","supports_unicode_filenames", + "isrelative"] # strings representing various path-related bits and pieces curdir = '.'