]> granicus.if.org Git - python/commitdiff
bpo-1198569: Allow string.Template braced pattern to be different (#3288)
authorBarry Warsaw <barry@python.org>
Mon, 4 Sep 2017 20:32:10 +0000 (16:32 -0400)
committerGitHub <noreply@github.com>
Mon, 4 Sep 2017 20:32:10 +0000 (16:32 -0400)
* 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
Lib/string.py
Lib/test/test_string.py
Misc/NEWS.d/next/Library/2017-09-04-10-53-06.bpo-1198569.vhh2nY.rst [new file with mode: 0644]

index 8176a81d4cfe856d6de9b70592aaf30f66642b8c..1a9b630975218ab14d1d24df4e93c372ff4ef915 100644 (file)
@@ -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
index bc9508c1e6ee85f17b2b17defbd79751841394e5..b46e60c38f49280a993a1cc82d19e62901e1bb17 100644 (file)
@@ -57,7 +57,7 @@ class _TemplateMetaclass(type):
     %(delim)s(?:
       (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
       (?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
     )
     """
@@ -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):
index a7b8aad8abab9b1ee2d0d36001a0e61a68d8ae35..6e241ac72abf2b5472e1be4510bcebaae90c65fa 100644 (file)
@@ -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 (file)
index 0000000..8675419
--- /dev/null
@@ -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``.