Skip to content

Commit e5abc80

Browse files
committed
Refine fnmatch translate and glob translate testcases.
1 parent 3929b06 commit e5abc80

File tree

5 files changed

+29
-13
lines changed

5 files changed

+29
-13
lines changed

Lib/fnmatch.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ def _translate(pat, star, question_mark):
8888
add = res.append
8989
star_indices = []
9090
inside_range = False
91-
add_negative_lookahead = False
9291
question_mark_char = re.sub(r'\[|\]|\^', '', question_mark)
9392

9493
i, n = 0, len(pat)
@@ -138,7 +137,7 @@ def _translate(pat, star, question_mark):
138137
if chunks[k-1][-1] > chunks[k][0]:
139138
chunks[k-1] = chunks[k-1][:-1] + chunks[k][1:]
140139
del chunks[k]
141-
if len(chunks)>1:
140+
if len(chunks) > 1:
142141
if question_mark_char:
143142
inside_range = chunks[0][-1] <= question_mark_char <= chunks[-1][0]
144143
# Escape backslashes and hyphens for set difference (--).
@@ -155,20 +154,15 @@ def _translate(pat, star, question_mark):
155154
else:
156155
negative_lookahead=''
157156
if question_mark != '.' and inside_range:
158-
add_negative_lookahead = True
159-
negative_lookahead = negative_lookahead + question_mark_char
157+
add(f'(?![{question_mark_char}])')
160158
# Escape set operations (&&, ~~ and ||).
161159
stuff = _re_setops_sub(r'\\\1', stuff)
162160
if stuff[0] == '!':
163161
if question_mark_char not in stuff and question_mark != '.':
164-
add_negative_lookahead = True
165-
negative_lookahead = negative_lookahead + question_mark_char
162+
stuff = f'^{question_mark_char}' + '^' + stuff[1:]
166163
stuff = '^' + stuff[1:]
167-
elif stuff[0] in ('^', '[', question_mark_char):
164+
elif stuff[0] in ('^', '['):
168165
stuff = '\\' + stuff
169-
if add_negative_lookahead:
170-
add(f'(?![{negative_lookahead}])')
171-
add_negative_lookahead = False
172166
add(f'[{stuff}]')
173167
else:
174168
add(_re_escape(c))

Lib/glob.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ def escape(pathname):
262262
_special_parts = ('', '.', '..')
263263
_dir_open_flags = os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0)
264264
_no_recurse_symlinks = object()
265-
266265
def translate(pat, *, recursive=False, include_hidden=False, seps=None):
267266
"""Translate a pathname with shell wildcards to a regular expression.
268267

Lib/test/test_fnmatch.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ def test_translate(self):
236236
self.assertEqual(translate('A*********'), r'(?s:A.*)\Z')
237237
self.assertEqual(translate('*********A'), r'(?s:.*A)\Z')
238238
self.assertEqual(translate('A*********?[?]?'), r'(?s:A.*.[?].)\Z')
239+
self.assertEqual(translate('foo[%-0]bar'), r'(?s:foo[%-0]bar)\Z')
240+
self.assertEqual(translate('foo[%-0][%-0[%-0]bar'), r'(?s:foo[%-0][%-0[%-0]bar)\Z')
241+
self.assertEqual(translate('foo[/-/]bar'), r'(?s:foo[/-/]bar)\Z')
242+
self.assertEqual(translate('foo[%-0][1-9]bar'), r'(?s:foo[%-0][1-9]bar)\Z')
243+
self.assertEqual(translate('foo[%-/]bar'), r'(?s:foo[%-/]bar)\Z')
244+
self.assertEqual(translate('foo?'), r'(?s:foo.)\Z')
245+
self.assertEqual(translate('foo.'), r'(?s:foo\.)\Z')
239246
# fancy translation to prevent exponential-time match failure
240247
t = translate('**a*a****a')
241248
self.assertEqual(t, r'(?s:(?>.*?a)(?>.*?a).*a)\Z')

Lib/test/test_glob.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,8 @@ def test_translate_matching(self):
456456
self.assertIsNone(match(os.path.join('foo', '.bar')))
457457
self.assertIsNotNone(match(os.path.join('foo', 'bar.txt')))
458458
self.assertIsNone(match(os.path.join('foo', '.bar.txt')))
459+
match = re.compile(glob.translate('foo[%-0]bar', recursive=True)).match
460+
self.assertIsNone(match(os.path.join('foo', 'bar')))
459461

460462
def test_translate(self):
461463
def fn(pat):
@@ -513,14 +515,17 @@ def fn(pat):
513515
return glob.translate(pat, recursive=True, include_hidden=True, seps=['/', '\\'])
514516
self.assertEqual(fn('foo/bar\\baz'), r'(?s:foo[/\\]bar[/\\]baz)\Z')
515517
self.assertEqual(fn('**/*'), r'(?s:(?:.+[/\\])?[^/\\]+)\Z')
516-
self.assertEqual(fn('foo[!a]bar'), r'(?s:foo(?![/\\])[^a]bar)\Z')
518+
self.assertEqual(fn('foo[!a]bar'), r'(?s:foo[^/\\^a]bar)\Z')
517519
self.assertEqual(fn('foo[%-0]bar'), r'(?s:foo(?![/\\])[%-0]bar)\Z')
518520
self.assertEqual(fn('foo[%-0][1-9]bar'), r'(?s:foo(?![/\\])[%-0][1-9]bar)\Z')
519521
self.assertEqual(fn('foo[0-%]bar'), r'(?s:foo(?!)bar)\Z')
520522
self.assertEqual(fn('foo[^-'), r'(?s:foo\[\^\-)\Z')
521523
self.assertEqual(fn('foo[/-/]bar'), r'(?s:foo\[[/\\]\-[/\\]\]bar)\Z')
522524
self.assertEqual(fn('foo[%-/]bar'), r'(?s:foo\[%\-[/\\]\]bar)\Z')
523525
self.assertEqual(fn('foo[/]bar'), r'(?s:foo\[[/\\]\]bar)\Z')
526+
self.assertEqual(fn('foo[%-0][0-%[%-0]bar'), r'(?s:foo(?![/\\])[%-0](?![/\\])[\[%-0]bar)\Z')
527+
self.assertEqual(fn('foo?'), r'(?s:foo[^/\\])\Z')
528+
self.assertEqual(fn('foo.'), r'(?s:foo\.)\Z')
524529

525530
if __name__ == "__main__":
526531
unittest.main()
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
1-
Glob.translate negative-lookaheads path separators regex ranges that ecompass path seperator. For ranges which include path separator literals, the range is escaped.
1+
.. versionchanged:: next
2+
:func:`glob.translate` now correctly handles ranges implicitly containing path
3+
separators (for instance, ``[%-0]`` contains ``/``) by adding either a negative
4+
lookahead (``(?!/)``) or by not including the path separator (``^/``). In addition,
5+
ranges including path separator literals are now correctly escaped, as specified by
6+
POSIX specifications.
7+
8+
.. versionchanged:: next
9+
:func:`fnmatch.translate` does not treat path separator characters as having any
10+
special meaning at all, so it still matches ranges implicitly containing path
11+
separators (for instance, ``[%-0]`` contains ``/``) and ranges explicitly
12+
containing path separators (for instance, ``[/-/]`` contains ``/``).

0 commit comments

Comments
 (0)