bpo-31202: Preserve case of literal parts in Path.glob() on Windows. (GH-16860)
(cherry picked from commit 10ecbadb79
)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
3dec84f40e
commit
175abccbbf
|
@ -187,6 +187,9 @@ class _WindowsFlavour(_Flavour):
|
|||
def casefold_parts(self, 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):
|
||||
s = str(path)
|
||||
if not s:
|
||||
|
@ -309,6 +312,9 @@ class _PosixFlavour(_Flavour):
|
|||
def casefold_parts(self, parts):
|
||||
return parts
|
||||
|
||||
def compile_pattern(self, pattern):
|
||||
return re.compile(fnmatch.translate(pattern)).fullmatch
|
||||
|
||||
def resolve(self, path, strict=False):
|
||||
sep = self.sep
|
||||
accessor = path._accessor
|
||||
|
@ -444,7 +450,7 @@ _normal_accessor = _NormalAccessor()
|
|||
# Globbing helpers
|
||||
#
|
||||
|
||||
def _make_selector(pattern_parts):
|
||||
def _make_selector(pattern_parts, flavour):
|
||||
pat = pattern_parts[0]
|
||||
child_parts = pattern_parts[1:]
|
||||
if pat == '**':
|
||||
|
@ -455,7 +461,7 @@ def _make_selector(pattern_parts):
|
|||
cls = _WildcardSelector
|
||||
else:
|
||||
cls = _PreciseSelector
|
||||
return cls(pat, child_parts)
|
||||
return cls(pat, child_parts, flavour)
|
||||
|
||||
if hasattr(functools, "lru_cache"):
|
||||
_make_selector = functools.lru_cache()(_make_selector)
|
||||
|
@ -465,10 +471,10 @@ class _Selector:
|
|||
"""A selector matches a specific glob pattern part against the children
|
||||
of a given path."""
|
||||
|
||||
def __init__(self, child_parts):
|
||||
def __init__(self, child_parts, flavour):
|
||||
self.child_parts = child_parts
|
||||
if child_parts:
|
||||
self.successor = _make_selector(child_parts)
|
||||
self.successor = _make_selector(child_parts, flavour)
|
||||
self.dironly = True
|
||||
else:
|
||||
self.successor = _TerminatingSelector()
|
||||
|
@ -494,9 +500,9 @@ class _TerminatingSelector:
|
|||
|
||||
class _PreciseSelector(_Selector):
|
||||
|
||||
def __init__(self, name, child_parts):
|
||||
def __init__(self, name, child_parts, flavour):
|
||||
self.name = name
|
||||
_Selector.__init__(self, child_parts)
|
||||
_Selector.__init__(self, child_parts, flavour)
|
||||
|
||||
def _select_from(self, parent_path, is_dir, exists, scandir):
|
||||
try:
|
||||
|
@ -510,13 +516,12 @@ class _PreciseSelector(_Selector):
|
|||
|
||||
class _WildcardSelector(_Selector):
|
||||
|
||||
def __init__(self, pat, child_parts):
|
||||
self.pat = re.compile(fnmatch.translate(pat))
|
||||
_Selector.__init__(self, child_parts)
|
||||
def __init__(self, pat, child_parts, flavour):
|
||||
self.match = flavour.compile_pattern(pat)
|
||||
_Selector.__init__(self, child_parts, flavour)
|
||||
|
||||
def _select_from(self, parent_path, is_dir, exists, scandir):
|
||||
try:
|
||||
cf = parent_path._flavour.casefold
|
||||
entries = list(scandir(parent_path))
|
||||
for entry in entries:
|
||||
entry_is_dir = False
|
||||
|
@ -527,8 +532,7 @@ class _WildcardSelector(_Selector):
|
|||
raise
|
||||
if not self.dironly or entry_is_dir:
|
||||
name = entry.name
|
||||
casefolded = cf(name)
|
||||
if self.pat.match(casefolded):
|
||||
if self.match(name):
|
||||
path = parent_path._make_child_relpath(name)
|
||||
for p in self.successor._select_from(path, is_dir, exists, scandir):
|
||||
yield p
|
||||
|
@ -539,8 +543,8 @@ class _WildcardSelector(_Selector):
|
|||
|
||||
class _RecursiveWildcardSelector(_Selector):
|
||||
|
||||
def __init__(self, pat, child_parts):
|
||||
_Selector.__init__(self, child_parts)
|
||||
def __init__(self, pat, child_parts, flavour):
|
||||
_Selector.__init__(self, child_parts, flavour)
|
||||
|
||||
def _iterate_directories(self, parent_path, is_dir, scandir):
|
||||
yield parent_path
|
||||
|
@ -1101,11 +1105,10 @@ class Path(PurePath):
|
|||
"""
|
||||
if not pattern:
|
||||
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
|
||||
pattern = self._flavour.casefold(pattern)
|
||||
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
|
||||
if drv or root:
|
||||
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):
|
||||
yield p
|
||||
|
||||
|
@ -1114,11 +1117,10 @@ class Path(PurePath):
|
|||
directories) matching the given relative pattern, anywhere in
|
||||
this subtree.
|
||||
"""
|
||||
pattern = self._flavour.casefold(pattern)
|
||||
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
|
||||
if drv or root:
|
||||
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):
|
||||
yield p
|
||||
|
||||
|
|
|
@ -2216,11 +2216,15 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase):
|
|||
P = self.cls
|
||||
p = P(BASE)
|
||||
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):
|
||||
P = self.cls
|
||||
p = P(BASE, "dirC")
|
||||
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):
|
||||
P = self.cls
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
The case the result of :func:`pathlib.WindowsPath.glob` matches now the case
|
||||
of the pattern for literal parts.
|
Loading…
Reference in New Issue