summaryrefslogtreecommitdiffstats
path: root/Lib/string.py
diff options
context:
space:
mode:
authorBarry Warsaw <barry@python.org>2004-09-10 03:08:08 (GMT)
committerBarry Warsaw <barry@python.org>2004-09-10 03:08:08 (GMT)
commit12827c1fa9e6c87248fa1e5e4341066e41ce4ffb (patch)
tree2c8f5a24b7b3ff3cd231ad0d5b09e4bde1594f72 /Lib/string.py
parent961c2882a920f137543585175db9957bf48223f3 (diff)
downloadcpython-12827c1fa9e6c87248fa1e5e4341066e41ce4ffb.zip
cpython-12827c1fa9e6c87248fa1e5e4341066e41ce4ffb.tar.gz
cpython-12827c1fa9e6c87248fa1e5e4341066e41ce4ffb.tar.bz2
Many updates to PEP 292 templates. Summary:
- Template no longer inherits from unicode. - SafeTemplate is removed. Now Templates have both a substitute() and a safe_substitute() method, so we don't need separate classes. No more __mod__() operator. - Adopt Tim Peter's idea for giving Template a metaclass, which makes the delimiter, the identifier pattern, or the entire pattern easy to override and document, while retaining efficiency of class-time compilation of the regexp. - More informative ValueError messages which will help a user narrow down the bogus delimiter to the line and column in the original string (helpful for long triple quoted strings).
Diffstat (limited to 'Lib/string.py')
-rw-r--r--Lib/string.py85
1 files changed, 54 insertions, 31 deletions
diff --git a/Lib/string.py b/Lib/string.py
index 9965111..fd9cc99 100644
--- a/Lib/string.py
+++ b/Lib/string.py
@@ -82,60 +82,83 @@ def maketrans(fromstr, tostr):
####################################################################
import re as _re
-class Template(unicode):
+class _TemplateMetaclass(type):
+ pattern = r"""
+ (?P<escaped>%(delim)s{2}) | # Escape sequence of two delimiters
+ %(delim)s(?P<named>%(id)s) | # delimiter and a Python identifier
+ %(delim)s{(?P<braced>%(id)s)} | # delimiter and a braced identifier
+ (?P<bogus>%(delim)s) # Other ill-formed delimiter exprs
+ """
+
+ def __init__(cls, name, bases, dct):
+ super(_TemplateMetaclass, cls).__init__(name, bases, dct)
+ if 'pattern' in dct:
+ pattern = cls.pattern
+ else:
+ pattern = _TemplateMetaclass.pattern % {
+ 'delim' : cls.delimiter,
+ 'id' : cls.idpattern,
+ }
+ cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
+
+
+class Template:
"""A string class for supporting $-substitutions."""
- __slots__ = []
+ __metaclass__ = _TemplateMetaclass
+
+ delimiter = r'\$'
+ idpattern = r'[_a-z][_a-z0-9]*'
+
+ def __init__(self, template):
+ self.template = template
# Search for $$, $identifier, ${identifier}, and any bare $'s
- pattern = _re.compile(r"""
- (?P<escaped>\${2})| # Escape sequence of two $ signs
- \$(?P<named>[_a-z][_a-z0-9]*)| # $ and a Python identifier
- \${(?P<braced>[_a-z][_a-z0-9]*)}| # $ and a brace delimited identifier
- (?P<bogus>\$) # Other ill-formed $ expressions
- """, _re.IGNORECASE | _re.VERBOSE)
-
- def __mod__(self, mapping):
+
+ def _bogus(self, mo):
+ i = mo.start('bogus')
+ lines = self.template[:i].splitlines(True)
+ if not lines:
+ colno = 1
+ lineno = 1
+ else:
+ colno = i - len(''.join(lines[:-1]))
+ lineno = len(lines)
+ raise ValueError('Invalid placeholder in string: line %d, col %d' %
+ (lineno, colno))
+
+ def substitute(self, mapping):
def convert(mo):
if mo.group('escaped') is not None:
return '$'
if mo.group('bogus') is not None:
- raise ValueError('Invalid placeholder at index %d' %
- mo.start('bogus'))
+ self._bogus(mo)
val = mapping[mo.group('named') or mo.group('braced')]
- return unicode(val)
- return self.pattern.sub(convert, self)
-
-
-class SafeTemplate(Template):
- """A string class for supporting $-substitutions.
-
- This class is 'safe' in the sense that you will never get KeyErrors if
- there are placeholders missing from the interpolation dictionary. In that
- case, you will get the original placeholder in the value string.
- """
- __slots__ = []
+ # We use this idiom instead of str() because the latter will fail
+ # if val is a Unicode containing non-ASCII characters.
+ return '%s' % val
+ return self.pattern.sub(convert, self.template)
- def __mod__(self, mapping):
+ def safe_substitute(self, mapping):
def convert(mo):
if mo.group('escaped') is not None:
return '$'
if mo.group('bogus') is not None:
- raise ValueError('Invalid placeholder at index %d' %
- mo.start('bogus'))
+ self._bogus(mo)
named = mo.group('named')
if named is not None:
try:
- return unicode(mapping[named])
+ # We use this idiom instead of str() because the latter
+ # will fail if val is a Unicode containing non-ASCII
+ return '%s' % mapping[named]
except KeyError:
return '$' + named
braced = mo.group('braced')
try:
- return unicode(mapping[braced])
+ return '%s' % mapping[braced]
except KeyError:
return '${' + braced + '}'
- return self.pattern.sub(convert, self)
+ return self.pattern.sub(convert, self.template)
-del _re
####################################################################