"""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.) | |
""" | |
import re | |
__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"] | |
_cache = {} | |
_MAXCACHE = 100 | |
def _purge(): | |
"""Clear the pattern cache""" | |
_cache.clear() | |
def fnmatch(name, pat): | |
"""Test whether FILENAME matches PATTERN. | |
Patterns are Unix shell style: | |
* matches everything | |
? 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). | |
""" | |
import os | |
name = os.path.normcase(name) | |
pat = os.path.normcase(pat) | |
return fnmatchcase(name, pat) | |
def filter(names, pat): | |
"""Return the subset of the list NAMES that match PAT""" | |
import os,posixpath | |
result=[] | |
pat=os.path.normcase(pat) | |
if not pat in _cache: | |
res = translate(pat) | |
if len(_cache) >= _MAXCACHE: | |
_cache.clear() | |
_cache[pat] = re.compile(res) | |
match=_cache[pat].match | |
if os.path is posixpath: | |
# normcase on posix is NOP. Optimize it away from the loop. | |
for name in names: | |
if match(name): | |
result.append(name) | |
else: | |
for name in names: | |
if match(os.path.normcase(name)): | |
result.append(name) | |
return result | |
def fnmatchcase(name, pat): | |
"""Test whether FILENAME matches PATTERN, including case. | |
This is a version of fnmatch() which doesn't case-normalize | |
its arguments. | |
""" | |
if not pat in _cache: | |
res = translate(pat) | |
if len(_cache) >= _MAXCACHE: | |
_cache.clear() | |
_cache[pat] = re.compile(res) | |
return _cache[pat].match(name) is not None | |
def translate(pat): | |
"""Translate a shell PATTERN to a regular expression. | |
There is no way to quote meta-characters. | |
""" | |
i, n = 0, len(pat) | |
res = '' | |
while i < n: | |
c = pat[i] | |
i = i+1 | |
if c == '*': | |
res = res + '.*' | |
elif c == '?': | |
res = res + '.' | |
elif c == '[': | |
j = i | |
if j < n and pat[j] == '!': | |
j = j+1 | |
if j < n and pat[j] == ']': | |
j = j+1 | |
while j < n and pat[j] != ']': | |
j = j+1 | |
if j >= n: | |
res = res + '\\[' | |
else: | |
stuff = pat[i:j].replace('\\','\\\\') | |
i = j+1 | |
if stuff[0] == '!': | |
stuff = '^' + stuff[1:] | |
elif stuff[0] == '^': | |
stuff = '\\' + stuff | |
res = '%s[%s]' % (res, stuff) | |
else: | |
res = res + re.escape(c) | |
return res + '\Z(?ms)' |