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``.
This commit is contained in:
Barry Warsaw 2017-09-04 16:32:10 -04:00 committed by GitHub
parent f9f17346d7
commit ba4279683f
4 changed files with 45 additions and 4 deletions

View file

@ -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

View file

@ -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):
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):
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):

View file

@ -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"""

View file

@ -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``.