"""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__ = ["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). """ name = name.lower() pat = pat.lower() return fnmatchcase(name, pat) def fnmatchcase(name, pat): """Test whether FILENAME matches PATTERN, including case. This is a version of fnmatch() which doesn't case-normalize its arguments. """ try: re_pat = _cache[pat] except KeyError: res = translate(pat) if len(_cache) >= _MAXCACHE: _cache.clear() _cache[pat] = re_pat = re.compile(res) return re_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 == '*': if i < n and pat[i] == '*': # is some flavor of "**" i = i + 1 # Treat **/ as ** so eat the "/" if i < n and pat[i] == '/': i = i + 1 if i >= n: # is "**EOF" - to align with .gitignore just accept all res = res + '.*' else: # is "**" # Note that this allows for any # of /'s (even 0) because # the .* will eat everything, even /'s res = res + '(.*/)?' else: # is "*" so map it to anything but "/" res = res + '[^/]*' elif c == '?': # "?" is any char except "/" 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 + '$'