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__ = ["filter", "fnmatch", "fnmatchcase", "translate"] 16 17_cache = {} 18_MAXCACHE = 100 19 20def _purge(): 21 """Clear the pattern cache""" 22 _cache.clear() 23 24def fnmatch(name, pat): 25 """Test whether FILENAME matches PATTERN. 26 27 Patterns are Unix shell style: 28 29 * matches everything 30 ? matches any single character 31 [seq] matches any character in seq 32 [!seq] matches any char not in seq 33 34 An initial period in FILENAME is not special. 35 Both FILENAME and PATTERN are first case-normalized 36 if the operating system requires it. 37 If you don't want this, use fnmatchcase(FILENAME, PATTERN). 38 """ 39 40 import os 41 name = os.path.normcase(name) 42 pat = os.path.normcase(pat) 43 return fnmatchcase(name, pat) 44 45def filter(names, pat): 46 """Return the subset of the list NAMES that match PAT""" 47 import os,posixpath 48 result=[] 49 pat=os.path.normcase(pat) 50 try: 51 re_pat = _cache[pat] 52 except KeyError: 53 res = translate(pat) 54 if len(_cache) >= _MAXCACHE: 55 _cache.clear() 56 _cache[pat] = re_pat = re.compile(res) 57 match = re_pat.match 58 if os.path is posixpath: 59 # normcase on posix is NOP. Optimize it away from the loop. 60 for name in names: 61 if match(name): 62 result.append(name) 63 else: 64 for name in names: 65 if match(os.path.normcase(name)): 66 result.append(name) 67 return result 68 69def fnmatchcase(name, pat): 70 """Test whether FILENAME matches PATTERN, including case. 71 72 This is a version of fnmatch() which doesn't case-normalize 73 its arguments. 74 """ 75 76 try: 77 re_pat = _cache[pat] 78 except KeyError: 79 res = translate(pat) 80 if len(_cache) >= _MAXCACHE: 81 _cache.clear() 82 _cache[pat] = re_pat = re.compile(res) 83 return re_pat.match(name) is not None 84 85def translate(pat): 86 """Translate a shell PATTERN to a regular expression. 87 88 There is no way to quote meta-characters. 89 """ 90 91 i, n = 0, len(pat) 92 res = '' 93 while i < n: 94 c = pat[i] 95 i = i+1 96 if c == '*': 97 res = res + '.*' 98 elif c == '?': 99 res = res + '.' 100 elif c == '[': 101 j = i 102 if j < n and pat[j] == '!': 103 j = j+1 104 if j < n and pat[j] == ']': 105 j = j+1 106 while j < n and pat[j] != ']': 107 j = j+1 108 if j >= n: 109 res = res + '\\[' 110 else: 111 stuff = pat[i:j].replace('\\','\\\\') 112 i = j+1 113 if stuff[0] == '!': 114 stuff = '^' + stuff[1:] 115 elif stuff[0] == '^': 116 stuff = '\\' + stuff 117 res = '%s[%s]' % (res, stuff) 118 else: 119 res = res + re.escape(c) 120 return res + '\Z(?ms)' 121