Skip to content

Commit e92ff9f

Browse files
bbtfrliyangLoveEatCandy
authored
glob support * / ? / [] in {} (#416)
* glob support `*` / `?` / `[]` in `{}` * add tests for new fnmatch * make lint happy * make lint happy 2 * fix: fix static check (#417) --------- Co-authored-by: liyang <[email protected]> Co-authored-by: Hongyang Peng <[email protected]>
1 parent 0a64f85 commit e92ff9f

File tree

5 files changed

+112
-28
lines changed

5 files changed

+112
-28
lines changed

megfile/interfaces.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ def truncate(self, size: Optional[int] = None) -> int:
194194
def write(self, data: AnyStr) -> int:
195195
raise OSError("not writable")
196196

197-
def writelines(self, lines: Iterable[AnyStr]) -> None: # pyre-ignore[14]
197+
def writelines( # pyre-ignore[14] # pytype: disable=signature-mismatch
198+
self, lines: Iterable[AnyStr]
199+
) -> None:
198200
raise OSError("not writable")
199201

200202

@@ -210,7 +212,9 @@ def write(self, data: AnyStr) -> int:
210212
Return the number of bytes or string written.
211213
"""
212214

213-
def writelines(self, lines: Iterable[AnyStr]) -> None: # pyre-ignore[14]
215+
def writelines( # pyre-ignore[14] # pytype: disable=signature-mismatch
216+
self, lines: Iterable[AnyStr]
217+
) -> None:
214218
"""Write `lines` to the file.
215219
216220
Note that newlines are not added.

megfile/lib/fnmatch.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""Compared with the standard library, syntax '{seq1,seq2}' is supported"""
1414

1515
import functools
16+
import io
1617
import os
1718
import re
1819
from typing import Callable, List, Match, Optional
@@ -71,14 +72,9 @@ def _compat(res: str) -> str:
7172
return r"(?s:%s)\Z" % res
7273

7374

74-
def translate(pat: str) -> str:
75-
"""Translate a shell PATTERN to a regular expression.
76-
77-
There is no way to quote meta-characters.
78-
"""
79-
75+
def _translate(pat: str, match_curly: bool) -> str:
8076
i, n = 0, len(pat)
81-
res = ""
77+
buf = io.StringIO()
8278
while i < n:
8379
c = pat[i]
8480
i = i + 1
@@ -90,14 +86,14 @@ def translate(pat: str) -> str:
9086
if (j < n and pat[j] == "/") and (i <= 1 or pat[i - 2] == "/"):
9187
# hit /**/ instead of /seq**/
9288
j = j + 1
93-
res = res + r"(.*/)?"
89+
buf.write(r"(.*/)?")
9490
else:
95-
res = res + r".*"
91+
buf.write(r".*")
9692
else:
97-
res = res + r"[^/]*"
93+
buf.write(r"[^/]*")
9894
i = j
9995
elif c == "?":
100-
res = res + r"."
96+
buf.write(r".")
10197
elif c == "[":
10298
j = i
10399
if j < n and pat[j] == "!":
@@ -107,28 +103,37 @@ def translate(pat: str) -> str:
107103
while j < n and pat[j] != "]":
108104
j = j + 1
109105
if j >= n:
110-
res = res + r"\["
106+
buf.write(r"\[")
111107
else:
112108
stuff = pat[i:j].replace("\\", r"\\")
113109
i = j + 1
114110
if stuff[0] == "!":
115111
stuff = r"^" + stuff[1:]
116112
elif stuff[0] == "^":
117113
stuff = "\\" + stuff
118-
res = r"%s[%s]" % (res, stuff)
119-
elif c == "{":
114+
buf.write(r"[%s]" % stuff)
115+
elif match_curly and c == "{":
120116
j = i
121117
if j < n and pat[j] == "}":
122118
j = j + 1
123119
while j < n and pat[j] != "}":
124120
j = j + 1
125121
if j >= n:
126-
res = res + r"\{"
122+
buf.write(r"\{")
127123
else:
128124
stuff = pat[i:j].replace("\\", r"\\")
129-
stuff = r"|".join(map(re.escape, stuff.split(","))) # pyre-ignore[6]
130-
res = r"%s(%s)" % (res, stuff)
125+
stuff = r"|".join(_translate(part, False) for part in stuff.split(","))
126+
buf.write(r"(%s)" % stuff)
131127
i = j + 1
132128
else:
133-
res = res + re.escape(c)
134-
return _compat(res)
129+
buf.write(re.escape(c))
130+
return buf.getvalue()
131+
132+
133+
def translate(pat: str) -> str:
134+
"""Translate a shell PATTERN to a regular expression.
135+
136+
There is no way to quote meta-characters.
137+
"""
138+
139+
return _compat(_translate(pat, True))

megfile/lib/shadow_handler.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ class BaseShadowHandler(RawIOBase):
1111
"""ShadowHandler using RawIOBase's interface. (avoid type checking error)"""
1212

1313

14-
class ShadowHandler(Readable, Seekable, Writable, BaseShadowHandler):
14+
class ShadowHandler( # pytype: disable=signature-mismatch
15+
Readable, Seekable, Writable, BaseShadowHandler
16+
):
1517
"""Create a File-Like Object, maintaining file pointer,
1618
to avoid misunderstanding the position when read / write / seek.
1719

tests/lib/test_fnmatch.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ def test_translate():
4141

4242
# weirdos
4343
assert fnmatch.translate("(a|b)") == fnmatch._compat(r"\(a\|b\)")
44-
assert fnmatch.translate("{*,d}") == fnmatch._compat(r"(\*|d)")
45-
assert fnmatch.translate("{**,d}") == fnmatch._compat(r"(\*\*|d)")
46-
assert fnmatch.translate("{[abc],d}") == fnmatch._compat(r"(\[abc\]|d)")
44+
assert fnmatch.translate("{*,d}") == fnmatch._compat(r"([^/]*|d)")
45+
assert fnmatch.translate("{**,d}") == fnmatch._compat(r"(.*|d)")
46+
assert fnmatch.translate("{[abc],d}") == fnmatch._compat(r"([abc]|d)")
4747
if sys.version_info > (3, 7):
4848
assert fnmatch.translate("{{a,b},d}") == fnmatch._compat(r"(\{a|b),d\}")
4949
else:
@@ -174,9 +174,60 @@ def test_filter():
174174

175175
# weirdos
176176
assert fnmatch.filter(file_list, "(a|b)") == ["(a|b)"]
177-
assert fnmatch.filter(file_list, "{*,d}") == ["d", "*"]
178-
assert fnmatch.filter(file_list, "{**,d}") == ["d", "**"]
179-
assert fnmatch.filter(file_list, "{[abc],d}") == ["d", "[abc]"]
177+
assert fnmatch.filter(file_list, "{*,d}") == [
178+
"a",
179+
"b",
180+
"d",
181+
"*",
182+
"**",
183+
"[abc]",
184+
"(a|b)",
185+
"{a",
186+
"b}",
187+
"{a,d}",
188+
"[",
189+
"]",
190+
"[]",
191+
"[!]",
192+
"{",
193+
"}",
194+
"{}",
195+
"{,}",
196+
"^",
197+
"!",
198+
"?",
199+
",",
200+
]
201+
assert fnmatch.filter(file_list, "{**,d}") == [
202+
"a",
203+
"b",
204+
"c/d",
205+
"d",
206+
"*",
207+
"**",
208+
"[abc]",
209+
"(a|b)",
210+
"{a",
211+
"b}",
212+
"{a,d}",
213+
"[",
214+
"]",
215+
"[]",
216+
"[!]",
217+
"{",
218+
"}",
219+
"{}",
220+
"{,}",
221+
"^",
222+
"!",
223+
"?",
224+
",",
225+
]
226+
assert fnmatch.filter(file_list, "{[abc],d}") == [
227+
"a",
228+
"b",
229+
"d",
230+
]
180231
assert fnmatch.filter(file_list, "{{a,b},d}") == ["{a,d}"]
181232

182233

tests/lib/test_glob.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,35 @@ def _glob_with_dironly():
324324
)
325325

326326

327+
def _glob_with_curly():
328+
"""
329+
scenario: pathname with the curly braces('{}')
330+
expectation: returns only contains pathname of files
331+
"""
332+
assert_glob(
333+
"/bucketForGlobTest/{1,2}/", ["/bucketForGlobTest/1/", "/bucketForGlobTest/2/"]
334+
)
335+
336+
assert_glob("/bucketForGlobTest/{[2-4],[4-9]}/", ["/bucketForGlobTest/2/"])
337+
338+
assert_glob(
339+
"/bucketForGlobTest/1/**/*.{json,msg}",
340+
[
341+
"/bucketForGlobTest/1/a/b/1.json",
342+
"/bucketForGlobTest/1/a/b/c/1.json",
343+
"/bucketForGlobTest/1/a/b/c/A.msg",
344+
],
345+
)
346+
347+
327348
def test_glob(fs_setup):
328349
_glob_with_common_wildcard()
329350
_glob_with_recursive_pathname()
330351
_glob_with_same_file_and_folder()
331352
_glob_with_nested_pathname()
332353
_glob_with_not_exists_dir()
333354
_glob_with_dironly()
355+
_glob_with_curly()
334356

335357

336358
def test_escape():

0 commit comments

Comments
 (0)