"""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 regularexpression. They cache the compiled regular expressions for speed.The function translate(PATTERN) returns a regular expressioncorresponding to PATTERN. (It does not compile it.)"""importosimportposixpathimportreimportfunctools__all__=["filter","fnmatch","fnmatchcase","translate"]
[docs]deffnmatch(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=os.path.normcase(name)pat=os.path.normcase(pat)returnfnmatchcase(name,pat)
@functools.lru_cache(maxsize=256,typed=True)def_compile_pattern(pat):ifisinstance(pat,bytes):pat_str=str(pat,'ISO-8859-1')res_str=translate(pat_str)res=bytes(res_str,'ISO-8859-1')else:res=translate(pat)returnre.compile(res).matchdeffilter(names,pat):"""Construct a list from those elements of the iterable NAMES that match PAT."""result=[]pat=os.path.normcase(pat)match=_compile_pattern(pat)ifos.pathisposixpath:# normcase on posix is NOP. Optimize it away from the loop.fornameinnames:ifmatch(name):result.append(name)else:fornameinnames:ifmatch(os.path.normcase(name)):result.append(name)returnresultdeffnmatchcase(name,pat):"""Test whether FILENAME matches PATTERN, including case. This is a version of fnmatch() which doesn't case-normalize its arguments. """match=_compile_pattern(pat)returnmatch(name)isnotNonedeftranslate(pat):"""Translate a shell PATTERN to a regular expression. There is no way to quote meta-characters. """i,n=0,len(pat)res=''whilei<n:c=pat[i]i=i+1ifc=='*':res=res+'.*'elifc=='?':res=res+'.'elifc=='[':j=iifj<nandpat[j]=='!':j=j+1ifj<nandpat[j]==']':j=j+1whilej<nandpat[j]!=']':j=j+1ifj>=n:res=res+'\\['else:stuff=pat[i:j]if'--'notinstuff:stuff=stuff.replace('\\',r'\\')else:chunks=[]k=i+2ifpat[i]=='!'elsei+1whileTrue:k=pat.find('-',k,j)ifk<0:breakchunks.append(pat[i:k])i=k+1k=k+3chunks.append(pat[i:j])# Escape backslashes and hyphens for set difference (--).# Hyphens that create ranges shouldn't be escaped.stuff='-'.join(s.replace('\\',r'\\').replace('-',r'\-')forsinchunks)# Escape set operations (&&, ~~ and ||).stuff=re.sub(r'([&~|])',r'\\\1',stuff)i=j+1ifstuff[0]=='!':stuff='^'+stuff[1:]elifstuff[0]in('^','['):stuff='\\'+stuffres='%s[%s]'%(res,stuff)else:res=res+re.escape(c)returnr'(?s:%s)\Z'%res