comparison corebio/_future/_string.py @ 7:8d676bbd1f2d

Uploaded
author davidmurphy
date Mon, 16 Jan 2012 07:03:36 -0500
parents c55bdc2fb9fa
children
comparison
equal deleted inserted replaced
6:4a4aca3d57c9 7:8d676bbd1f2d
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