Commits

Antoine Pitrou committed 585a2e9

Add PurePath.match()

Comments (0)

Files changed (4)

 History
 -------
 
+Version 0.6
+^^^^^^^^^^^
+
+- Add PurePath.match()
+
 Version 0.5
 ^^^^^^^^^^^
 
       PureNTPath('c:\\Program Files')
 
 
+.. method:: PurePath.match(pattern)
+
+   Match this path against the provided glob-style pattern.  Return True
+   if matching is successful, False otherwise.
+
+   If *pattern* is relative, the path can be either relative or absolute,
+   and matching is done from the right::
+
+      >>> PurePath('a/b.py').match('*.py')
+      True
+      >>> PurePath('/a/b/c.py').match('b/*.py')
+      True
+      >>> PurePath('/a/b/c.py').match('a/*.py')
+      False
+
+   If *pattern* is absolute, the path must be absolute, and the whole path
+   must match::
+
+      >>> PurePath('/a.py').match('/*.py')
+      True
+      >>> PurePath('a/b.py').match('/*.py')
+      False
+
+   As with other methods, case-sensitivity is observed::
+
+      >>> PureNTPath('b.py').match('*.PY')
+      True
+
+
 .. method:: PurePath.normcase()
 
    Return a case-folded version of the path.  Calling this method is *not*
+import fnmatch
 import io
 import ntpath
 import os
         by the system, if any."""
         return self._flavour.is_reserved(self._parts)
 
+    def match(self, path_pattern):
+        """
+        Return True if this path matches the given pattern.
+        """
+        cf = self._flavour.casefold_parts
+        path_pattern, = cf((path_pattern,))
+        drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
+        if not pat_parts:
+            raise ValueError("empty pattern")
+        if drv and drv != cf((self._drv,))[0]:
+            return False
+        if root and root != cf((self._root,))[0]:
+            return False
+        parts = self._cparts
+        if drv or root:
+            if len(pat_parts) != len(parts):
+                return False
+            pat_parts = pat_parts[1:]
+        elif len(pat_parts) > len(parts):
+            return False
+        for part, pat in zip(reversed(parts), reversed(pat_parts)):
+            if not fnmatch.fnmatchcase(part, pat):
+                return False
+        return True
+
 
 class PurePosixPath(PurePath):
     _flavour = _posix_flavour
         self.assertNotEqual(P('/a/b'), P('/'))
         self.assertNotEqual(P(), P('/'))
 
+    def test_match_common(self):
+        P = self.cls
+        self.assertRaises(ValueError, P('a').match, '')
+        self.assertRaises(ValueError, P('a').match, '.')
+        # Simple relative pattern
+        self.assertTrue(P('b.py').match('b.py'))
+        self.assertTrue(P('a/b.py').match('b.py'))
+        self.assertTrue(P('/a/b.py').match('b.py'))
+        self.assertFalse(P('a.py').match('b.py'))
+        self.assertFalse(P('b/py').match('b.py'))
+        self.assertFalse(P('/a.py').match('b.py'))
+        self.assertFalse(P('b.py/c').match('b.py'))
+        # Wilcard relative pattern
+        self.assertTrue(P('b.py').match('*.py'))
+        self.assertTrue(P('a/b.py').match('*.py'))
+        self.assertTrue(P('/a/b.py').match('*.py'))
+        self.assertFalse(P('b.pyc').match('*.py'))
+        self.assertFalse(P('b./py').match('*.py'))
+        self.assertFalse(P('b.py/c').match('*.py'))
+        # Multi-part relative pattern
+        self.assertTrue(P('ab/c.py').match('a*/*.py'))
+        self.assertTrue(P('/d/ab/c.py').match('a*/*.py'))
+        self.assertFalse(P('a.py').match('a*/*.py'))
+        self.assertFalse(P('/dab/c.py').match('a*/*.py'))
+        self.assertFalse(P('ab/c.py/d').match('a*/*.py'))
+        # Absolute pattern
+        self.assertTrue(P('/b.py').match('/*.py'))
+        self.assertFalse(P('b.py').match('/*.py'))
+        self.assertFalse(P('a/b.py').match('/*.py'))
+        self.assertFalse(P('/a/b.py').match('/*.py'))
+        # Multi-part absolute pattern
+        self.assertTrue(P('/a/b.py').match('/a/*.py'))
+        self.assertFalse(P('/ab.py').match('/a/*.py'))
+        self.assertFalse(P('/a/b/c.py').match('/a/*.py'))
+
     def test_ordering_common(self):
         # Ordering is tuple-alike
         def assertLess(a, b):
         P = self.cls
         self.assertNotEqual(P('a/b'), P('A/b'))
 
+    def test_match(self):
+        P = self.cls
+        self.assertFalse(P('A.py').match('a.PY'))
+
     def test_is_absolute(self):
         P = self.cls
         self.assertFalse(P().is_absolute())
         self.assertEqual(P('C:a/B'), P('c:A/b'))
         self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b'))
 
+    def test_match_common(self):
+        P = self.cls
+        # Absolute patterns
+        self.assertTrue(P('c:/b.py').match('/*.py'))
+        self.assertTrue(P('c:/b.py').match('c:*.py'))
+        self.assertTrue(P('c:/b.py').match('c:/*.py'))
+        self.assertFalse(P('d:/b.py').match('c:/*.py'))  # wrong drive
+        self.assertFalse(P('b.py').match('/*.py'))
+        self.assertFalse(P('b.py').match('c:*.py'))
+        self.assertFalse(P('b.py').match('c:/*.py'))
+        self.assertFalse(P('c:b.py').match('/*.py'))
+        self.assertFalse(P('c:b.py').match('c:/*.py'))
+        self.assertFalse(P('/b.py').match('c:*.py'))
+        self.assertFalse(P('/b.py').match('c:/*.py'))
+        # UNC patterns
+        self.assertTrue(P('//some/share/a.py').match('/*.py'))
+        self.assertTrue(P('//some/share/a.py').match('//some/share/*.py'))
+        self.assertFalse(P('//other/share/a.py').match('//some/share/*.py'))
+        self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py'))
+        # Case-insensitivity
+        self.assertTrue(P('B.py').match('b.PY'))
+        self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY'))
+        self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY'))
+
     def test_ordering_common(self):
         # Case-insensitivity
         def assertOrderedEqual(a, b):