diff -r 40a1652349e9 Doc/library/fnmatch.rst --- a/Doc/library/fnmatch.rst Sun Oct 14 22:16:27 2012 +0200 +++ b/Doc/library/fnmatch.rst Mon Oct 15 17:49:03 2012 +0300 @@ -70,12 +70,27 @@ ``[n for n in names if fnmatch(n, pattern)]``, but implemented more efficiently. +.. function:: escape(string) + + Escape all special characters (``'?'``, ``'*'`` and ``'['``). + This is useful if you want to match an arbitrary literal string + that may have special characters in it. + + Example: + + >>> import fnmatch + >>> + >>> pat = '*' + fnmatch.escape('-[Converted].png') + >>> fnmatch.fnmatch('file_locked-[Converted].png', pat) + True + + .. versionadded:: 3.4 + + .. function:: translate(pattern) Return the shell-style *pattern* converted to a regular expression. - Be aware there is no way to quote meta-characters. - Example: >>> import fnmatch, re diff -r 40a1652349e9 Lib/fnmatch.py --- a/Lib/fnmatch.py Sun Oct 14 22:16:27 2012 +0200 +++ b/Lib/fnmatch.py Mon Oct 15 17:49:03 2012 +0300 @@ -70,11 +70,20 @@ match = _compile_pattern(pat) return match(name) is not None +_magic_check = re.compile('([*?[])') +_magic_check_bytes = re.compile(b'([*?[])') + +def escape(string): + """Escape all special characters. + """ + # Escaping is done by wrapping any of "*?[" between square brackets. + if isinstance(string, bytes): + return _magic_check_bytes.sub(br'[\1]', string) + else: + return _magic_check.sub(r'[\1]', string) def translate(pat): """Translate a shell PATTERN to a regular expression. - - There is no way to quote meta-characters. """ i, n = 0, len(pat) diff -r 40a1652349e9 Lib/test/test_fnmatch.py --- a/Lib/test/test_fnmatch.py Sun Oct 14 22:16:27 2012 +0200 +++ b/Lib/test/test_fnmatch.py Mon Oct 15 17:49:03 2012 +0300 @@ -3,7 +3,7 @@ from test import support import unittest -from fnmatch import fnmatch, fnmatchcase, translate, filter +from fnmatch import fnmatch, fnmatchcase, translate, filter, escape class FnmatchTestCase(unittest.TestCase): @@ -44,6 +44,20 @@ check('\nfoo', 'foo*', False) check('\n', '*') + def test_escape(self): + # indirect tests + check = self.check_match + check('a[bc]', escape('a[bc]')) + check('a?c', escape('a?c')) + check('abc', escape('a?c'), False) + check('a*', escape('a*')) + check('abc', escape('a*'), False) + + # direct tests + self.assertEqual(escape('['), '[[]') + self.assertEqual(escape('?'), '[?]') + self.assertEqual(escape('*'), '[*]') + def test_mix_bytes_str(self): self.assertRaises(TypeError, fnmatch, 'test', b'*') self.assertRaises(TypeError, fnmatch, b'test', '*') @@ -59,6 +73,7 @@ self.check_match(b'test', b'te*') self.check_match(b'test\xff', b'te*\xff') self.check_match(b'foo\nbar', b'foo*') + self.check_match(b'a[b\xff]', escape(b'a[b\xff]')) class TranslateTestCase(unittest.TestCase):