GH-129835: Yield path with trailing slash from ReadablePath.glob('') (#129836)

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.
This commit is contained in:
Barney Gale 2025-02-08 06:47:09 +00:00 committed by GitHub
parent 6c67904e79
commit 707d066193
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 15 additions and 30 deletions

View 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):

View 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

View 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 == '.':

View 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