Home | History | Annotate | Download | only in Lib
      1 """Filename matching with shell patterns.
      2 
      3 fnmatch(FILENAME, PATTERN) matches according to the local convention.
      4 fnmatchcase(FILENAME, PATTERN) always takes case in account.
      5 
      6 The functions operate by translating the pattern into a regular
      7 expression.  They cache the compiled regular expressions for speed.
      8 
      9 The function translate(PATTERN) returns a regular expression
     10 corresponding to PATTERN.  (It does not compile it.)
     11 """
     12 
     13 import re
     14 
     15 __all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
     16 
     17 _cache = {}
     18 _MAXCACHE = 100
     19 
     20 def _purge():
     21     """Clear the pattern cache"""
     22     _cache.clear()
     23 
     24 def 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 
     45 def 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 
     69 def 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 
     85 def 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