]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-129835: Yield path with trailing slash from `ReadablePath.glob('')` (#129836)
authorBarney Gale <barney.gale@gmail.com>
Sat, 8 Feb 2025 06:47:09 +0000 (06:47 +0000)
committerGitHub <noreply@github.com>
Sat, 8 Feb 2025 06:47:09 +0000 (06:47 +0000)
In the private pathlib ABCs, make `ReadablePath.glob('')` yield a path with
a trailing slash (if it yields anything at all). As a result, `glob()`
works similarly to `joinpath()` when given a non-magic pattern.

In the globbing implementation, we preemptively add trailing slashes to
intermediate paths if there are pattern parts remaining; this removes the
need to check for existing trailing slashes (in the removed `add_slash()`
method) at subsequent steps.

Lib/glob.py
Lib/pathlib/_abc.py
Lib/pathlib/_local.py
Lib/test/test_pathlib/test_pathlib_abc.py

index a834ea7f7ce556250baae4257a0592c43e324030..cd8859e63318f3f9c5a1f442d6f8aa4e68ab85aa 100644 (file)
@@ -352,12 +352,6 @@ class _GlobberBase:
         """
         raise NotImplementedError
 
-    @staticmethod
-    def add_slash(path):
-        """Returns a path with a trailing slash added.
-        """
-        raise NotImplementedError
-
     @staticmethod
     def concat_path(path, text):
         """Implements path concatenation.
@@ -389,10 +383,12 @@ class _GlobberBase:
     def special_selector(self, part, parts):
         """Returns a function that selects special children of the given path.
         """
+        if parts:
+            part += self.sep
         select_next = self.selector(parts)
 
         def select_special(path, exists=False):
-            path = self.concat_path(self.add_slash(path), part)
+            path = self.concat_path(path, part)
             return select_next(path, exists)
         return select_special
 
@@ -402,14 +398,16 @@ class _GlobberBase:
 
         # Optimization: consume and join any subsequent literal parts here,
         # rather than leaving them for the next selector. This reduces the
-        # number of string concatenation operations and calls to add_slash().
+        # number of string concatenation operations.
         while parts and magic_check.search(parts[-1]) is None:
             part += self.sep + parts.pop()
+        if parts:
+            part += self.sep
 
         select_next = self.selector(parts)
 
         def select_literal(path, exists=False):
-            path = self.concat_path(self.add_slash(path), part)
+            path = self.concat_path(path, part)
             return select_next(path, exists=False)
         return select_literal
 
@@ -437,7 +435,7 @@ class _GlobberBase:
                                     continue
                             except OSError:
                                 continue
-                        if dir_only:
+                            entry_path = self.concat_path(entry_path, self.sep)
                             yield from select_next(entry_path, exists=True)
                         else:
                             yield entry_path
@@ -467,7 +465,6 @@ class _GlobberBase:
         select_next = self.selector(parts)
 
         def select_recursive(path, exists=False):
-            path = self.add_slash(path)
             match_pos = len(str(path))
             if match is None or match(str(path), match_pos):
                 yield from select_next(path, exists)
@@ -491,7 +488,10 @@ class _GlobberBase:
                         pass
 
                     if is_dir or not dir_only:
-                        if match is None or match(str(entry_path), match_pos):
+                        entry_path_str = str(entry_path)
+                        if dir_only:
+                            entry_path = self.concat_path(entry_path, self.sep)
+                        if match is None or match(entry_path_str, match_pos):
                             if dir_only:
                                 yield from select_next(entry_path, exists=True)
                             else:
@@ -528,27 +528,12 @@ class _StringGlobber(_GlobberBase):
             entries = list(scandir_it)
         return ((entry, entry.name, entry.path) for entry in entries)
 
-    if os.name == 'nt':
-        @staticmethod
-        def add_slash(pathname):
-            tail = os.path.splitroot(pathname)[2]
-            if not tail or tail[-1] in '\\/':
-                return pathname
-            return f'{pathname}\\'
-    else:
-        @staticmethod
-        def add_slash(pathname):
-            if not pathname or pathname[-1] == '/':
-                return pathname
-            return f'{pathname}/'
-
 
 class _PathGlobber(_GlobberBase):
     """Provides shell-style pattern matching and globbing for pathlib paths.
     """
 
     lexists = operator.methodcaller('exists', follow_symlinks=False)
-    add_slash = operator.methodcaller('joinpath', '')
 
     @staticmethod
     def scandir(path):
index d20f04fc5b6dc38aa189b179b6cbe41a630926dd..65d91e4d67b463a92441bddc5869cf71e1181539 100644 (file)
@@ -460,7 +460,7 @@ class ReadablePath(JoinablePath):
         recursive = True if recurse_symlinks else _no_recurse_symlinks
         globber = _PathGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
         select = globber.selector(parts)
-        return select(self)
+        return select(self.joinpath(''))
 
     def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
         """Recursively yield all existing files (of any kind, including
index 07d361d7b1352c917ec04d4715f715004ded2f62..6cdcd448991c8cd04b56c581de1fce26bb3090bd 100644 (file)
@@ -959,7 +959,7 @@ class Path(WritablePath, ReadablePath, PurePath):
         globber = _StringGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
         select = globber.selector(parts[::-1])
         root = str(self)
-        paths = select(root)
+        paths = select(self.parser.join(root, ''))
 
         # Normalize results
         if root == '.':
index 696874273a21fdf1f57dda83988c2ad576912d27..836d8387bdc43374497f68bba9aa411c4d4f9476 100644 (file)
@@ -1125,7 +1125,7 @@ class DummyReadablePathTest(DummyJoinablePathTest):
     def test_glob_empty_pattern(self):
         P = self.cls
         p = P(self.base)
-        self.assertEqual(list(p.glob("")), [p])
+        self.assertEqual(list(p.glob("")), [p.joinpath("")])
 
     def test_glob_case_sensitive(self):
         P = self.cls