Commit 10ecbadb authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

bpo-31202: Preserve case of literal parts in Path.glob() on Windows. (GH-16860)

parent 1e739454
...@@ -187,6 +187,9 @@ class _WindowsFlavour(_Flavour): ...@@ -187,6 +187,9 @@ class _WindowsFlavour(_Flavour):
def casefold_parts(self, parts): def casefold_parts(self, parts):
return [p.lower() for p in parts] return [p.lower() for p in parts]
def compile_pattern(self, pattern):
return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch
def resolve(self, path, strict=False): def resolve(self, path, strict=False):
s = str(path) s = str(path)
if not s: if not s:
...@@ -309,6 +312,9 @@ class _PosixFlavour(_Flavour): ...@@ -309,6 +312,9 @@ class _PosixFlavour(_Flavour):
def casefold_parts(self, parts): def casefold_parts(self, parts):
return parts return parts
def compile_pattern(self, pattern):
return re.compile(fnmatch.translate(pattern)).fullmatch
def resolve(self, path, strict=False): def resolve(self, path, strict=False):
sep = self.sep sep = self.sep
accessor = path._accessor accessor = path._accessor
...@@ -446,7 +452,7 @@ _normal_accessor = _NormalAccessor() ...@@ -446,7 +452,7 @@ _normal_accessor = _NormalAccessor()
# Globbing helpers # Globbing helpers
# #
def _make_selector(pattern_parts): def _make_selector(pattern_parts, flavour):
pat = pattern_parts[0] pat = pattern_parts[0]
child_parts = pattern_parts[1:] child_parts = pattern_parts[1:]
if pat == '**': if pat == '**':
...@@ -457,7 +463,7 @@ def _make_selector(pattern_parts): ...@@ -457,7 +463,7 @@ def _make_selector(pattern_parts):
cls = _WildcardSelector cls = _WildcardSelector
else: else:
cls = _PreciseSelector cls = _PreciseSelector
return cls(pat, child_parts) return cls(pat, child_parts, flavour)
if hasattr(functools, "lru_cache"): if hasattr(functools, "lru_cache"):
_make_selector = functools.lru_cache()(_make_selector) _make_selector = functools.lru_cache()(_make_selector)
...@@ -467,10 +473,10 @@ class _Selector: ...@@ -467,10 +473,10 @@ class _Selector:
"""A selector matches a specific glob pattern part against the children """A selector matches a specific glob pattern part against the children
of a given path.""" of a given path."""
def __init__(self, child_parts): def __init__(self, child_parts, flavour):
self.child_parts = child_parts self.child_parts = child_parts
if child_parts: if child_parts:
self.successor = _make_selector(child_parts) self.successor = _make_selector(child_parts, flavour)
self.dironly = True self.dironly = True
else: else:
self.successor = _TerminatingSelector() self.successor = _TerminatingSelector()
...@@ -496,9 +502,9 @@ class _TerminatingSelector: ...@@ -496,9 +502,9 @@ class _TerminatingSelector:
class _PreciseSelector(_Selector): class _PreciseSelector(_Selector):
def __init__(self, name, child_parts): def __init__(self, name, child_parts, flavour):
self.name = name self.name = name
_Selector.__init__(self, child_parts) _Selector.__init__(self, child_parts, flavour)
def _select_from(self, parent_path, is_dir, exists, scandir): def _select_from(self, parent_path, is_dir, exists, scandir):
try: try:
...@@ -512,13 +518,12 @@ class _PreciseSelector(_Selector): ...@@ -512,13 +518,12 @@ class _PreciseSelector(_Selector):
class _WildcardSelector(_Selector): class _WildcardSelector(_Selector):
def __init__(self, pat, child_parts): def __init__(self, pat, child_parts, flavour):
self.pat = re.compile(fnmatch.translate(pat)) self.match = flavour.compile_pattern(pat)
_Selector.__init__(self, child_parts) _Selector.__init__(self, child_parts, flavour)
def _select_from(self, parent_path, is_dir, exists, scandir): def _select_from(self, parent_path, is_dir, exists, scandir):
try: try:
cf = parent_path._flavour.casefold
entries = list(scandir(parent_path)) entries = list(scandir(parent_path))
for entry in entries: for entry in entries:
entry_is_dir = False entry_is_dir = False
...@@ -529,8 +534,7 @@ class _WildcardSelector(_Selector): ...@@ -529,8 +534,7 @@ class _WildcardSelector(_Selector):
raise raise
if not self.dironly or entry_is_dir: if not self.dironly or entry_is_dir:
name = entry.name name = entry.name
casefolded = cf(name) if self.match(name):
if self.pat.match(casefolded):
path = parent_path._make_child_relpath(name) path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(path, is_dir, exists, scandir): for p in self.successor._select_from(path, is_dir, exists, scandir):
yield p yield p
...@@ -541,8 +545,8 @@ class _WildcardSelector(_Selector): ...@@ -541,8 +545,8 @@ class _WildcardSelector(_Selector):
class _RecursiveWildcardSelector(_Selector): class _RecursiveWildcardSelector(_Selector):
def __init__(self, pat, child_parts): def __init__(self, pat, child_parts, flavour):
_Selector.__init__(self, child_parts) _Selector.__init__(self, child_parts, flavour)
def _iterate_directories(self, parent_path, is_dir, scandir): def _iterate_directories(self, parent_path, is_dir, scandir):
yield parent_path yield parent_path
...@@ -1118,11 +1122,10 @@ class Path(PurePath): ...@@ -1118,11 +1122,10 @@ class Path(PurePath):
""" """
if not pattern: if not pattern:
raise ValueError("Unacceptable pattern: {!r}".format(pattern)) raise ValueError("Unacceptable pattern: {!r}".format(pattern))
pattern = self._flavour.casefold(pattern)
drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
if drv or root: if drv or root:
raise NotImplementedError("Non-relative patterns are unsupported") raise NotImplementedError("Non-relative patterns are unsupported")
selector = _make_selector(tuple(pattern_parts)) selector = _make_selector(tuple(pattern_parts), self._flavour)
for p in selector.select_from(self): for p in selector.select_from(self):
yield p yield p
...@@ -1131,11 +1134,10 @@ class Path(PurePath): ...@@ -1131,11 +1134,10 @@ class Path(PurePath):
directories) matching the given relative pattern, anywhere in directories) matching the given relative pattern, anywhere in
this subtree. this subtree.
""" """
pattern = self._flavour.casefold(pattern)
drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
if drv or root: if drv or root:
raise NotImplementedError("Non-relative patterns are unsupported") raise NotImplementedError("Non-relative patterns are unsupported")
selector = _make_selector(("**",) + tuple(pattern_parts)) selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour)
for p in selector.select_from(self): for p in selector.select_from(self):
yield p yield p
......
...@@ -2378,11 +2378,15 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase): ...@@ -2378,11 +2378,15 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase):
P = self.cls P = self.cls
p = P(BASE) p = P(BASE)
self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") }) self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") })
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\FILEa"})
self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"})
def test_rglob(self): def test_rglob(self):
P = self.cls P = self.cls
p = P(BASE, "dirC") p = P(BASE, "dirC")
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") }) self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\FILEd"})
def test_expanduser(self): def test_expanduser(self):
P = self.cls P = self.cls
......
The case the result of :func:`pathlib.WindowsPath.glob` matches now the case
of the pattern for literal parts.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment