From ba4279683f8eb8f59be10d12547ea89480614388 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 4 Sep 2017 16:32:10 -0400 Subject: [PATCH] bpo-1198569: Allow string.Template braced pattern to be different (#3288) * bpo-1198569: Allow the braced pattern to be different ``string.Template`` subclasses can optionally define ``braceidpattern`` if they want to specify different placeholder patterns inside and outside the braces. If None (the default) it falls back to ``idpattern``. --- Doc/library/string.rst | 18 +++++++++++--- Lib/string.py | 4 +++- Lib/test/test_string.py | 24 +++++++++++++++++++ ...2017-09-04-10-53-06.bpo-1198569.vhh2nY.rst | 3 +++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-09-04-10-53-06.bpo-1198569.vhh2nY.rst diff --git a/Doc/library/string.rst b/Doc/library/string.rst index 8176a81d4c..1a9b630975 100644 --- a/Doc/library/string.rst +++ b/Doc/library/string.rst @@ -754,9 +754,21 @@ attributes: be set in the subclass's class namespace). * *idpattern* -- This is the regular expression describing the pattern for - non-braced placeholders (the braces will be added automatically as - appropriate). The default value is the regular expression - ``[_a-z][_a-z0-9]*``. + non-braced placeholders. The default value is the regular expression + ``[_a-z][_a-z0-9]*``. If this is given and *braceidpattern* is ``None`` + this pattern will also apply to braced placeholders. + + .. versionchanged:: 3.7 + *braceidpattern* can be used to define separate patterns used inside and + outside the braces. + +* *braceidpattern* -- This is like *idpattern* but describes the pattern for + braced placeholders. Defaults to ``None`` which means to fall back to + *idpattern* (i.e. the same pattern is used both inside and outside braces). + If given, this allows you to define different patterns for braced and + unbraced placeholders. + + .. versionadded:: 3.7 * *flags* -- The regular expression flags that will be applied when compiling the regular expression used for recognizing substitutions. The default value diff --git a/Lib/string.py b/Lib/string.py index bc9508c1e6..b46e60c38f 100644 --- a/Lib/string.py +++ b/Lib/string.py @@ -57,7 +57,7 @@ class _TemplateMetaclass(type): %(delim)s(?: (?P%(delim)s) | # Escape sequence of two delimiters (?P%(id)s) | # delimiter and a Python identifier - {(?P%(id)s)} | # delimiter and a braced identifier + {(?P%(bid)s)} | # delimiter and a braced identifier (?P) # Other ill-formed delimiter exprs ) """ @@ -70,6 +70,7 @@ class _TemplateMetaclass(type): pattern = _TemplateMetaclass.pattern % { 'delim' : _re.escape(cls.delimiter), 'id' : cls.idpattern, + 'bid' : cls.braceidpattern or cls.idpattern, } cls.pattern = _re.compile(pattern, cls.flags | _re.VERBOSE) @@ -79,6 +80,7 @@ class Template(metaclass=_TemplateMetaclass): delimiter = '$' idpattern = r'[_a-z][_a-z0-9]*' + braceidpattern = None flags = _re.IGNORECASE def __init__(self, template): diff --git a/Lib/test/test_string.py b/Lib/test/test_string.py index a7b8aad8ab..6e241ac72a 100644 --- a/Lib/test/test_string.py +++ b/Lib/test/test_string.py @@ -282,6 +282,30 @@ class TestTemplate(unittest.TestCase): s = PathPattern('$bag.foo.who likes to eat a bag of $bag.what') self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham') + def test_idpattern_override_inside_outside(self): + # bpo-1198569: Allow the regexp inside and outside braces to be + # different when deriving from Template. + class MyPattern(Template): + idpattern = r'[a-z]+' + braceidpattern = r'[A-Z]+' + flags = 0 + m = dict(foo='foo', BAR='BAR') + s = MyPattern('$foo ${BAR}') + self.assertEqual(s.substitute(m), 'foo BAR') + + def test_idpattern_override_inside_outside_invalid_unbraced(self): + # bpo-1198569: Allow the regexp inside and outside braces to be + # different when deriving from Template. + class MyPattern(Template): + idpattern = r'[a-z]+' + braceidpattern = r'[A-Z]+' + flags = 0 + m = dict(foo='foo', BAR='BAR') + s = MyPattern('$FOO') + self.assertRaises(ValueError, s.substitute, m) + s = MyPattern('${bar}') + self.assertRaises(ValueError, s.substitute, m) + def test_pattern_override(self): class MyPattern(Template): pattern = r""" diff --git a/Misc/NEWS.d/next/Library/2017-09-04-10-53-06.bpo-1198569.vhh2nY.rst b/Misc/NEWS.d/next/Library/2017-09-04-10-53-06.bpo-1198569.vhh2nY.rst new file mode 100644 index 0000000000..86754191e7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-09-04-10-53-06.bpo-1198569.vhh2nY.rst @@ -0,0 +1,3 @@ +``string.Template`` subclasses can optionally define ``braceidpattern`` if +they want to specify different placeholder patterns inside and outside the +braces. If None (the default) it falls back to ``idpattern``. -- 2.40.0