"""Filename matching with shell patterns.
fnmatch(FILENAME, PATTERN) matches according to the local convention.
fnmatchcase(FILENAME, PATTERN) always takes case in account.
The functions operate by translating the pattern into a regular
expression. They cache the compiled regular expressions for speed.
The function translate(PATTERN) returns a regular expression
corresponding to PATTERN. (It does not compile it.)
__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
"""Test whether FILENAME matches PATTERN.
Patterns are Unix shell style:
? matches any single character
[seq] matches any character in seq
[!seq] matches any char not in seq
An initial period in FILENAME is not special.
Both FILENAME and PATTERN are first case-normalized
if the operating system requires it.
If you don't want this, use fnmatchcase(FILENAME, PATTERN).
name = os.path.normcase(name)
pat = os.path.normcase(pat)
return fnmatchcase(name, pat)
@functools.lru_cache(maxsize=256, typed=True)
def _compile_pattern(pat):
if isinstance(pat, bytes):
pat_str = str(pat, 'ISO-8859-1')
res_str = translate(pat_str)
res = bytes(res_str, 'ISO-8859-1')
return re.compile(res).match
"""Return the subset of the list NAMES that match PAT."""
pat = os.path.normcase(pat)
match = _compile_pattern(pat)
# normcase on posix is NOP. Optimize it away from the loop.
if match(os.path.normcase(name)):
def fnmatchcase(name, pat):
"""Test whether FILENAME matches PATTERN, including case.
This is a version of fnmatch() which doesn't case-normalize
match = _compile_pattern(pat)
return match(name) is not None
"""Translate a shell PATTERN to a regular expression.
There is no way to quote meta-characters.
if j < n and pat[j] == '!':
if j < n and pat[j] == ']':
while j < n and pat[j] != ']':
stuff = pat[i:j].replace('\\','\\\\')
res = '%s[%s]' % (res, stuff)
return r'(?s:%s)\Z' % res