announce change of default behaviour and deprecation of `handle_io_errors`
[docutils/kirr.git] / docutils / docutils / _string_template_compat.py
blob38929c2962918bfcfde6fbaf42bec113919c53c9
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
4 # string_template_compat.py: string.Template for Python <= 2.4
5 # =====================================================
7 # This is just an excerpt of the standard string module to provide backwards
8 # compatibility.
10 import re as _re
12 class _multimap:
13 """Helper class for combining multiple mappings.
15 Used by .{safe_,}substitute() to combine the mapping and keyword
16 arguments.
17 """
18 def __init__(self, primary, secondary):
19 self._primary = primary
20 self._secondary = secondary
22 def __getitem__(self, key):
23 try:
24 return self._primary[key]
25 except KeyError:
26 return self._secondary[key]
29 class _TemplateMetaclass(type):
30 pattern = r"""
31 %(delim)s(?:
32 (?P<escaped>%(delim)s) | # Escape sequence of two delimiters
33 (?P<named>%(id)s) | # delimiter and a Python identifier
34 {(?P<braced>%(id)s)} | # delimiter and a braced identifier
35 (?P<invalid>) # Other ill-formed delimiter exprs
37 """
39 def __init__(cls, name, bases, dct):
40 super(_TemplateMetaclass, cls).__init__(name, bases, dct)
41 if 'pattern' in dct:
42 pattern = cls.pattern
43 else:
44 pattern = _TemplateMetaclass.pattern % {
45 'delim' : _re.escape(cls.delimiter),
46 'id' : cls.idpattern,
48 cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE)
51 class Template:
52 """A string class for supporting $-substitutions."""
53 __metaclass__ = _TemplateMetaclass
55 delimiter = '$'
56 idpattern = r'[_a-z][_a-z0-9]*'
58 def __init__(self, template):
59 self.template = template
61 # Search for $$, $identifier, ${identifier}, and any bare $'s
63 def _invalid(self, mo):
64 i = mo.start('invalid')
65 lines = self.template[:i].splitlines(True)
66 if not lines:
67 colno = 1
68 lineno = 1
69 else:
70 colno = i - len(''.join(lines[:-1]))
71 lineno = len(lines)
72 raise ValueError('Invalid placeholder in string: line %d, col %d' %
73 (lineno, colno))
75 def substitute(self, *args, **kws):
76 if len(args) > 1:
77 raise TypeError('Too many positional arguments')
78 if not args:
79 mapping = kws
80 elif kws:
81 mapping = _multimap(kws, args[0])
82 else:
83 mapping = args[0]
84 # Helper function for .sub()
85 def convert(mo):
86 # Check the most common path first.
87 named = mo.group('named') or mo.group('braced')
88 if named is not None:
89 val = mapping[named]
90 # We use this idiom instead of str() because the latter will
91 # fail if val is a Unicode containing non-ASCII characters.
92 return '%s' % (val,)
93 if mo.group('escaped') is not None:
94 return self.delimiter
95 if mo.group('invalid') is not None:
96 self._invalid(mo)
97 raise ValueError('Unrecognized named group in pattern',
98 self.pattern)
99 return self.pattern.sub(convert, self.template)
101 def safe_substitute(self, *args, **kws):
102 if len(args) > 1:
103 raise TypeError('Too many positional arguments')
104 if not args:
105 mapping = kws
106 elif kws:
107 mapping = _multimap(kws, args[0])
108 else:
109 mapping = args[0]
110 # Helper function for .sub()
111 def convert(mo):
112 named = mo.group('named')
113 if named is not None:
114 try:
115 # We use this idiom instead of str() because the latter
116 # will fail if val is a Unicode containing non-ASCII
117 return '%s' % (mapping[named],)
118 except KeyError:
119 return self.delimiter + named
120 braced = mo.group('braced')
121 if braced is not None:
122 try:
123 return '%s' % (mapping[braced],)
124 except KeyError:
125 return self.delimiter + '{' + braced + '}'
126 if mo.group('escaped') is not None:
127 return self.delimiter
128 if mo.group('invalid') is not None:
129 return self.delimiter
130 raise ValueError('Unrecognized named group in pattern',
131 self.pattern)
132 return self.pattern.sub(convert, self.template)