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