Commit ba427968 authored by Barry Warsaw's avatar Barry Warsaw Committed by GitHub

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``.
parent f9f17346
...@@ -754,9 +754,21 @@ attributes: ...@@ -754,9 +754,21 @@ attributes:
be set in the subclass's class namespace). be set in the subclass's class namespace).
* *idpattern* -- This is the regular expression describing the pattern for * *idpattern* -- This is the regular expression describing the pattern for
non-braced placeholders (the braces will be added automatically as non-braced placeholders. The default value is the regular expression
appropriate). The default value is the regular expression ``[_a-z][_a-z0-9]*``. If this is given and *braceidpattern* is ``None``
``[_a-z][_a-z0-9]*``. 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 * *flags* -- The regular expression flags that will be applied when compiling
the regular expression used for recognizing substitutions. The default value the regular expression used for recognizing substitutions. The default value
......
...@@ -57,7 +57,7 @@ class _TemplateMetaclass(type): ...@@ -57,7 +57,7 @@ class _TemplateMetaclass(type):
%(delim)s(?: %(delim)s(?:
(?P<escaped>%(delim)s) | # Escape sequence of two delimiters (?P<escaped>%(delim)s) | # Escape sequence of two delimiters
(?P<named>%(id)s) | # delimiter and a Python identifier (?P<named>%(id)s) | # delimiter and a Python identifier
{(?P<braced>%(id)s)} | # delimiter and a braced identifier {(?P<braced>%(bid)s)} | # delimiter and a braced identifier
(?P<invalid>) # Other ill-formed delimiter exprs (?P<invalid>) # Other ill-formed delimiter exprs
) )
""" """
...@@ -70,6 +70,7 @@ class _TemplateMetaclass(type): ...@@ -70,6 +70,7 @@ class _TemplateMetaclass(type):
pattern = _TemplateMetaclass.pattern % { pattern = _TemplateMetaclass.pattern % {
'delim' : _re.escape(cls.delimiter), 'delim' : _re.escape(cls.delimiter),
'id' : cls.idpattern, 'id' : cls.idpattern,
'bid' : cls.braceidpattern or cls.idpattern,
} }
cls.pattern = _re.compile(pattern, cls.flags | _re.VERBOSE) cls.pattern = _re.compile(pattern, cls.flags | _re.VERBOSE)
...@@ -79,6 +80,7 @@ class Template(metaclass=_TemplateMetaclass): ...@@ -79,6 +80,7 @@ class Template(metaclass=_TemplateMetaclass):
delimiter = '$' delimiter = '$'
idpattern = r'[_a-z][_a-z0-9]*' idpattern = r'[_a-z][_a-z0-9]*'
braceidpattern = None
flags = _re.IGNORECASE flags = _re.IGNORECASE
def __init__(self, template): def __init__(self, template):
......
...@@ -282,6 +282,30 @@ class TestTemplate(unittest.TestCase): ...@@ -282,6 +282,30 @@ class TestTemplate(unittest.TestCase):
s = PathPattern('$bag.foo.who likes to eat a bag of $bag.what') 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') 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): def test_pattern_override(self):
class MyPattern(Template): class MyPattern(Template):
pattern = r""" pattern = r"""
......
``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``.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment