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    # Search for $$, $identifier, ${identifier}, and any bare $'s
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        # Helper function for .sub()
0080        def convert(mo):
0081            # Check the most common path first.
0082            named = mo.group('named') or mo.group('braced')
0083            if named is not None:
0084                val = mapping[named]
0085                # We use this idiom instead of str() because the latter will
0086                # fail if val is a Unicode containing non-ASCII characters.
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        # Helper function for .sub()
0106        def convert(mo):
0107            named = mo.group('named')
0108            if named is not None:
0109                try:
0110                    # We use this idiom instead of str() because the latter
0111                    # will fail if val is a Unicode containing non-ASCII
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)