Skip to content

Commit

Permalink
pythonGH-129835: Yield path with trailing slash from `ReadablePath.gl…
Browse files Browse the repository at this point in the history
…ob('')` (python#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.
  • Loading branch information
barneygale authored Feb 8, 2025
1 parent 6c67904 commit 707d066
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 30 deletions.
39 changes: 12 additions & 27 deletions Lib/glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,6 @@ def scandir(path):
"""
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.
Expand Down Expand Up @@ -389,10 +383,12 @@ def selector(self, parts):
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

Expand All @@ -402,14 +398,16 @@ def literal_selector(self, part, parts):

# 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

Expand Down Expand Up @@ -437,7 +435,7 @@ def select_wildcard(path, exists=False):
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
Expand Down Expand Up @@ -467,7 +465,6 @@ def recursive_selector(self, part, parts):
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)
Expand All @@ -491,7 +488,10 @@ def select_recursive_step(stack, match_pos):
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:
Expand Down Expand Up @@ -528,27 +528,12 @@ def scandir(path):
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):
Expand Down
2 changes: 1 addition & 1 deletion Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
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
Expand Down
2 changes: 1 addition & 1 deletion Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
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 == '.':
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,7 @@ def test_glob_windows(self):
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
Expand Down

0 comments on commit 707d066

Please sign in to comment.