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