123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- """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 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.)
- """
-
- import re
-
- __all__ = ["fnmatch", "fnmatchcase", "translate"]
-
- _cache = {}
- _MAXCACHE = 100
-
-
- def _purge():
- """Clear the pattern cache"""
- _cache.clear()
-
-
- def fnmatch(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 = name.lower()
- pat = pat.lower()
- return fnmatchcase(name, pat)
-
-
- def fnmatchcase(name, pat):
- """Test whether FILENAME matches PATTERN, including case.
- This is a version of fnmatch() which doesn't case-normalize
- its arguments.
- """
-
- try:
- re_pat = _cache[pat]
- except KeyError:
- res = translate(pat)
- if len(_cache) >= _MAXCACHE:
- _cache.clear()
- _cache[pat] = re_pat = re.compile(res)
- return re_pat.match(name) is not None
-
-
- def translate(pat):
- """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 == '*':
- if i < n and pat[i] == '*':
- # is some flavor of "**"
- i = i + 1
- # Treat **/ as ** so eat the "/"
- if i < n and pat[i] == '/':
- i = i + 1
- if i >= n:
- # is "**EOF" - to align with .gitignore just accept all
- res = res + '.*'
- else:
- # is "**"
- # Note that this allows for any # of /'s (even 0) because
- # the .* will eat everything, even /'s
- res = res + '(.*/)?'
- else:
- # is "*" so map it to anything but "/"
- res = res + '[^/]*'
- elif c == '?':
- # "?" is any char except "/"
- 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:
- stuff = pat[i:j].replace('\\', '\\\\')
- i = j + 1
- if stuff[0] == '!':
- stuff = '^' + stuff[1:]
- elif stuff[0] == '^':
- stuff = '\\' + stuff
- res = f'{res}[{stuff}]'
- else:
- res = res + re.escape(c)
-
- return res + '$'
|