Skip to content

Commit 2f8d4f0

Browse files
bpo-31202: Preserve case of literal parts in Path.glob() on Windows. (GH-16860)
(cherry picked from commit 10ecbad) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent b1fc8c0 commit 2f8d4f0

File tree

3 files changed

+26
-18
lines changed

3 files changed

+26
-18
lines changed

Lib/pathlib.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ def casefold(self, s):
187187
def casefold_parts(self, parts):
188188
return [p.lower() for p in parts]
189189

190+
def compile_pattern(self, pattern):
191+
return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch
192+
190193
def resolve(self, path, strict=False):
191194
s = str(path)
192195
if not s:
@@ -309,6 +312,9 @@ def casefold(self, s):
309312
def casefold_parts(self, parts):
310313
return parts
311314

315+
def compile_pattern(self, pattern):
316+
return re.compile(fnmatch.translate(pattern)).fullmatch
317+
312318
def resolve(self, path, strict=False):
313319
sep = self.sep
314320
accessor = path._accessor
@@ -446,7 +452,7 @@ def readlink(self, path):
446452
# Globbing helpers
447453
#
448454

449-
def _make_selector(pattern_parts):
455+
def _make_selector(pattern_parts, flavour):
450456
pat = pattern_parts[0]
451457
child_parts = pattern_parts[1:]
452458
if pat == '**':
@@ -457,7 +463,7 @@ def _make_selector(pattern_parts):
457463
cls = _WildcardSelector
458464
else:
459465
cls = _PreciseSelector
460-
return cls(pat, child_parts)
466+
return cls(pat, child_parts, flavour)
461467

462468
if hasattr(functools, "lru_cache"):
463469
_make_selector = functools.lru_cache()(_make_selector)
@@ -467,10 +473,10 @@ class _Selector:
467473
"""A selector matches a specific glob pattern part against the children
468474
of a given path."""
469475

470-
def __init__(self, child_parts):
476+
def __init__(self, child_parts, flavour):
471477
self.child_parts = child_parts
472478
if child_parts:
473-
self.successor = _make_selector(child_parts)
479+
self.successor = _make_selector(child_parts, flavour)
474480
self.dironly = True
475481
else:
476482
self.successor = _TerminatingSelector()
@@ -496,9 +502,9 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
496502

497503
class _PreciseSelector(_Selector):
498504

499-
def __init__(self, name, child_parts):
505+
def __init__(self, name, child_parts, flavour):
500506
self.name = name
501-
_Selector.__init__(self, child_parts)
507+
_Selector.__init__(self, child_parts, flavour)
502508

503509
def _select_from(self, parent_path, is_dir, exists, scandir):
504510
try:
@@ -512,13 +518,12 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
512518

513519
class _WildcardSelector(_Selector):
514520

515-
def __init__(self, pat, child_parts):
516-
self.pat = re.compile(fnmatch.translate(pat))
517-
_Selector.__init__(self, child_parts)
521+
def __init__(self, pat, child_parts, flavour):
522+
self.match = flavour.compile_pattern(pat)
523+
_Selector.__init__(self, child_parts, flavour)
518524

519525
def _select_from(self, parent_path, is_dir, exists, scandir):
520526
try:
521-
cf = parent_path._flavour.casefold
522527
entries = list(scandir(parent_path))
523528
for entry in entries:
524529
entry_is_dir = False
@@ -529,8 +534,7 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
529534
raise
530535
if not self.dironly or entry_is_dir:
531536
name = entry.name
532-
casefolded = cf(name)
533-
if self.pat.match(casefolded):
537+
if self.match(name):
534538
path = parent_path._make_child_relpath(name)
535539
for p in self.successor._select_from(path, is_dir, exists, scandir):
536540
yield p
@@ -541,8 +545,8 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
541545

542546
class _RecursiveWildcardSelector(_Selector):
543547

544-
def __init__(self, pat, child_parts):
545-
_Selector.__init__(self, child_parts)
548+
def __init__(self, pat, child_parts, flavour):
549+
_Selector.__init__(self, child_parts, flavour)
546550

547551
def _iterate_directories(self, parent_path, is_dir, scandir):
548552
yield parent_path
@@ -1109,11 +1113,10 @@ def glob(self, pattern):
11091113
"""
11101114
if not pattern:
11111115
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
1112-
pattern = self._flavour.casefold(pattern)
11131116
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
11141117
if drv or root:
11151118
raise NotImplementedError("Non-relative patterns are unsupported")
1116-
selector = _make_selector(tuple(pattern_parts))
1119+
selector = _make_selector(tuple(pattern_parts), self._flavour)
11171120
for p in selector.select_from(self):
11181121
yield p
11191122

@@ -1122,11 +1125,10 @@ def rglob(self, pattern):
11221125
directories) matching the given relative pattern, anywhere in
11231126
this subtree.
11241127
"""
1125-
pattern = self._flavour.casefold(pattern)
11261128
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
11271129
if drv or root:
11281130
raise NotImplementedError("Non-relative patterns are unsupported")
1129-
selector = _make_selector(("**",) + tuple(pattern_parts))
1131+
selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour)
11301132
for p in selector.select_from(self):
11311133
yield p
11321134

Lib/test/test_pathlib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,11 +2291,15 @@ def test_glob(self):
22912291
P = self.cls
22922292
p = P(BASE)
22932293
self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
2294+
self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") })
2295+
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\FILEa"})
2296+
self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"})
22942297

22952298
def test_rglob(self):
22962299
P = self.cls
22972300
p = P(BASE, "dirC")
22982301
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
2302+
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\FILEd"})
22992303

23002304
def test_expanduser(self):
23012305
P = self.cls
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The case the result of :func:`pathlib.WindowsPath.glob` matches now the case
2+
of the pattern for literal parts.

0 commit comments

Comments
 (0)