diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 8d08e8b..66b549d 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -46,14 +46,18 @@ class _Flavour(object): """A flavour implements a particular (platform-specific) set of path semantics.""" - def __init__(self): - self.join = self.sep.join + def join(self, parts): + if parts and parts[-1] == '.': + return ''.join([i+self.sep for i in parts[:-1]]) + else: + return self.sep.join(parts) def parse_parts(self, parts): parsed = [] sep = self.sep altsep = self.altsep drv = root = '' + dir_path = parts and parts[-1] and parts[-1][-1] in (sep, altsep) it = reversed(parts) for part in it: if not part: @@ -78,11 +82,21 @@ class _Flavour(object): if drv: break break + if not parsed: + dir_path = False if drv or root: parsed.append(drv + root) parsed.reverse() + if dir_path: + parsed.append('.') return drv, root, parsed + def join_parts_only(self, parts, parts2): + if parts and parts[-1] == '.': + return parts[:-1] + parts2 + else: + return parts + parts2 + def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2): """ Join the two paths represented by the respective @@ -94,10 +108,10 @@ class _Flavour(object): elif drv2: if drv2 == drv or self.casefold(drv2) == self.casefold(drv): # Same drive => second path is relative to the first - return drv, root, parts + parts2[1:] + return drv, root, self.join_parts_only(parts, parts2[1:]) else: # Second path is non-anchored (common case) - return drv, root, parts + parts2 + return drv, root, self.join_parts_only(parts, parts2) return drv2, root2, parts2 @@ -795,6 +809,8 @@ class PurePath(object): to_abs_parts = [to_drv, to_root] + to_parts[1:] else: to_abs_parts = to_parts + if to_abs_parts and to_abs_parts[-1] == '.': + del to_abs_parts[-1] n = len(to_abs_parts) cf = self._flavour.casefold_parts if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts): diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 2268f94..4a651b6 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -39,11 +39,11 @@ class _BaseFlavourTest(object): # Unanchored parts check([], ('', '', [])) check(['a'], ('', '', ['a'])) - check(['a/'], ('', '', ['a'])) + check(['a/'], ('', '', ['a', '.'])) check(['a', 'b'], ('', '', ['a', 'b'])) # Expansion check(['a/b'], ('', '', ['a', 'b'])) - check(['a/b/'], ('', '', ['a', 'b'])) + check(['a/b/'], ('', '', ['a', 'b', '.'])) check(['a', 'b/c', 'd'], ('', '', ['a', 'b', 'c', 'd'])) # Collapsing and stripping excess slashes check(['a', 'b//c', 'd'], ('', '', ['a', 'b', 'c', 'd'])) @@ -121,7 +121,7 @@ class NTFlavourTest(_BaseFlavourTest, unittest.TestCase): # UNC paths check(['a', '\\\\b\\c', 'd'], ('\\\\b\\c', '\\', ['\\\\b\\c\\', 'd'])) # Collapsing and stripping excess slashes - check(['a', 'Z:\\\\b\\\\c\\', 'd\\'], ('Z:', '\\', ['Z:\\', 'b', 'c', 'd'])) + check(['a', 'Z:\\\\b\\\\c\\', 'd\\'], ('Z:', '\\', ['Z:\\', 'b', 'c', 'd', '.'])) # UNC paths check(['a', '\\\\b\\c\\\\', 'd'], ('\\\\b\\c', '\\', ['\\\\b\\c\\', 'd'])) # Extended paths @@ -166,17 +166,23 @@ class _BasePurePathTest(object): # supposed to produce equal paths equivalences = { 'a/b': [ - ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'), - ('a/b/',), ('a//b',), ('a//b//',), + ('a', 'b'), ('a/', 'b'), ('a//b',), # empty components get removed ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''), ], + 'a/b/': [ + ('a', 'b/'), ('a/', 'b/'), ('a//b//',), + ('a', 'b', './'), + ], '/b/c/d': [ - ('a', '/b/c', 'd'), ('a', '///b//c', 'd/'), + ('a', '/b/c', 'd'), ('a', '///b//c/', 'd'), ('/a', '/b/c', 'd'), # empty components get removed ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'), ], + '/b/c/d/': [ + ('a', '/b/c', 'd/'), ('a', '///b//c/', 'd/'), + ], } def setUp(self): @@ -667,7 +673,8 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): equivalences = _BasePurePathTest.equivalences.copy() equivalences.update({ - 'c:a': [ ('c:', 'a'), ('c:', 'a/'), ('/', 'c:', 'a') ], + 'c:a': [ ('c:', 'a'), ('/', 'c:', 'a') ], + 'c:a/': [ ('c:', 'a/') ], 'c:/a': [ ('c:/', 'a'), ('c:', '/', 'a'), ('c:', '/a'), ('/z', 'c:/', 'a'), ('//x/y', 'c:/', 'a'), @@ -795,20 +802,22 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): P = self.cls p = P('z:a/b/') par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('z:a')) - self.assertEqual(par[1], P('z:')) - self.assertEqual(list(par), [P('z:a'), P('z:')]) + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('z:a/b')) + self.assertEqual(par[1], P('z:a')) + self.assertEqual(par[2], P('z:')) + self.assertEqual(list(par), [P('z:a/b'), P('z:a'), P('z:')]) with self.assertRaises(IndexError): - par[2] + par[3] p = P('z:/a/b/') par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('z:/a')) - self.assertEqual(par[1], P('z:/')) - self.assertEqual(list(par), [P('z:/a'), P('z:/')]) + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('z:/a/b')) + self.assertEqual(par[1], P('z:/a')) + self.assertEqual(par[2], P('z:/')) + self.assertEqual(list(par), [P('z:/a/b'),P('z:/a'), P('z:/')]) with self.assertRaises(IndexError): - par[2] + par[3] p = P('//a/b/c/d') par = p.parents self.assertEqual(len(par), 2)