Skip to content

Commit ba42796

Browse files
authored
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``.
1 parent f9f1734 commit ba42796

File tree

4 files changed

+45
-4
lines changed

4 files changed

+45
-4
lines changed

Doc/library/string.rst

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -754,9 +754,21 @@ attributes:
754754
be set in the subclass's class namespace).
755755

756756
* *idpattern* -- This is the regular expression describing the pattern for
757-
non-braced placeholders (the braces will be added automatically as
758-
appropriate). The default value is the regular expression
759-
``[_a-z][_a-z0-9]*``.
757+
non-braced placeholders. The default value is the regular expression
758+
``[_a-z][_a-z0-9]*``. If this is given and *braceidpattern* is ``None``
759+
this pattern will also apply to braced placeholders.
760+
761+
.. versionchanged:: 3.7
762+
*braceidpattern* can be used to define separate patterns used inside and
763+
outside the braces.
764+
765+
* *braceidpattern* -- This is like *idpattern* but describes the pattern for
766+
braced placeholders. Defaults to ``None`` which means to fall back to
767+
*idpattern* (i.e. the same pattern is used both inside and outside braces).
768+
If given, this allows you to define different patterns for braced and
769+
unbraced placeholders.
770+
771+
.. versionadded:: 3.7
760772

761773
* *flags* -- The regular expression flags that will be applied when compiling
762774
the regular expression used for recognizing substitutions. The default value

Lib/string.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class _TemplateMetaclass(type):
5757
%(delim)s(?:
5858
(?P<escaped>%(delim)s) | # Escape sequence of two delimiters
5959
(?P<named>%(id)s) | # delimiter and a Python identifier
60-
{(?P<braced>%(id)s)} | # delimiter and a braced identifier
60+
{(?P<braced>%(bid)s)} | # delimiter and a braced identifier
6161
(?P<invalid>) # Other ill-formed delimiter exprs
6262
)
6363
"""
@@ -70,6 +70,7 @@ def __init__(cls, name, bases, dct):
7070
pattern = _TemplateMetaclass.pattern % {
7171
'delim' : _re.escape(cls.delimiter),
7272
'id' : cls.idpattern,
73+
'bid' : cls.braceidpattern or cls.idpattern,
7374
}
7475
cls.pattern = _re.compile(pattern, cls.flags | _re.VERBOSE)
7576

@@ -79,6 +80,7 @@ class Template(metaclass=_TemplateMetaclass):
7980

8081
delimiter = '$'
8182
idpattern = r'[_a-z][_a-z0-9]*'
83+
braceidpattern = None
8284
flags = _re.IGNORECASE
8385

8486
def __init__(self, template):

Lib/test/test_string.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,30 @@ class PathPattern(Template):
282282
s = PathPattern('$bag.foo.who likes to eat a bag of $bag.what')
283283
self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham')
284284

285+
def test_idpattern_override_inside_outside(self):
286+
# bpo-1198569: Allow the regexp inside and outside braces to be
287+
# different when deriving from Template.
288+
class MyPattern(Template):
289+
idpattern = r'[a-z]+'
290+
braceidpattern = r'[A-Z]+'
291+
flags = 0
292+
m = dict(foo='foo', BAR='BAR')
293+
s = MyPattern('$foo ${BAR}')
294+
self.assertEqual(s.substitute(m), 'foo BAR')
295+
296+
def test_idpattern_override_inside_outside_invalid_unbraced(self):
297+
# bpo-1198569: Allow the regexp inside and outside braces to be
298+
# different when deriving from Template.
299+
class MyPattern(Template):
300+
idpattern = r'[a-z]+'
301+
braceidpattern = r'[A-Z]+'
302+
flags = 0
303+
m = dict(foo='foo', BAR='BAR')
304+
s = MyPattern('$FOO')
305+
self.assertRaises(ValueError, s.substitute, m)
306+
s = MyPattern('${bar}')
307+
self.assertRaises(ValueError, s.substitute, m)
308+
285309
def test_pattern_override(self):
286310
class MyPattern(Template):
287311
pattern = r"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``string.Template`` subclasses can optionally define ``braceidpattern`` if
2+
they want to specify different placeholder patterns inside and outside the
3+
braces. If None (the default) it falls back to ``idpattern``.

0 commit comments

Comments
 (0)