fnmatch.py 2.95 KB
Newer Older
Guido van Rossum's avatar
Guido van Rossum committed
1
"""Filename matching with shell patterns.
2

Guido van Rossum's avatar
Guido van Rossum committed
3 4
fnmatch(FILENAME, PATTERN) matches according to the local convention.
fnmatchcase(FILENAME, PATTERN) always takes case in account.
5

Guido van Rossum's avatar
Guido van Rossum committed
6 7 8 9 10 11 12
The functions operate by translating the pattern into a regular
expression.  They cache the compiled regular expressions for speed.

The function translate(PATTERN) returns a regular expression
corresponding to PATTERN.  (It does not compile it.)
"""

13 14
import re

15
__all__ = ["filter", "fnmatch","fnmatchcase","translate"]
Skip Montanaro's avatar
Skip Montanaro committed
16

Guido van Rossum's avatar
Guido van Rossum committed
17
_cache = {}
Guido van Rossum's avatar
Guido van Rossum committed
18 19

def fnmatch(name, pat):
Tim Peters's avatar
Tim Peters committed
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
    """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).
    """

    import os
    name = os.path.normcase(name)
    pat = os.path.normcase(pat)
    return fnmatchcase(name, pat)
Guido van Rossum's avatar
Guido van Rossum committed
39

40 41 42 43 44
def filter(names, pat):
    """Return the subset of the list NAMES that match PAT"""
    import os,posixpath
    result=[]
    pat=os.path.normcase(pat)
45
    if not pat in _cache:
46 47 48 49 50 51 52 53 54 55 56 57 58 59
        res = translate(pat)
        _cache[pat] = re.compile(res)
    match=_cache[pat].match
    if os.path is posixpath:
        # normcase on posix is NOP. Optimize it away from the loop.
        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

Guido van Rossum's avatar
Guido van Rossum committed
60
def fnmatchcase(name, pat):
Tim Peters's avatar
Tim Peters committed
61 62 63 64 65 66
    """Test whether FILENAME matches PATTERN, including case.

    This is a version of fnmatch() which doesn't case-normalize
    its arguments.
    """

67
    if not pat in _cache:
Tim Peters's avatar
Tim Peters committed
68 69 70
        res = translate(pat)
        _cache[pat] = re.compile(res)
    return _cache[pat].match(name) is not None
Guido van Rossum's avatar
Guido van Rossum committed
71

72
def translate(pat):
Tim Peters's avatar
Tim Peters committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    """Translate a shell PATTERN to a regular expression.

    There is no way to quote meta-characters.
    """

    i, n = 0, len(pat)
    res = ''
    while i < n:
        c = pat[i]
        i = i+1
        if c == '*':
            res = res + '.*'
        elif c == '?':
            res = res + '.'
        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:
                res = res + '\\['
            else:
98
                stuff = pat[i:j].replace('\\','\\\\')
Tim Peters's avatar
Tim Peters committed
99 100
                i = j+1
                if stuff[0] == '!':
101 102 103 104
                    stuff = '^' + stuff[1:]
                elif stuff[0] == '^':
                    stuff = '\\' + stuff
                res = '%s[%s]' % (res, stuff)
Tim Peters's avatar
Tim Peters committed
105 106 107
        else:
            res = res + re.escape(c)
    return res + "$"