Skip to content

Commit 707d066

Browse files
authored
pythonGH-129835: Yield path with trailing slash from ReadablePath.glob('') (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.
1 parent 6c67904 commit 707d066

File tree

4 files changed

+15
-30
lines changed

4 files changed

+15
-30
lines changed

Lib/glob.py

+12-27
Original file line numberDiff line numberDiff line change
@@ -352,12 +352,6 @@ def scandir(path):
352352
"""
353353
raise NotImplementedError
354354

355-
@staticmethod
356-
def add_slash(path):
357-
"""Returns a path with a trailing slash added.
358-
"""
359-
raise NotImplementedError
360-
361355
@staticmethod
362356
def concat_path(path, text):
363357
"""Implements path concatenation.
@@ -389,10 +383,12 @@ def selector(self, parts):
389383
def special_selector(self, part, parts):
390384
"""Returns a function that selects special children of the given path.
391385
"""
386+
if parts:
387+
part += self.sep
392388
select_next = self.selector(parts)
393389

394390
def select_special(path, exists=False):
395-
path = self.concat_path(self.add_slash(path), part)
391+
path = self.concat_path(path, part)
396392
return select_next(path, exists)
397393
return select_special
398394

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

403399
# Optimization: consume and join any subsequent literal parts here,
404400
# rather than leaving them for the next selector. This reduces the
405-
# number of string concatenation operations and calls to add_slash().
401+
# number of string concatenation operations.
406402
while parts and magic_check.search(parts[-1]) is None:
407403
part += self.sep + parts.pop()
404+
if parts:
405+
part += self.sep
408406

409407
select_next = self.selector(parts)
410408

411409
def select_literal(path, exists=False):
412-
path = self.concat_path(self.add_slash(path), part)
410+
path = self.concat_path(path, part)
413411
return select_next(path, exists=False)
414412
return select_literal
415413

@@ -437,7 +435,7 @@ def select_wildcard(path, exists=False):
437435
continue
438436
except OSError:
439437
continue
440-
if dir_only:
438+
entry_path = self.concat_path(entry_path, self.sep)
441439
yield from select_next(entry_path, exists=True)
442440
else:
443441
yield entry_path
@@ -467,7 +465,6 @@ def recursive_selector(self, part, parts):
467465
select_next = self.selector(parts)
468466

469467
def select_recursive(path, exists=False):
470-
path = self.add_slash(path)
471468
match_pos = len(str(path))
472469
if match is None or match(str(path), match_pos):
473470
yield from select_next(path, exists)
@@ -491,7 +488,10 @@ def select_recursive_step(stack, match_pos):
491488
pass
492489

493490
if is_dir or not dir_only:
494-
if match is None or match(str(entry_path), match_pos):
491+
entry_path_str = str(entry_path)
492+
if dir_only:
493+
entry_path = self.concat_path(entry_path, self.sep)
494+
if match is None or match(entry_path_str, match_pos):
495495
if dir_only:
496496
yield from select_next(entry_path, exists=True)
497497
else:
@@ -528,27 +528,12 @@ def scandir(path):
528528
entries = list(scandir_it)
529529
return ((entry, entry.name, entry.path) for entry in entries)
530530

531-
if os.name == 'nt':
532-
@staticmethod
533-
def add_slash(pathname):
534-
tail = os.path.splitroot(pathname)[2]
535-
if not tail or tail[-1] in '\\/':
536-
return pathname
537-
return f'{pathname}\\'
538-
else:
539-
@staticmethod
540-
def add_slash(pathname):
541-
if not pathname or pathname[-1] == '/':
542-
return pathname
543-
return f'{pathname}/'
544-
545531

546532
class _PathGlobber(_GlobberBase):
547533
"""Provides shell-style pattern matching and globbing for pathlib paths.
548534
"""
549535

550536
lexists = operator.methodcaller('exists', follow_symlinks=False)
551-
add_slash = operator.methodcaller('joinpath', '')
552537

553538
@staticmethod
554539
def scandir(path):

Lib/pathlib/_abc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
460460
recursive = True if recurse_symlinks else _no_recurse_symlinks
461461
globber = _PathGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
462462
select = globber.selector(parts)
463-
return select(self)
463+
return select(self.joinpath(''))
464464

465465
def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
466466
"""Recursively yield all existing files (of any kind, including

Lib/pathlib/_local.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
959959
globber = _StringGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
960960
select = globber.selector(parts[::-1])
961961
root = str(self)
962-
paths = select(root)
962+
paths = select(self.parser.join(root, ''))
963963

964964
# Normalize results
965965
if root == '.':

Lib/test/test_pathlib/test_pathlib_abc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,7 @@ def test_glob_windows(self):
11251125
def test_glob_empty_pattern(self):
11261126
P = self.cls
11271127
p = P(self.base)
1128-
self.assertEqual(list(p.glob("")), [p])
1128+
self.assertEqual(list(p.glob("")), [p.joinpath("")])
11291129

11301130
def test_glob_case_sensitive(self):
11311131
P = self.cls

0 commit comments

Comments
 (0)