8000 GH-81079: Add case_sensitive argument to `pathlib.Path.glob()` by barneygale · Pull Request #102710 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

GH-81079: Add case_sensitive argument to pathlib.Path.glob() #102710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 4, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Restore _PreciseSelector
  • Loading branch information
barneygale committed May 3, 2023
commit d097f8c3b8e228ab75816a95b2d2daf395f09ce5
70 changes: 45 additions & 25 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _is_case_sensitive(flavour):
#

@functools.lru_cache()
def _make_selector(pattern_parts, case_sensitive):
def _make_selector(pattern_parts, flavour, case_sensitive):
pat = pattern_parts[0]
child_parts = pattern_parts[1:]
if not pat:
Expand All @@ -78,19 +78,21 @@ def _make_selector(pattern_parts, case_sensitive):
cls = _ParentSelector
elif '**' in pat:
raise ValueError("Invalid pattern: '**' can only be an entire path component")
else:
elif _is_wildcard_pattern(pat) or case_sensitive != _is_case_sensitive(flavour):
cls = _WildcardSelector
return cls(pat, child_parts, case_sensitive)
else:
cls = _PreciseSelector
return cls(pat, child_parts, flavour, case_sensitive)


class _Selector:
"""A selector matches a specific glob pattern part against the children
of a given path."""

def __init__(self, child_parts, case_sensitive):
def __init__(self, child_parts, flavour, case_sensitive):
self.child_parts = child_parts
if child_parts:
self.successor = _make_selector(child_parts, case_sensitive)
self.successor = _make_selector(child_parts, flavour, case_sensitive)
self.dironly = True
else:
self.successor = _TerminatingSelector()
Expand All @@ -100,37 +102,55 @@ def select_from(self, parent_path):
"""Iterate over all child paths of `parent_path` matched by this
selector. This can contain parent_path itself."""
path_cls = type(parent_path)
is_dir = path_cls.is_dir
exists = path_cls.exists
scandir = path_cls._scandir
if not parent_path.is_dir():
if not is_dir(parent_path):
return iter([])
return self._select_from(parent_path, scandir)
return self._select_from(parent_path, is_dir, exists, scandir)


class _TerminatingSelector:

def _select_from(self, parent_path, scandir):
def _select_from(self, parent_path, is_dir, exists, scandir):
yield parent_path


class _ParentSelector(_Selector):

def __init__(self, name, child_parts, case_sensitive):
_Selector.__init__(self, child_parts, case_sensitive)
def __init__(self, name, child_parts, flavour, case_sensitive):
_Selector.__init__(self, child_parts, flavour, case_sensitive)

def _select_from(self, parent_path, scandir):
def _select_from(self, parent_path, is_dir, exists, scandir):
path = parent_path._make_child_relpath('..')
for p in self.successor._select_from(path, scandir):
for p in self.successor._select_from(path, is_dir, exists, scandir):
yield p


class _PreciseSelector(_Selector):

def __init__(self, name, child_parts, flavour, case_sensitive):
self.name = name
_Selector.__init__(self, child_parts, flavour, case_sensitive)

def _select_from(self, parent_path, is_dir, exists, scandir):
try:
path = parent_path._make_child_relpath(self.name)
if (is_dir if self.dironly else exists)(path):
for p in self.successor._select_from(path, is_dir, exists, scandir):
yield p
except PermissionError:
return


class _WildcardSelector(_Selector):

def __init__(self, pat, child_parts, case_sensitive):
def __init__(self, pat, child_parts, flavour, case_sensitive):
flags = re.NOFLAG if case_sensitive else re.IGNORECASE
self.match = re.compile(fnmatch.translate(pat), flags=flags).fullmatch
_Selector.__init__(self, child_parts, case_sensitive)
_Selector.__init__(self, child_parts, flavour, case_sensitive)

def _select_from(self, parent_path, scandir):
def _select_from(self, parent_path, is_dir, exists, scandir):
try:
# We must close the scandir() object before proceeding to
# avoid exhausting file descriptors when globbing deep trees.
Expand All @@ -151,18 +171,18 @@ def _select_from(self, parent_path, scandir):
name = entry.name
if self.match(name):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(path, scandir):
for p in self.successor._select_from(path, is_dir, exists, scandir):
yield p
except PermissionError:
return


class _RecursiveWildcardSelector(_Selector):

def __init__(self, pat, child_parts, case_sensitive):
_Selector.__init__(self, child_parts, case_sensitive)
def __init__(self, pat, child_parts, flavour, case_sensitive):
_Selector.__init__(self, child_parts, flavour, case_sensitive)

def _iterate_directories(self, parent_path, scandir):
def _iterate_directories(self, parent_path, is_dir, scandir):
yield parent_path
try:
# We must close the scandir() object before proceeding to
Expand All @@ -178,18 +198,18 @@ def _iterate_directories(self, parent_path, scandir):
raise
if entry_is_dir and not entry.is_symlink():
path = parent_path._make_child_relpath(entry.name)
for p in self._iterate_directories(path, scandir):
for p in self._iterate_directories(path, is_dir, scandir):
yield p
except PermissionError:
return

def _select_from(self, parent_path, scandir):
def _select_from(self, parent_path, is_dir, exists, scandir):
try:
yielded = set()
try:
successor_select = self.successor._select_from
for starting_point in self._iterate_directories(parent_path, scandir):
for p in successor_select(starting_point, scandir):
for starting_point in self._iterate_directories(parent_path, is_dir, scandir):
for p in successor_select(starting_point, is_dir, exists, scandir):
if p not in yielded:
yield p
yielded.add(p)
Expand Down Expand Up @@ -839,7 +859,7 @@ def glob(self, pattern, *, case_sensitive=None):
pattern_parts.append('')
if case_sensitive is None:
case_sensitive = _is_case_sensitive(self._flavour)
selector = _make_selector(tuple(pattern_parts), case_sensitive)
selector = _make_selector(tuple(pattern_parts), self._flavour, case_sensitive)
for p in selector.select_from(self):
yield p

Expand All @@ -856,7 +876,7 @@ def rglob(self, pattern, *, case_sensitive=None):
pattern_parts.append('')
if case_sensitive is None:
case_sensitive = _is_case_sensitive(self._flavour)
selector = _make_selector(("**",) + tuple(pattern_parts), case_sensitive)
selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour, case_sensitive)
for p in selector.select_from(self):
yield p

Expand Down
0