Commit | Line | Data |
---|---|---|
920dae64 AT |
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 | ||
19 | def fnmatch(name, pat): | |
20 | """Test whether FILENAME matches PATTERN. | |
21 | ||
22 | Patterns are Unix shell style: | |
23 | ||
24 | * matches everything | |
25 | ? matches any single character | |
26 | [seq] matches any character in seq | |
27 | [!seq] matches any char not in seq | |
28 | ||
29 | An initial period in FILENAME is not special. | |
30 | Both FILENAME and PATTERN are first case-normalized | |
31 | if the operating system requires it. | |
32 | If you don't want this, use fnmatchcase(FILENAME, PATTERN). | |
33 | """ | |
34 | ||
35 | import os | |
36 | name = os.path.normcase(name) | |
37 | pat = os.path.normcase(pat) | |
38 | return fnmatchcase(name, pat) | |
39 | ||
40 | def filter(names, pat): | |
41 | """Return the subset of the list NAMES that match PAT""" | |
42 | import os,posixpath | |
43 | result=[] | |
44 | pat=os.path.normcase(pat) | |
45 | if not pat in _cache: | |
46 | res = translate(pat) | |
47 | _cache[pat] = re.compile(res) | |
48 | match=_cache[pat].match | |
49 | if os.path is posixpath: | |
50 | # normcase on posix is NOP. Optimize it away from the loop. | |
51 | for name in names: | |
52 | if match(name): | |
53 | result.append(name) | |
54 | else: | |
55 | for name in names: | |
56 | if match(os.path.normcase(name)): | |
57 | result.append(name) | |
58 | return result | |
59 | ||
60 | def fnmatchcase(name, pat): | |
61 | """Test whether FILENAME matches PATTERN, including case. | |
62 | ||
63 | This is a version of fnmatch() which doesn't case-normalize | |
64 | its arguments. | |
65 | """ | |
66 | ||
67 | if not pat in _cache: | |
68 | res = translate(pat) | |
69 | _cache[pat] = re.compile(res) | |
70 | return _cache[pat].match(name) is not None | |
71 | ||
72 | def translate(pat): | |
73 | """Translate a shell PATTERN to a regular expression. | |
74 | ||
75 | There is no way to quote meta-characters. | |
76 | """ | |
77 | ||
78 | i, n = 0, len(pat) | |
79 | res = '' | |
80 | while i < n: | |
81 | c = pat[i] | |
82 | i = i+1 | |
83 | if c == '*': | |
84 | res = res + '.*' | |
85 | elif c == '?': | |
86 | res = res + '.' | |
87 | elif c == '[': | |
88 | j = i | |
89 | if j < n and pat[j] == '!': | |
90 | j = j+1 | |
91 | if j < n and pat[j] == ']': | |
92 | j = j+1 | |
93 | while j < n and pat[j] != ']': | |
94 | j = j+1 | |
95 | if j >= n: | |
96 | res = res + '\\[' | |
97 | else: | |
98 | stuff = pat[i:j].replace('\\','\\\\') | |
99 | i = j+1 | |
100 | if stuff[0] == '!': | |
101 | stuff = '^' + stuff[1:] | |
102 | elif stuff[0] == '^': | |
103 | stuff = '\\' + stuff | |
104 | res = '%s[%s]' % (res, stuff) | |
105 | else: | |
106 | res = res + re.escape(c) | |
107 | return res + "$" |