Skip to content

Commit 175abcc

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 3dec84f commit 175abcc

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
@@ -444,7 +450,7 @@ def readlink(self, path):
444450
# Globbing helpers
445451
#
446452

447-
def _make_selector(pattern_parts):
453+
def _make_selector(pattern_parts, flavour):
448454
pat = pattern_parts[0]
449455
child_parts = pattern_parts[1:]
450456
if pat == '**':
@@ -455,7 +461,7 @@ def _make_selector(pattern_parts):
455461
cls = _WildcardSelector
456462
else:
457463
cls = _PreciseSelector
458-
return cls(pat, child_parts)
464+
return cls(pat, child_parts, flavour)
459465

460466
if hasattr(functools, "lru_cache"):
461467
_make_selector = functools.lru_cache()(_make_selector)
@@ -465,10 +471,10 @@ class _Selector:
465471
"""A selector matches a specific glob pattern part against the children
466472
of a given path."""
467473

468-
def __init__(self, child_parts):
474+
def __init__(self, child_parts, flavour):
469475
self.child_parts = child_parts
470476
if child_parts:
471-
self.successor = _make_selector(child_parts)
477+
self.successor = _make_selector(child_parts, flavour)
472478
self.dironly = True
473479
else:
474480
self.successor = _TerminatingSelector()
@@ -494,9 +500,9 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
494500

495501
class _PreciseSelector(_Selector):
496502

497-
def __init__(self, name, child_parts):
503+
def __init__(self, name, child_parts, flavour):
498504
self.name = name
499-
_Selector.__init__(self, child_parts)
505+
_Selector.__init__(self, child_parts, flavour)
500506

501507
def _select_from(self, parent_path, is_dir, exists, scandir):
502508
try:
@@ -510,13 +516,12 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
510516

511517
class _WildcardSelector(_Selector):
512518

513-
def __init__(self, pat, child_parts):
514-
self.pat = re.compile(fnmatch.translate(pat))
515-
_Selector.__init__(self, child_parts)
519+
def __init__(self, pat, child_parts, flavour):
520+
self.match = flavour.compile_pattern(pat)
521+
_Selector.__init__(self, child_parts, flavour)
516522

517523
def _select_from(self, parent_path, is_dir, exists, scandir):
518524
try:
519-
cf = parent_path._flavour.casefold
520525
entries = list(scandir(parent_path))
521526
for entry in entries:
522527
entry_is_dir = False
@@ -527,8 +532,7 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
527532
raise
528533
if not self.dironly or entry_is_dir:
529534
name = entry.name
530-
casefolded = cf(name)
531-
if self.pat.match(casefolded):
535+
if self.match(name):
532536
path = parent_path._make_child_relpath(name)
533537
for p in self.successor._select_from(path, is_dir, exists, scandir):
534538
yield p
@@ -539,8 +543,8 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
539543

540544
class _RecursiveWildcardSelector(_Selector):
541545

542-
def __init__(self, pat, child_parts):
543-
_Selector.__init__(self, child_parts)
546+
def __init__(self, pat, child_parts, flavour):
547+
_Selector.__init__(self, child_parts, flavour)
544548

545549
def _iterate_directories(self, parent_path, is_dir, scandir):
546550
yield parent_path
@@ -1101,11 +1105,10 @@ def glob(self, pattern):
11011105
"""
11021106
if not pattern:
11031107
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
1104-
pattern = self._flavour.casefold(pattern)
11051108
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
11061109
if drv or root:
11071110
raise NotImplementedError("Non-relative patterns are unsupported")
1108-
selector = _make_selector(tuple(pattern_parts))
1111+
selector = _make_selector(tuple(pattern_parts), self._flavour)
11091112
for p in selector.select_from(self):
11101113
yield p
11111114

@@ -1114,11 +1117,10 @@ def rglob(self, pattern):
11141117
directories) matching the given relative pattern, anywhere in
11151118
this subtree.
11161119
"""
1117-
pattern = self._flavour.casefold(pattern)
11181120
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
11191121
if drv or root:
11201122
raise NotImplementedError("Non-relative patterns are unsupported")
1121-
selector = _make_selector(("**",) + tuple(pattern_parts))
1123+
selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour)
11221124
for p in selector.select_from(self):
11231125
yield p
11241126

Lib/test/test_pathlib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2216,11 +2216,15 @@ def test_glob(self):
22162216
P = self.cls
22172217
p = P(BASE)
22182218
self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
2219+
self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") })
2220+
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\FILEa"})
2221+
self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"})
22192222

22202223
def test_rglob(self):
22212224
P = self.cls
22222225
p = P(BASE, "dirC")
22232226
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
2227+
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\FILEd"})
22242228

22252229
def test_expanduser(self):
22262230
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)