diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2019-10-21 17:37:15 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-21 17:37:15 (GMT) |
commit | 10ecbadb799ddf3393d1fc80119a3db14724d381 (patch) | |
tree | cc60337c59e1c2e32a7f1564e68faf02cb8ececc | |
parent | 1e73945470644202262e1ddee2b49e2708a29794 (diff) | |
download | cpython-10ecbadb799ddf3393d1fc80119a3db14724d381.zip cpython-10ecbadb799ddf3393d1fc80119a3db14724d381.tar.gz cpython-10ecbadb799ddf3393d1fc80119a3db14724d381.tar.bz2 |
bpo-31202: Preserve case of literal parts in Path.glob() on Windows. (GH-16860)
-rw-r--r-- | Lib/pathlib.py | 38 | ||||
-rw-r--r-- | Lib/test/test_pathlib.py | 4 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2019-10-20-12-04-48.bpo-31202.NfdIus.rst | 2 |
3 files changed, 26 insertions, 18 deletions
diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 7bd66c1..67b94e0 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -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 @@ -446,7 +452,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 == '**': @@ -457,7 +463,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) @@ -467,10 +473,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() @@ -496,9 +502,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: @@ -512,13 +518,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 @@ -529,8 +534,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 @@ -541,8 +545,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 @@ -1118,11 +1122,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 @@ -1131,11 +1134,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 diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index f3304f0..221c272 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2378,11 +2378,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 diff --git a/Misc/NEWS.d/next/Library/2019-10-20-12-04-48.bpo-31202.NfdIus.rst b/Misc/NEWS.d/next/Library/2019-10-20-12-04-48.bpo-31202.NfdIus.rst new file mode 100644 index 0000000..8edb09d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-10-20-12-04-48.bpo-31202.NfdIus.rst @@ -0,0 +1,2 @@ +The case the result of :func:`pathlib.WindowsPath.glob` matches now the case +of the pattern for literal parts. |