filename matching with shell patterns fnmatchfilename pattern matches according to the local convention fnmatchcasefilename pattern always takes case in account the functions operate by translating the pattern into a regular expression they cache the compiled regular expressions for speed the function translatepattern returns a regular expression corresponding to pattern it does not compile it 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 casenormalized if the operating system requires it if you don t want this use fnmatchcasefilename pattern construct a list from those elements of the iterable names that match pat result pat os path normcasepat match compilepatternpat if os path is posixpath normcase on posix is nop optimize it away from the loop for name in names if matchname result appendname else for name in names if matchos path normcasename result appendname return result def fnmatchcasename pat match compilepatternpat return matchname is not none def translatepat star object parts translatepat star return jointranslatedpartsparts star def translatepat star questionmark res add res append i n 0 lenpat while i n c pati i i1 if c compress consecutive into one if not res or res1 is not star addstar elif c addquestionmark elif c j i if j n and patj j j1 if j n and patj j j1 while j n and patj j j1 if j n add else stuff pati j if not in stuff stuff stuff replace r else chunks k i2 if pati else i1 while true k pat find k j if k 0 break chunks appendpati k i k1 k k3 chunk pati j if chunk chunks appendchunk else chunks1 remove empty ranges invalid in re for k in rangelenchunks1 0 1 if chunksk11 chunksk0 chunksk1 chunksk1 1 chunksk1 del chunksk escape backslashes and hyphens for set difference hyphens that create ranges shouldn t be escaped stuff joins replace r replace r for s in chunks escape set operations and stuff re subr r 1 stuff i j1 if not stuff empty range never match add elif stuff negated empty range match any character add else if stuff0 stuff stuff1 elif stuff0 in stuff stuff addf stuff else addre escapec assert i n return res def jointranslatedpartsinp star deal with stars res add res append i n 0 leninp fixed pieces at the start while i n and inpi is not star addinpi i 1 now deal with star fixed star fixed for an interior star fixed pairing we want to do a minimal match followed by fixed with no possibility of backtracking atomic groups allow us to spell that directly note people rely on the undocumented ability to join multiple translate results together via to build large regexps matching one of many shell patterns while i n assert inpi is star i 1 if i n add break assert inpi is not star fixed while i n and inpi is not star fixed appendinpi i 1 fixed joinfixed if i n add addfixed else addf fixed assert i n res joinres return fr s resz 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 construct a list from those elements of the iterable names that match pat normcase on posix is nop optimize it away from the loop test whether filename matches pattern including case this is a version of fnmatch which doesn t case normalize its arguments translate a shell pattern to a regular expression there is no way to quote meta characters compress consecutive into one remove empty ranges invalid in re escape backslashes and hyphens for set difference hyphens that create ranges shouldn t be escaped escape set operations and empty range never match negated empty range match any character deal with stars fixed pieces at the start now deal with star fixed star fixed for an interior star fixed pairing we want to do a minimal match followed by fixed with no possibility of backtracking atomic groups allow us to spell that directly note people rely on the undocumented ability to join multiple translate results together via to build large regexps matching one of many shell patterns
import os
import posixpath
import re
import functools

__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]

def fnmatch(name, pat):
    
    name = os.path.normcase(name)
    pat = os.path.normcase(pat)
    return fnmatchcase(name, pat)

@functools.lru_cache(maxsize=32768, typed=True)
def _compile_pattern(pat):
    if isinstance(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)
    return re.compile(res).match

def filter(names, pat):
    
    result = []
    pat = os.path.normcase(pat)
    match = _compile_pattern(pat)
    if os.path is posixpath:
        
        for name in names:
            if match(name):
                result.append(name)
    else:
        for name in names:
            if match(os.path.normcase(name)):
                result.append(name)
    return result

def fnmatchcase(name, pat):
    
    match = _compile_pattern(pat)
    return match(name) is not None


def translate(pat):
    

    STAR = object()
    parts = _translate(pat, STAR, '.')
    return _join_translated_parts(parts, STAR)


def _translate(pat, STAR, QUESTION_MARK):
    res = []
    add = res.append
    i, n = 0, len(pat)
    while i < n:
        c = pat[i]
        i = i+1
        if c == '*':
            
            if (not res) or res[-1] is not STAR:
                add(STAR)
        elif c == '?':
            add(QUESTION_MARK)
        elif c == '[':
            j = i
            if j < n and pat[j] == '!':
                j = j+1
            if j < n and pat[j] == ']':
                j = j+1
            while j < n and pat[j] != ']':
                j = j+1
            if j >= n:
                add('\\[')
            else:
                stuff = pat[i:j]
                if '-' not in stuff:
                    stuff = stuff.replace('\\', r'\\')
                else:
                    chunks = []
                    k = i+2 if pat[i] == '!' else i+1
                    while True:
                        k = pat.find('-', k, j)
                        if k < 0:
                            break
                        chunks.append(pat[i:k])
                        i = k+1
                        k = k+3
                    chunk = pat[i:j]
                    if chunk:
                        chunks.append(chunk)
                    else:
                        chunks[-1] += '-'
                    
                    for k in range(len(chunks)-1, 0, -1):
                        if chunks[k-1][-1] > chunks[k][0]:
                            chunks[k-1] = chunks[k-1][:-1] + chunks[k][1:]
                            del chunks[k]
                    
                    
                    stuff = '-'.join(s.replace('\\', r'\\').replace('-', r'\-')
                                     for s in chunks)
                
                stuff = re.sub(r'([&~|])', r'\\\1', stuff)
                i = j+1
                if not stuff:
                    
                    add('(?!)')
                elif stuff == '!':
                    
                    add('.')
                else:
                    if stuff[0] == '!':
                        stuff = '^' + stuff[1:]
                    elif stuff[0] in ('^', '['):
                        stuff = '\\' + stuff
                    add(f'[{stuff}]')
        else:
            add(re.escape(c))
    assert i == n
    return res


def _join_translated_parts(inp, STAR):
    
    res = []
    add = res.append
    i, n = 0, len(inp)
    
    while i < n and inp[i] is not STAR:
        add(inp[i])
        i += 1
    
    
    
    
    
    
    
    while i < n:
        assert inp[i] is STAR
        i += 1
        if i == n:
            add(".*")
            break
        assert inp[i] is not STAR
        fixed = []
        while i < n and inp[i] is not STAR:
            fixed.append(inp[i])
            i += 1
        fixed = "".join(fixed)
        if i == n:
            add(".*")
            add(fixed)
        else:
            add(f"(?>.*?{fixed})")
    assert i == n
    res = "".join(res)
    return fr'(?s:{res})\Z'