Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 6 additions & 3 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,8 @@ Pure paths provide the following methods and properties:
Return ``True`` if matching is successful, ``False`` otherwise.

This method is similar to :meth:`~PurePath.full_match`, but empty patterns
aren't allowed (:exc:`ValueError` is raised), the recursive wildcard
"``**``" isn't supported (it acts like non-recursive "``*``"), and if a
relative pattern is provided, then matching is done from the right::
aren't allowed (:exc:`ValueError` is raised), and if a relative pattern is
provided, then matching is done from the right::

>>> PurePath('a/b.py').match('*.py')
True
Expand All @@ -609,6 +608,10 @@ Pure paths provide the following methods and properties:
.. versionchanged:: 3.12
The *case_sensitive* parameter was added.

.. versionchanged:: next
Added support for the recursive wildcard "``**``". In previous versions,
it acted like non-recursive "``*``".


.. method:: PurePath.relative_to(other, walk_up=False)

Expand Down
23 changes: 8 additions & 15 deletions Lib/pathlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,27 +566,20 @@ def match(self, path_pattern, *, case_sensitive=None):
"""
Return True if this path matches the given pattern. If the pattern is
relative, matching is done from the right; otherwise, the entire path
is matched. The recursive wildcard '**' is *not* supported by this
method.
is matched.
"""
if not hasattr(path_pattern, 'with_segments'):
path_pattern = self.with_segments(path_pattern)
if case_sensitive is None:
case_sensitive = self.parser is posixpath
path_parts = self.parts[::-1]
pattern_parts = path_pattern.parts[::-1]
if not pattern_parts:
if not path_pattern.parts:
raise ValueError("empty pattern")
if len(path_parts) < len(pattern_parts):
return False
if len(path_parts) > len(pattern_parts) and path_pattern.anchor:
return False
globber = _StringGlobber(self.parser.sep, case_sensitive)
for path_part, pattern_part in zip(path_parts, pattern_parts):
match = globber.compile(pattern_part)
if match(path_part) is None:
return False
return True
if not path_pattern.anchor:
path_pattern = '**' / path_pattern
path = str(self) if self.parts else ''
pattern = str(path_pattern)
globber = _StringGlobber(self.parser.sep, case_sensitive, recursive=True)
return globber.compile(pattern)(path) is not None

# Subclassing os.PathLike makes isinstance() checks slower,
# which in turn makes Path construction slower. Register instead!
Expand Down
11 changes: 9 additions & 2 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,16 +584,23 @@ def test_match_common(self):
self.assertFalse(P('/ab.py').match('/a/*.py'))
self.assertFalse(P('/a/b/c.py').match('/a/*.py'))
# Multi-part glob-style pattern.
self.assertFalse(P('/a/b/c.py').match('/**/*.py'))
self.assertTrue(P('/a/b/c.py').match('/**/*.py'))
self.assertTrue(P('/a/b/c.py').match('/a/**/*.py'))
self.assertTrue(P('/a/b/c.py').match('/a/b/**/*.py'))
self.assertTrue(P('/a/b/c.py').match('/**/**/**/**/*.py'))
self.assertTrue(P('/a/b/c.py').match('/**'))
self.assertTrue(P('/a/b/c.py').match('/a/**'))
self.assertFalse(P('a/b/c.py').match('/a/b/c.py/**'))
self.assertFalse(P('a/b/c.py').match('/**/a/b/c.py'))
self.assertFalse(P('c.py').match('c/**'))
# Case-sensitive flag
self.assertFalse(P('A.py').match('a.PY', case_sensitive=True))
self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
# Matching against empty path
self.assertFalse(P('').match('*'))
self.assertFalse(P('').match('**'))
self.assertTrue(P('').match('**'))
self.assertFalse(P('').match('**/*'))

@needs_posix
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for the recursive wildcard "``**``" in
:meth:`pathlib.PurePath.match`.
Loading