fnmatch.py 3.09 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
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.)
"""
12 13
import os
import posixpath
14
import re
Antoine Pitrou's avatar
Antoine Pitrou committed
15
import functools
16

Antoine Pitrou's avatar
Antoine Pitrou committed
17
__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
18

Guido van Rossum's avatar
Guido van Rossum committed
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
    """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)
    return fnmatchcase(name, pat)
Guido van Rossum's avatar
Guido van Rossum committed
37

38
@functools.lru_cache(maxsize=256, typed=True)
39 40
def _compile_pattern(pat):
    if isinstance(pat, bytes):
Antoine Pitrou's avatar
Antoine Pitrou committed
41 42 43 44 45 46
        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
47

48
def filter(names, pat):
49
    """Return the subset of the list NAMES that match PAT."""
50 51
    result = []
    pat = os.path.normcase(pat)
52
    match = _compile_pattern(pat)
53 54 55 56 57 58 59 60 61 62 63
    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
64
def fnmatchcase(name, pat):
Tim Peters's avatar
Tim Peters committed
65 66 67 68 69
    """Test whether FILENAME matches PATTERN, including case.

    This is a version of fnmatch() which doesn't case-normalize
    its arguments.
    """
70
    match = _compile_pattern(pat)
71
    return match(name) is not None
Guido van Rossum's avatar
Guido van Rossum committed
72

73

74
def translate(pat):
Tim Peters's avatar
Tim Peters committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    """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:
100
                stuff = pat[i:j].replace('\\','\\\\')
Tim Peters's avatar
Tim Peters committed
101 102
                i = j+1
                if stuff[0] == '!':
103 104 105 106
                    stuff = '^' + stuff[1:]
                elif stuff[0] == '^':
                    stuff = '\\' + stuff
                res = '%s[%s]' % (res, stuff)
Tim Peters's avatar
Tim Peters committed
107 108
        else:
            res = res + re.escape(c)
109
    return res + '\Z(?ms)'