1"""Filename matching with shell patterns. 2 3fnmatch(FILENAME, PATTERN) matches according to the local convention. 4fnmatchcase(FILENAME, PATTERN) always takes case in account. 5 6The functions operate by translating the pattern into a regular 7expression. They cache the compiled regular expressions for speed. 8 9The function translate(PATTERN) returns a regular expression 10corresponding to PATTERN. (It does not compile it.) 11""" 12 13import re 14 15__all__ = ["fnmatch", "fnmatchcase", "translate"] 16 17_cache = {} 18_MAXCACHE = 100 19 20 21def _purge(): 22 """Clear the pattern cache""" 23 _cache.clear() 24 25 26def fnmatch(name, pat): 27 """Test whether FILENAME matches PATTERN. 28 29 Patterns are Unix shell style: 30 31 * matches everything 32 ? matches any single character 33 [seq] matches any character in seq 34 [!seq] matches any char not in seq 35 36 An initial period in FILENAME is not special. 37 Both FILENAME and PATTERN are first case-normalized 38 if the operating system requires it. 39 If you don't want this, use fnmatchcase(FILENAME, PATTERN). 40 """ 41 42 name = name.lower() 43 pat = pat.lower() 44 return fnmatchcase(name, pat) 45 46 47def fnmatchcase(name, pat): 48 """Test whether FILENAME matches PATTERN, including case. 49 This is a version of fnmatch() which doesn't case-normalize 50 its arguments. 51 """ 52 53 try: 54 re_pat = _cache[pat] 55 except KeyError: 56 res = translate(pat) 57 if len(_cache) >= _MAXCACHE: 58 _cache.clear() 59 _cache[pat] = re_pat = re.compile(res) 60 return re_pat.match(name) is not None 61 62 63def translate(pat): 64 """Translate a shell PATTERN to a regular expression. 65 66 There is no way to quote meta-characters. 67 """ 68 i, n = 0, len(pat) 69 res = '^' 70 while i < n: 71 c = pat[i] 72 i = i + 1 73 if c == '*': 74 if i < n and pat[i] == '*': 75 # is some flavor of "**" 76 i = i + 1 77 # Treat **/ as ** so eat the "/" 78 if i < n and pat[i] == '/': 79 i = i + 1 80 if i >= n: 81 # is "**EOF" - to align with .gitignore just accept all 82 res = res + '.*' 83 else: 84 # is "**" 85 # Note that this allows for any # of /'s (even 0) because 86 # the .* will eat everything, even /'s 87 res = res + '(.*/)?' 88 else: 89 # is "*" so map it to anything but "/" 90 res = res + '[^/]*' 91 elif c == '?': 92 # "?" is any char except "/" 93 res = res + '[^/]' 94 elif c == '[': 95 j = i 96 if j < n and pat[j] == '!': 97 j = j + 1 98 if j < n and pat[j] == ']': 99 j = j + 1 100 while j < n and pat[j] != ']': 101 j = j + 1 102 if j >= n: 103 res = res + '\\[' 104 else: 105 stuff = pat[i:j].replace('\\', '\\\\') 106 i = j + 1 107 if stuff[0] == '!': 108 stuff = '^' + stuff[1:] 109 elif stuff[0] == '^': 110 stuff = '\\' + stuff 111 res = f'{res}[{stuff}]' 112 else: 113 res = res + re.escape(c) 114 115 return res + '$' 116