1*cda5da8dSAndroid Build Coastguard Worker"""Filename matching with shell patterns. 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard Workerfnmatch(FILENAME, PATTERN) matches according to the local convention. 4*cda5da8dSAndroid Build Coastguard Workerfnmatchcase(FILENAME, PATTERN) always takes case in account. 5*cda5da8dSAndroid Build Coastguard Worker 6*cda5da8dSAndroid Build Coastguard WorkerThe functions operate by translating the pattern into a regular 7*cda5da8dSAndroid Build Coastguard Workerexpression. They cache the compiled regular expressions for speed. 8*cda5da8dSAndroid Build Coastguard Worker 9*cda5da8dSAndroid Build Coastguard WorkerThe function translate(PATTERN) returns a regular expression 10*cda5da8dSAndroid Build Coastguard Workercorresponding to PATTERN. (It does not compile it.) 11*cda5da8dSAndroid Build Coastguard Worker""" 12*cda5da8dSAndroid Build Coastguard Workerimport os 13*cda5da8dSAndroid Build Coastguard Workerimport posixpath 14*cda5da8dSAndroid Build Coastguard Workerimport re 15*cda5da8dSAndroid Build Coastguard Workerimport functools 16*cda5da8dSAndroid Build Coastguard Worker 17*cda5da8dSAndroid Build Coastguard Worker__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"] 18*cda5da8dSAndroid Build Coastguard Worker 19*cda5da8dSAndroid Build Coastguard Workerdef fnmatch(name, pat): 20*cda5da8dSAndroid Build Coastguard Worker """Test whether FILENAME matches PATTERN. 21*cda5da8dSAndroid Build Coastguard Worker 22*cda5da8dSAndroid Build Coastguard Worker Patterns are Unix shell style: 23*cda5da8dSAndroid Build Coastguard Worker 24*cda5da8dSAndroid Build Coastguard Worker * matches everything 25*cda5da8dSAndroid Build Coastguard Worker ? matches any single character 26*cda5da8dSAndroid Build Coastguard Worker [seq] matches any character in seq 27*cda5da8dSAndroid Build Coastguard Worker [!seq] matches any char not in seq 28*cda5da8dSAndroid Build Coastguard Worker 29*cda5da8dSAndroid Build Coastguard Worker An initial period in FILENAME is not special. 30*cda5da8dSAndroid Build Coastguard Worker Both FILENAME and PATTERN are first case-normalized 31*cda5da8dSAndroid Build Coastguard Worker if the operating system requires it. 32*cda5da8dSAndroid Build Coastguard Worker If you don't want this, use fnmatchcase(FILENAME, PATTERN). 33*cda5da8dSAndroid Build Coastguard Worker """ 34*cda5da8dSAndroid Build Coastguard Worker name = os.path.normcase(name) 35*cda5da8dSAndroid Build Coastguard Worker pat = os.path.normcase(pat) 36*cda5da8dSAndroid Build Coastguard Worker return fnmatchcase(name, pat) 37*cda5da8dSAndroid Build Coastguard Worker 38*cda5da8dSAndroid Build Coastguard Worker@functools.lru_cache(maxsize=32768, typed=True) 39*cda5da8dSAndroid Build Coastguard Workerdef _compile_pattern(pat): 40*cda5da8dSAndroid Build Coastguard Worker if isinstance(pat, bytes): 41*cda5da8dSAndroid Build Coastguard Worker pat_str = str(pat, 'ISO-8859-1') 42*cda5da8dSAndroid Build Coastguard Worker res_str = translate(pat_str) 43*cda5da8dSAndroid Build Coastguard Worker res = bytes(res_str, 'ISO-8859-1') 44*cda5da8dSAndroid Build Coastguard Worker else: 45*cda5da8dSAndroid Build Coastguard Worker res = translate(pat) 46*cda5da8dSAndroid Build Coastguard Worker return re.compile(res).match 47*cda5da8dSAndroid Build Coastguard Worker 48*cda5da8dSAndroid Build Coastguard Workerdef filter(names, pat): 49*cda5da8dSAndroid Build Coastguard Worker """Construct a list from those elements of the iterable NAMES that match PAT.""" 50*cda5da8dSAndroid Build Coastguard Worker result = [] 51*cda5da8dSAndroid Build Coastguard Worker pat = os.path.normcase(pat) 52*cda5da8dSAndroid Build Coastguard Worker match = _compile_pattern(pat) 53*cda5da8dSAndroid Build Coastguard Worker if os.path is posixpath: 54*cda5da8dSAndroid Build Coastguard Worker # normcase on posix is NOP. Optimize it away from the loop. 55*cda5da8dSAndroid Build Coastguard Worker for name in names: 56*cda5da8dSAndroid Build Coastguard Worker if match(name): 57*cda5da8dSAndroid Build Coastguard Worker result.append(name) 58*cda5da8dSAndroid Build Coastguard Worker else: 59*cda5da8dSAndroid Build Coastguard Worker for name in names: 60*cda5da8dSAndroid Build Coastguard Worker if match(os.path.normcase(name)): 61*cda5da8dSAndroid Build Coastguard Worker result.append(name) 62*cda5da8dSAndroid Build Coastguard Worker return result 63*cda5da8dSAndroid Build Coastguard Worker 64*cda5da8dSAndroid Build Coastguard Workerdef fnmatchcase(name, pat): 65*cda5da8dSAndroid Build Coastguard Worker """Test whether FILENAME matches PATTERN, including case. 66*cda5da8dSAndroid Build Coastguard Worker 67*cda5da8dSAndroid Build Coastguard Worker This is a version of fnmatch() which doesn't case-normalize 68*cda5da8dSAndroid Build Coastguard Worker its arguments. 69*cda5da8dSAndroid Build Coastguard Worker """ 70*cda5da8dSAndroid Build Coastguard Worker match = _compile_pattern(pat) 71*cda5da8dSAndroid Build Coastguard Worker return match(name) is not None 72*cda5da8dSAndroid Build Coastguard Worker 73*cda5da8dSAndroid Build Coastguard Worker 74*cda5da8dSAndroid Build Coastguard Workerdef translate(pat): 75*cda5da8dSAndroid Build Coastguard Worker """Translate a shell PATTERN to a regular expression. 76*cda5da8dSAndroid Build Coastguard Worker 77*cda5da8dSAndroid Build Coastguard Worker There is no way to quote meta-characters. 78*cda5da8dSAndroid Build Coastguard Worker """ 79*cda5da8dSAndroid Build Coastguard Worker 80*cda5da8dSAndroid Build Coastguard Worker STAR = object() 81*cda5da8dSAndroid Build Coastguard Worker res = [] 82*cda5da8dSAndroid Build Coastguard Worker add = res.append 83*cda5da8dSAndroid Build Coastguard Worker i, n = 0, len(pat) 84*cda5da8dSAndroid Build Coastguard Worker while i < n: 85*cda5da8dSAndroid Build Coastguard Worker c = pat[i] 86*cda5da8dSAndroid Build Coastguard Worker i = i+1 87*cda5da8dSAndroid Build Coastguard Worker if c == '*': 88*cda5da8dSAndroid Build Coastguard Worker # compress consecutive `*` into one 89*cda5da8dSAndroid Build Coastguard Worker if (not res) or res[-1] is not STAR: 90*cda5da8dSAndroid Build Coastguard Worker add(STAR) 91*cda5da8dSAndroid Build Coastguard Worker elif c == '?': 92*cda5da8dSAndroid Build Coastguard Worker add('.') 93*cda5da8dSAndroid Build Coastguard Worker elif c == '[': 94*cda5da8dSAndroid Build Coastguard Worker j = i 95*cda5da8dSAndroid Build Coastguard Worker if j < n and pat[j] == '!': 96*cda5da8dSAndroid Build Coastguard Worker j = j+1 97*cda5da8dSAndroid Build Coastguard Worker if j < n and pat[j] == ']': 98*cda5da8dSAndroid Build Coastguard Worker j = j+1 99*cda5da8dSAndroid Build Coastguard Worker while j < n and pat[j] != ']': 100*cda5da8dSAndroid Build Coastguard Worker j = j+1 101*cda5da8dSAndroid Build Coastguard Worker if j >= n: 102*cda5da8dSAndroid Build Coastguard Worker add('\\[') 103*cda5da8dSAndroid Build Coastguard Worker else: 104*cda5da8dSAndroid Build Coastguard Worker stuff = pat[i:j] 105*cda5da8dSAndroid Build Coastguard Worker if '-' not in stuff: 106*cda5da8dSAndroid Build Coastguard Worker stuff = stuff.replace('\\', r'\\') 107*cda5da8dSAndroid Build Coastguard Worker else: 108*cda5da8dSAndroid Build Coastguard Worker chunks = [] 109*cda5da8dSAndroid Build Coastguard Worker k = i+2 if pat[i] == '!' else i+1 110*cda5da8dSAndroid Build Coastguard Worker while True: 111*cda5da8dSAndroid Build Coastguard Worker k = pat.find('-', k, j) 112*cda5da8dSAndroid Build Coastguard Worker if k < 0: 113*cda5da8dSAndroid Build Coastguard Worker break 114*cda5da8dSAndroid Build Coastguard Worker chunks.append(pat[i:k]) 115*cda5da8dSAndroid Build Coastguard Worker i = k+1 116*cda5da8dSAndroid Build Coastguard Worker k = k+3 117*cda5da8dSAndroid Build Coastguard Worker chunk = pat[i:j] 118*cda5da8dSAndroid Build Coastguard Worker if chunk: 119*cda5da8dSAndroid Build Coastguard Worker chunks.append(chunk) 120*cda5da8dSAndroid Build Coastguard Worker else: 121*cda5da8dSAndroid Build Coastguard Worker chunks[-1] += '-' 122*cda5da8dSAndroid Build Coastguard Worker # Remove empty ranges -- invalid in RE. 123*cda5da8dSAndroid Build Coastguard Worker for k in range(len(chunks)-1, 0, -1): 124*cda5da8dSAndroid Build Coastguard Worker if chunks[k-1][-1] > chunks[k][0]: 125*cda5da8dSAndroid Build Coastguard Worker chunks[k-1] = chunks[k-1][:-1] + chunks[k][1:] 126*cda5da8dSAndroid Build Coastguard Worker del chunks[k] 127*cda5da8dSAndroid Build Coastguard Worker # Escape backslashes and hyphens for set difference (--). 128*cda5da8dSAndroid Build Coastguard Worker # Hyphens that create ranges shouldn't be escaped. 129*cda5da8dSAndroid Build Coastguard Worker stuff = '-'.join(s.replace('\\', r'\\').replace('-', r'\-') 130*cda5da8dSAndroid Build Coastguard Worker for s in chunks) 131*cda5da8dSAndroid Build Coastguard Worker # Escape set operations (&&, ~~ and ||). 132*cda5da8dSAndroid Build Coastguard Worker stuff = re.sub(r'([&~|])', r'\\\1', stuff) 133*cda5da8dSAndroid Build Coastguard Worker i = j+1 134*cda5da8dSAndroid Build Coastguard Worker if not stuff: 135*cda5da8dSAndroid Build Coastguard Worker # Empty range: never match. 136*cda5da8dSAndroid Build Coastguard Worker add('(?!)') 137*cda5da8dSAndroid Build Coastguard Worker elif stuff == '!': 138*cda5da8dSAndroid Build Coastguard Worker # Negated empty range: match any character. 139*cda5da8dSAndroid Build Coastguard Worker add('.') 140*cda5da8dSAndroid Build Coastguard Worker else: 141*cda5da8dSAndroid Build Coastguard Worker if stuff[0] == '!': 142*cda5da8dSAndroid Build Coastguard Worker stuff = '^' + stuff[1:] 143*cda5da8dSAndroid Build Coastguard Worker elif stuff[0] in ('^', '['): 144*cda5da8dSAndroid Build Coastguard Worker stuff = '\\' + stuff 145*cda5da8dSAndroid Build Coastguard Worker add(f'[{stuff}]') 146*cda5da8dSAndroid Build Coastguard Worker else: 147*cda5da8dSAndroid Build Coastguard Worker add(re.escape(c)) 148*cda5da8dSAndroid Build Coastguard Worker assert i == n 149*cda5da8dSAndroid Build Coastguard Worker 150*cda5da8dSAndroid Build Coastguard Worker # Deal with STARs. 151*cda5da8dSAndroid Build Coastguard Worker inp = res 152*cda5da8dSAndroid Build Coastguard Worker res = [] 153*cda5da8dSAndroid Build Coastguard Worker add = res.append 154*cda5da8dSAndroid Build Coastguard Worker i, n = 0, len(inp) 155*cda5da8dSAndroid Build Coastguard Worker # Fixed pieces at the start? 156*cda5da8dSAndroid Build Coastguard Worker while i < n and inp[i] is not STAR: 157*cda5da8dSAndroid Build Coastguard Worker add(inp[i]) 158*cda5da8dSAndroid Build Coastguard Worker i += 1 159*cda5da8dSAndroid Build Coastguard Worker # Now deal with STAR fixed STAR fixed ... 160*cda5da8dSAndroid Build Coastguard Worker # For an interior `STAR fixed` pairing, we want to do a minimal 161*cda5da8dSAndroid Build Coastguard Worker # .*? match followed by `fixed`, with no possibility of backtracking. 162*cda5da8dSAndroid Build Coastguard Worker # Atomic groups ("(?>...)") allow us to spell that directly. 163*cda5da8dSAndroid Build Coastguard Worker # Note: people rely on the undocumented ability to join multiple 164*cda5da8dSAndroid Build Coastguard Worker # translate() results together via "|" to build large regexps matching 165*cda5da8dSAndroid Build Coastguard Worker # "one of many" shell patterns. 166*cda5da8dSAndroid Build Coastguard Worker while i < n: 167*cda5da8dSAndroid Build Coastguard Worker assert inp[i] is STAR 168*cda5da8dSAndroid Build Coastguard Worker i += 1 169*cda5da8dSAndroid Build Coastguard Worker if i == n: 170*cda5da8dSAndroid Build Coastguard Worker add(".*") 171*cda5da8dSAndroid Build Coastguard Worker break 172*cda5da8dSAndroid Build Coastguard Worker assert inp[i] is not STAR 173*cda5da8dSAndroid Build Coastguard Worker fixed = [] 174*cda5da8dSAndroid Build Coastguard Worker while i < n and inp[i] is not STAR: 175*cda5da8dSAndroid Build Coastguard Worker fixed.append(inp[i]) 176*cda5da8dSAndroid Build Coastguard Worker i += 1 177*cda5da8dSAndroid Build Coastguard Worker fixed = "".join(fixed) 178*cda5da8dSAndroid Build Coastguard Worker if i == n: 179*cda5da8dSAndroid Build Coastguard Worker add(".*") 180*cda5da8dSAndroid Build Coastguard Worker add(fixed) 181*cda5da8dSAndroid Build Coastguard Worker else: 182*cda5da8dSAndroid Build Coastguard Worker add(f"(?>.*?{fixed})") 183*cda5da8dSAndroid Build Coastguard Worker assert i == n 184*cda5da8dSAndroid Build Coastguard Worker res = "".join(res) 185*cda5da8dSAndroid Build Coastguard Worker return fr'(?s:{res})\Z' 186