0001"""
0002Just string.Template, backported for use with Python 2.3
0003"""
0004
0005import re as _re
0006
0007class _multimap:
0008 """Helper class for combining multiple mappings.
0009
0010 Used by .{safe_,}substitute() to combine the mapping and keyword
0011 arguments.
0012 """
0013 def __init__(self, primary, secondary):
0014 self._primary = primary
0015 self._secondary = secondary
0016
0017 def __getitem__(self, key):
0018 try:
0019 return self._primary[key]
0020 except KeyError:
0021 return self._secondary[key]
0022
0023
0024class _TemplateMetaclass(type):
0025 pattern = r"""
0026 %(delim)s(?:
0027 (?P<escaped>%(delim)s) | # Escape sequence of two delimiters
0028 (?P<named>%(id)s) | # delimiter and a Python identifier
0029 {(?P<braced>%(id)s)} | # delimiter and a braced identifier
0030 (?P<invalid>) # Other ill-formed delimiter exprs
0031 )
0032 """
0033
0034 def __init__(cls, name, bases, dct):
0035 super(_TemplateMetaclass, cls).__init__(name, bases, dct)
0036 if 'pattern' in dct:
0037 pattern = cls.pattern
0038 else:
0039 pattern = _TemplateMetaclass.pattern % {
0040 'delim' : _re.escape(cls.delimiter),
0041 'id' : cls.idpattern,
0042 }
0043 cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
0044
0045
0046class Template:
0047 """A string class for supporting $-substitutions."""
0048 __metaclass__ = _TemplateMetaclass
0049
0050 delimiter = '$'
0051 idpattern = r'[_a-z][_a-z0-9]*'
0052
0053 def __init__(self, template):
0054 self.template = template
0055
0056
0057
0058 def _invalid(self, mo):
0059 i = mo.start('invalid')
0060 lines = self.template[:i].splitlines(True)
0061 if not lines:
0062 colno = 1
0063 lineno = 1
0064 else:
0065 colno = i - len(''.join(lines[:-1]))
0066 lineno = len(lines)
0067 raise ValueError('Invalid placeholder in string: line %d, col %d' %
0068 (lineno, colno))
0069
0070 def substitute(self, *args, **kws):
0071 if len(args) > 1:
0072 raise TypeError('Too many positional arguments')
0073 if not args:
0074 mapping = kws
0075 elif kws:
0076 mapping = _multimap(kws, args[0])
0077 else:
0078 mapping = args[0]
0079
0080 def convert(mo):
0081
0082 named = mo.group('named') or mo.group('braced')
0083 if named is not None:
0084 val = mapping[named]
0085
0086
0087 return '%s' % val
0088 if mo.group('escaped') is not None:
0089 return self.delimiter
0090 if mo.group('invalid') is not None:
0091 self._invalid(mo)
0092 raise ValueError('Unrecognized named group in pattern',
0093 self.pattern)
0094 return self.pattern.sub(convert, self.template)
0095
0096 def safe_substitute(self, *args, **kws):
0097 if len(args) > 1:
0098 raise TypeError('Too many positional arguments')
0099 if not args:
0100 mapping = kws
0101 elif kws:
0102 mapping = _multimap(kws, args[0])
0103 else:
0104 mapping = args[0]
0105
0106 def convert(mo):
0107 named = mo.group('named')
0108 if named is not None:
0109 try:
0110
0111
0112 return '%s' % mapping[named]
0113 except KeyError:
0114 return self.delimiter + named
0115 braced = mo.group('braced')
0116 if braced is not None:
0117 try:
0118 return '%s' % mapping[braced]
0119 except KeyError:
0120 return self.delimiter + '{' + braced + '}'
0121 if mo.group('escaped') is not None:
0122 return self.delimiter
0123 if mo.group('invalid') is not None:
0124 return self.delimiter
0125 raise ValueError('Unrecognized named group in pattern',
0126 self.pattern)
0127 return self.pattern.sub(convert, self.template)