diff -r 5fda36bff39d Doc/library/os.path.rst --- a/Doc/library/os.path.rst Mon Nov 18 13:06:43 2013 +0200 +++ b/Doc/library/os.path.rst Mon Nov 18 19:26:36 2013 +0200 @@ -201,7 +201,7 @@ path, all previous components (on Windows, including the previous drive letter, if there was one) are thrown away, and joining continues. The return value is the concatenation of *path1*, and optionally *path2*, etc., with exactly one - directory separator (``os.sep``) following each non-empty part except the last. + directory separator (:attr:``os.sep``) following each non-empty part except the last. (This means that an empty last part will result in a path that ends with a separator.) Note that on Windows, since there is a current directory for each drive, ``os.path.join("c:", "foo")`` represents a path relative to the @@ -290,8 +290,8 @@ *path* is empty, both *head* and *tail* are empty. Trailing slashes are stripped from *head* unless it is the root (one or more slashes only). In all cases, ``join(head, tail)`` returns a path to the same location as *path* - (but the strings may differ). Also see the functions :func:`dirname` and - :func:`basename`. + (but the strings may differ). Also see the functions :func:`dirname`, + :func:`basename` and :func:`splitpath`. .. function:: splitdrive(path) @@ -320,6 +320,49 @@ returns ``('.cshrc', '')``. +.. function:: splitpath(path) + + Split the pathname *path* into a list of components using the same + algorithm as :func:`split`. Equivalent to:: + + def splitpath(path): + head, tail = split(path) + if head == path: + return [head] + return splitpath(head) + [tail] + + * The first element is always the root component (for an absolute path), + or an empty string (for a relative path). + + * The last element is an empty string if the path name ended in a directory + separator, except when the path is a root directory. + + * ``join(*splitpath(path))`` returns a path to the same location as *path* + (but the strings may differ). + + Examples on Windows:: + + >>> splitpath('C:\\Program Files\\Python\\python.exe') + ['C:\\', 'Program Files', 'Python', 'python.exe'] + >>> splitpath('C:Program Files\\Python\\python.exe') + ['C:', 'Program Files', 'Python', 'python.exe'] + >>> splitpath('Program Files\\Python\\python.exe') + ['', 'Program Files', 'Python', 'python.exe'] + >>> splitpath('C:\\Program Files\\Python\\') + ['C:\\', 'Program Files', 'Python', ''] + + Examples on Unix:: + + >>> splitpath('/usr/bin/python') + ['/', 'usr', 'bin', 'python'] + >>> splitpath('usr/bin/python') + ['', 'usr', 'bin', 'python'] + >>> splitpath('/usr/bin/') + ['/', 'usr', 'bin', ''] + + .. versionadded:: 3.4 + + .. function:: splitunc(path) .. deprecated:: 3.1 diff -r 5fda36bff39d Lib/macpath.py --- a/Lib/macpath.py Mon Nov 18 13:06:43 2013 +0200 +++ b/Lib/macpath.py Mon Nov 18 19:26:36 2013 +0200 @@ -10,7 +10,7 @@ "getatime","getctime", "islink","exists","lexists","isdir","isfile", "expanduser","expandvars","normpath","abspath", "curdir","pardir","sep","pathsep","defpath","altsep","extsep", - "devnull","realpath","supports_unicode_filenames"] + "devnull","realpath","supports_unicode_filenames", "splitpath"] # strings representing various path-related bits and pieces # These are primarily for export; internally, they are hardcoded. @@ -82,6 +82,20 @@ return path, file +def splitpath(p): + """Split the pathname into a list of components.""" + # Inefficient generic implementation + result = [] + while True: + head, tail = split(p) + if head == p: + result.append(head) + break + result.append(tail) + p = head + return result[::-1] + + def splitext(p): if isinstance(p, bytes): return genericpath._splitext(p, b':', altsep, b'.') diff -r 5fda36bff39d Lib/ntpath.py --- a/Lib/ntpath.py Mon Nov 18 13:06:43 2013 +0200 +++ b/Lib/ntpath.py Mon Nov 18 19:26:36 2013 +0200 @@ -5,6 +5,7 @@ module as os.path. """ +import re import os import sys import stat @@ -17,7 +18,7 @@ "ismount", "expanduser","expandvars","normpath","abspath", "splitunc","curdir","pardir","sep","pathsep","defpath","altsep", "extsep","devnull","realpath","supports_unicode_filenames","relpath", - "samefile", "sameopenfile", "samestat",] + "samefile", "sameopenfile", "samestat", "splitpath"] # strings representing various path-related bits and pieces # These are primarily for export; internally, they are hardcoded. @@ -32,6 +33,9 @@ defpath = '\\Windows' devnull = 'nul' +seps_re = re.compile(r'[\\/]+') +bseps_re = re.compile(br'[\\/]+') + def _get_empty(path): if isinstance(path, bytes): return b'' @@ -288,6 +292,22 @@ return d + head, tail +def splitpath(p): + """Split the pathname into a list of components.""" + root, path = splitdrive(p) + if isinstance(p, bytes): + seps = bseps_re + else: + seps = seps_re + m = seps.match(path) + if m is not None: + root += m.group() + path = path[m.end():] + if not path: + return [root] + return [root] + seps.split(path) + + # Split a path in root and extension. # The extension is everything starting at the last dot in the last # pathname component; the root is everything before that. diff -r 5fda36bff39d Lib/posixpath.py --- a/Lib/posixpath.py Mon Nov 18 13:06:43 2013 +0200 +++ b/Lib/posixpath.py Mon Nov 18 19:26:36 2013 +0200 @@ -22,7 +22,8 @@ "ismount", "expanduser","expandvars","normpath","abspath", "samefile","sameopenfile","samestat", "curdir","pardir","sep","pathsep","defpath","altsep","extsep", - "devnull","realpath","supports_unicode_filenames","relpath"] + "devnull","realpath","supports_unicode_filenames","relpath", + "splitpath"] # Strings representing various path-related bits and pieces. # These are primarily for export; internally, they are hardcoded. @@ -110,6 +111,21 @@ return head, tail +def splitpath(p): + """Split the pathname into a list of components.""" + sep = _get_sep(p) + sepc = sep[0] + for i, c in enumerate(p): + if c != sepc: + break + else: + return [p] + result = [p[:i]] + list(filter(None, p[i:].split(sep))) + if p[-1] == sepc: + result.append(p[:0]) + return result + + # Split a path in root and extension. # The extension is everything starting at the last dot in the last # pathname component; the root is everything before that. diff -r 5fda36bff39d Lib/test/test_macpath.py --- a/Lib/test/test_macpath.py Mon Nov 18 13:06:43 2013 +0200 +++ b/Lib/test/test_macpath.py Mon Nov 18 19:26:36 2013 +0200 @@ -46,6 +46,34 @@ self.assertEqual(split(b":conky:mountpoint:"), (b':conky:mountpoint', b'')) + def test_splitpath(self): + splitpath = macpath.splitpath + self.assertEqual(splitpath("foo"), ['', 'foo']) + self.assertEqual(splitpath(":foo"), ['', 'foo']) + self.assertEqual(splitpath("foo:"), ['foo:']) + self.assertEqual(splitpath("foo:bar"), ['foo:', 'bar']) + self.assertEqual(splitpath("foo::bar"), ['foo:', 'bar']) + self.assertEqual(splitpath("foo:::bar"), ['foo:', '', 'bar']) + self.assertEqual(splitpath("foo:bar:"), ['foo:', 'bar', '']) + self.assertEqual(splitpath("conky:mountpoint:foo:bar"), + ['conky:', 'mountpoint', 'foo', 'bar']) + self.assertEqual(splitpath(":"), ['', '']) + self.assertEqual(splitpath(":conky:mountpoint:"), + ['', 'conky', 'mountpoint', '']) + + self.assertEqual(splitpath(b"foo"), [b'', b'foo']) + self.assertEqual(splitpath(b":foo"), [b'', b'foo']) + self.assertEqual(splitpath(b"foo:"), [b'foo:']) + self.assertEqual(splitpath(b"foo:bar"), [b'foo:', b'bar']) + self.assertEqual(splitpath(b"foo::bar"), [b'foo:', b'bar']) + self.assertEqual(splitpath(b"foo:::bar"), [b'foo:', b'', b'bar']) + self.assertEqual(splitpath(b"foo:bar:"), [b'foo:', b'bar', b'']) + self.assertEqual(splitpath(b"conky:mountpoint:foo:bar"), + [b'conky:', b'mountpoint', b'foo', b'bar']) + self.assertEqual(splitpath(b":"), [b'', b'']) + self.assertEqual(splitpath(b":conky:mountpoint:"), + [b'', b'conky', b'mountpoint', b'']) + def test_join(self): join = macpath.join self.assertEqual(join('a', 'b'), ':a:b') diff -r 5fda36bff39d Lib/test/test_ntpath.py --- a/Lib/test/test_ntpath.py Mon Nov 18 13:06:43 2013 +0200 +++ b/Lib/test/test_ntpath.py Mon Nov 18 19:26:36 2013 +0200 @@ -29,6 +29,8 @@ wantResult = wantResult.encode('ascii') elif isinstance(wantResult, tuple): wantResult = tuple(r.encode('ascii') for r in wantResult) + elif isinstance(wantResult, list): + wantResult = [r.encode('ascii') for r in wantResult] gotResult = eval(fn) if wantResult != gotResult: @@ -79,6 +81,48 @@ tester('ntpath.split("c:/")', ('c:/', '')) tester('ntpath.split("//conky/mountpoint/")', ('//conky/mountpoint/', '')) + def test_splitpath(self): + tester('ntpath.splitpath("foo\\bar")', ['', 'foo', 'bar']) + tester('ntpath.splitpath("foo/bar")', ['', 'foo', 'bar']) + tester('ntpath.splitpath("")', ['']) + tester('ntpath.splitpath("foo\\bar\\")', ['', 'foo', 'bar', '']) + tester('ntpath.splitpath("foo/bar/")', ['', 'foo', 'bar', '']) + tester('ntpath.splitpath("c:foo\\bar")', ['c:', 'foo', 'bar']) + tester('ntpath.splitpath("c:foo/bar")', ['c:', 'foo', 'bar']) + tester('ntpath.splitpath("c:")', ['c:']) + + tester('ntpath.splitpath("\\foo\\bar")', ['\\', 'foo', 'bar']) + tester('ntpath.splitpath("/foo/bar")', ['/', 'foo', 'bar']) + tester('ntpath.splitpath("\\")', ['\\']) + tester('ntpath.splitpath("/")', ['/']) + tester('ntpath.splitpath("c:\\foo\\bar")', ['c:\\', 'foo', 'bar']) + tester('ntpath.splitpath("c:/foo/bar")', ['c:/', 'foo', 'bar']) + tester('ntpath.splitpath("c:\\")', ['c:\\']) + tester('ntpath.splitpath("c:/")', ['c:/']) + tester('ntpath.splitpath("c:\\\\foo\\bar")', ['c:\\\\', 'foo', 'bar']) + tester('ntpath.splitpath("c://foo/bar")', ['c://', 'foo', 'bar']) + + tester('ntpath.splitpath("\\\\conky\\mountpoint\\foo\\bar")', + ['\\\\conky\\mountpoint\\', 'foo', 'bar']) + tester('ntpath.splitpath("//conky/mountpoint/foo/bar")', + ['//conky/mountpoint/', 'foo', 'bar']) + tester('ntpath.splitpath("\\\\conky\\mountpoint\\")', + ['\\\\conky\\mountpoint\\']) + tester('ntpath.splitpath("//conky/mountpoint/")', + ['//conky/mountpoint/']) + tester('ntpath.splitpath("\\\\\\conky\\mountpoint\\foo\\bar")', + ['\\\\\\', 'conky', 'mountpoint', 'foo', 'bar']) + tester('ntpath.splitpath("///conky/mountpoint/foo/bar")', + ['///', 'conky', 'mountpoint', 'foo', 'bar']) + tester('ntpath.splitpath("\\\\conky\\\\mountpoint\\foo\\bar")', + ['\\\\', 'conky', 'mountpoint', 'foo', 'bar']) + tester('ntpath.splitpath("//conky//mountpoint/foo/bar")', + ['//', 'conky', 'mountpoint', 'foo', 'bar']) + + def test_splitpath_long_path(self): + self.assertEqual(ntpath.splitpath('c:' + '/spam' * 10**5), + ['c:/'] + ['spam'] * 10**5) + def test_isabs(self): tester('ntpath.isabs("c:\\")', 1) tester('ntpath.isabs("\\\\conky\\mountpoint\\")', 1) diff -r 5fda36bff39d Lib/test/test_posixpath.py --- a/Lib/test/test_posixpath.py Mon Nov 18 13:06:43 2013 +0200 +++ b/Lib/test/test_posixpath.py Mon Nov 18 19:26:36 2013 +0200 @@ -86,6 +86,28 @@ self.assertEqual(posixpath.split(b"////foo"), (b"////", b"foo")) self.assertEqual(posixpath.split(b"//foo//bar"), (b"//foo", b"bar")) + def test_splitpath(self): + splitpath = posixpath.splitpath + self.assertEqual(splitpath('foo/bar'), ['', 'foo', 'bar']) + self.assertEqual(splitpath('foo/bar/'), ['', 'foo', 'bar', '']) + self.assertEqual(splitpath(''), ['']) + self.assertEqual(splitpath('/foo/bar'), ['/', 'foo', 'bar']) + self.assertEqual(splitpath('/'), ['/']) + self.assertEqual(splitpath('////foo/bar'), ['////', 'foo', 'bar']) + self.assertEqual(splitpath('//foo//bar'), ['//', 'foo', 'bar']) + + self.assertEqual(splitpath(b'foo/bar'), [b'', b'foo', b'bar']) + self.assertEqual(splitpath(b'foo/bar/'), [b'', b'foo', b'bar', b'']) + self.assertEqual(splitpath(b''), [b'']) + self.assertEqual(splitpath(b'/foo/bar'), [b'/', b'foo', b'bar']) + self.assertEqual(splitpath(b'/'), [b'/']) + self.assertEqual(splitpath(b'////foo/bar'), [b'////', b'foo', b'bar']) + self.assertEqual(splitpath(b'//foo//bar'), [b'//', b'foo', b'bar']) + + def test_splitpath_long_path(self): + self.assertEqual(posixpath.splitpath('/spam' * 10**5), + ['/'] + ['spam'] * 10**5) + def splitextTest(self, path, filename, ext): self.assertEqual(posixpath.splitext(path), (filename, ext)) self.assertEqual(posixpath.splitext("/" + path), ("/" + filename, ext))