diff options
author | Barry Warsaw <barry@python.org> | 2004-09-10 03:08:08 (GMT) |
---|---|---|
committer | Barry Warsaw <barry@python.org> | 2004-09-10 03:08:08 (GMT) |
commit | 12827c1fa9e6c87248fa1e5e4341066e41ce4ffb (patch) | |
tree | 2c8f5a24b7b3ff3cd231ad0d5b09e4bde1594f72 /Lib/string.py | |
parent | 961c2882a920f137543585175db9957bf48223f3 (diff) | |
download | cpython-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.py | 85 |
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 #################################################################### |