Skip to content

Commit 0b7ca17

Browse files
committed
Add fnmatch and shlex
1 parent 91d5fc3 commit 0b7ca17

3 files changed

Lines changed: 489 additions & 2 deletions

File tree

Lib/fnmatch.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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+
import os
13+
import posixpath
14+
import re
15+
import functools
16+
17+
__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
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+
name = os.path.normcase(name)
35+
pat = os.path.normcase(pat)
36+
return fnmatchcase(name, pat)
37+
38+
@functools.lru_cache(maxsize=256, typed=True)
39+
def _compile_pattern(pat):
40+
if isinstance(pat, bytes):
41+
pat_str = str(pat, 'ISO-8859-1')
42+
res_str = translate(pat_str)
43+
res = bytes(res_str, 'ISO-8859-1')
44+
else:
45+
res = translate(pat)
46+
return re.compile(res).match
47+
48+
def filter(names, pat):
49+
"""Return the subset of the list NAMES that match PAT."""
50+
result = []
51+
pat = os.path.normcase(pat)
52+
match = _compile_pattern(pat)
53+
if os.path is posixpath:
54+
# normcase on posix is NOP. Optimize it away from the loop.
55+
for name in names:
56+
if match(name):
57+
result.append(name)
58+
else:
59+
for name in names:
60+
if match(os.path.normcase(name)):
61+
result.append(name)
62+
return result
63+
64+
def fnmatchcase(name, pat):
65+
"""Test whether FILENAME matches PATTERN, including case.
66+
67+
This is a version of fnmatch() which doesn't case-normalize
68+
its arguments.
69+
"""
70+
match = _compile_pattern(pat)
71+
return match(name) is not None
72+
73+
74+
def translate(pat):
75+
"""Translate a shell PATTERN to a regular expression.
76+
77+
There is no way to quote meta-characters.
78+
"""
79+
80+
i, n = 0, len(pat)
81+
res = ''
82+
while i < n:
83+
c = pat[i]
84+
i = i+1
85+
if c == '*':
86+
res = res + '.*'
87+
elif c == '?':
88+
res = res + '.'
89+
elif c == '[':
90+
j = i
91+
if j < n and pat[j] == '!':
92+
j = j+1
93+
if j < n and pat[j] == ']':
94+
j = j+1
95+
while j < n and pat[j] != ']':
96+
j = j+1
97+
if j >= n:
98+
res = res + '\\['
99+
else:
100+
stuff = pat[i:j]
101+
if '--' not in stuff:
102+
stuff = stuff.replace('\\', r'\\')
103+
else:
104+
chunks = []
105+
k = i+2 if pat[i] == '!' else i+1
106+
while True:
107+
k = pat.find('-', k, j)
108+
if k < 0:
109+
break
110+
chunks.append(pat[i:k])
111+
i = k+1
112+
k = k+3
113+
chunks.append(pat[i:j])
114+
# Escape backslashes and hyphens for set difference (--).
115+
# Hyphens that create ranges shouldn't be escaped.
116+
stuff = '-'.join(s.replace('\\', r'\\').replace('-', r'\-')
117+
for s in chunks)
118+
# Escape set operations (&&, ~~ and ||).
119+
stuff = re.sub(r'([&~|])', r'\\\1', stuff)
120+
i = j+1
121+
if stuff[0] == '!':
122+
stuff = '^' + stuff[1:]
123+
elif stuff[0] in ('^', '['):
124+
stuff = '\\' + stuff
125+
res = '%s[%s]' % (res, stuff)
126+
else:
127+
res = res + re.escape(c)
128+
return r'(?s:%s)\Z' % res

0 commit comments

Comments
 (0)