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)' |
---|