| """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 + '$' |