##// END OF EJS Templates
Itpl now does not croak on unicode in 'live' template parts -> non-ascii prompts allowed. Closes #168
vivainio -
Show More
@@ -1,278 +1,284 b''
1 1 # -*- coding: utf-8 -*-
2 2 """String interpolation for Python (by Ka-Ping Yee, 14 Feb 2000).
3 3
4 4 This module lets you quickly and conveniently interpolate values into
5 5 strings (in the flavour of Perl or Tcl, but with less extraneous
6 6 punctuation). You get a bit more power than in the other languages,
7 7 because this module allows subscripting, slicing, function calls,
8 8 attribute lookup, or arbitrary expressions. Variables and expressions
9 9 are evaluated in the namespace of the caller.
10 10
11 11 The itpl() function returns the result of interpolating a string, and
12 12 printpl() prints out an interpolated string. Here are some examples:
13 13
14 14 from Itpl import printpl
15 15 printpl("Here is a $string.")
16 16 printpl("Here is a $module.member.")
17 17 printpl("Here is an $object.member.")
18 18 printpl("Here is a $functioncall(with, arguments).")
19 19 printpl("Here is an ${arbitrary + expression}.")
20 20 printpl("Here is an $array[3] member.")
21 21 printpl("Here is a $dictionary['member'].")
22 22
23 23 The filter() function filters a file object so that output through it
24 24 is interpolated. This lets you produce the illusion that Python knows
25 25 how to do interpolation:
26 26
27 27 import Itpl
28 28 sys.stdout = Itpl.filter()
29 29 f = "fancy"
30 30 print "Isn't this $f?"
31 31 print "Standard output has been replaced with a $sys.stdout object."
32 32 sys.stdout = Itpl.unfilter()
33 33 print "Okay, back $to $normal."
34 34
35 35 Under the hood, the Itpl class represents a string that knows how to
36 36 interpolate values. An instance of the class parses the string once
37 37 upon initialization; the evaluation and substitution can then be done
38 38 each time the instance is evaluated with str(instance). For example:
39 39
40 40 from Itpl import Itpl
41 41 s = Itpl("Here is $foo.")
42 42 foo = 5
43 43 print str(s)
44 44 foo = "bar"
45 45 print str(s)
46 46
47 $Id: Itpl.py 958 2005-12-27 23:17:51Z fperez $
47 $Id: Itpl.py 2918 2007-12-31 14:34:47Z vivainio $
48 48 """ # ' -> close an open quote for stupid emacs
49 49
50 50 #*****************************************************************************
51 51 #
52 52 # Copyright (c) 2001 Ka-Ping Yee <ping@lfw.org>
53 53 #
54 54 #
55 55 # Published under the terms of the MIT license, hereby reproduced:
56 56 #
57 57 # Permission is hereby granted, free of charge, to any person obtaining a copy
58 58 # of this software and associated documentation files (the "Software"), to
59 59 # deal in the Software without restriction, including without limitation the
60 60 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
61 61 # sell copies of the Software, and to permit persons to whom the Software is
62 62 # furnished to do so, subject to the following conditions:
63 63 #
64 64 # The above copyright notice and this permission notice shall be included in
65 65 # all copies or substantial portions of the Software.
66 66 #
67 67 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68 68 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69 69 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70 70 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71 71 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
72 72 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
73 73 # IN THE SOFTWARE.
74 74 #
75 75 #*****************************************************************************
76 76
77 77 __author__ = 'Ka-Ping Yee <ping@lfw.org>'
78 78 __license__ = 'MIT'
79 79
80 80 import string
81 81 import sys
82 82 from tokenize import tokenprog
83 83 from types import StringType
84 84
85 85 class ItplError(ValueError):
86 86 def __init__(self, text, pos):
87 87 self.text = text
88 88 self.pos = pos
89 89 def __str__(self):
90 90 return "unfinished expression in %s at char %d" % (
91 91 repr(self.text), self.pos)
92 92
93 93 def matchorfail(text, pos):
94 94 match = tokenprog.match(text, pos)
95 95 if match is None:
96 96 raise ItplError(text, pos)
97 97 return match, match.end()
98 98
99 99 class Itpl:
100 100 """Class representing a string with interpolation abilities.
101 101
102 102 Upon creation, an instance works out what parts of the format
103 103 string are literal and what parts need to be evaluated. The
104 104 evaluation and substitution happens in the namespace of the
105 105 caller when str(instance) is called."""
106 106
107 def __init__(self, format,codec='utf_8',encoding_errors='backslashreplace'):
107 def __init__(self, format,codec=sys.stdin.encoding,encoding_errors='backslashreplace'):
108 108 """The single mandatory argument to this constructor is a format
109 109 string.
110 110
111 111 The format string is parsed according to the following rules:
112 112
113 113 1. A dollar sign and a name, possibly followed by any of:
114 114 - an open-paren, and anything up to the matching paren
115 115 - an open-bracket, and anything up to the matching bracket
116 116 - a period and a name
117 117 any number of times, is evaluated as a Python expression.
118 118
119 119 2. A dollar sign immediately followed by an open-brace, and
120 120 anything up to the matching close-brace, is evaluated as
121 121 a Python expression.
122 122
123 123 3. Outside of the expressions described in the above two rules,
124 124 two dollar signs in a row give you one literal dollar sign.
125 125
126 126 Optional arguments:
127 127
128 128 - codec('utf_8'): a string containing the name of a valid Python
129 129 codec.
130 130
131 131 - encoding_errors('backslashreplace'): a string with a valid error handling
132 132 policy. See the codecs module documentation for details.
133 133
134 134 These are used to encode the format string if a call to str() fails on
135 135 the expanded result."""
136 136
137 137 if not isinstance(format,basestring):
138 138 raise TypeError, "needs string initializer"
139 139 self.format = format
140 140 self.codec = codec
141 141 self.encoding_errors = encoding_errors
142 142
143 143 namechars = "abcdefghijklmnopqrstuvwxyz" \
144 144 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
145 145 chunks = []
146 146 pos = 0
147 147
148 148 while 1:
149 149 dollar = string.find(format, "$", pos)
150 150 if dollar < 0: break
151 151 nextchar = format[dollar+1]
152 152
153 153 if nextchar == "{":
154 154 chunks.append((0, format[pos:dollar]))
155 155 pos, level = dollar+2, 1
156 156 while level:
157 157 match, pos = matchorfail(format, pos)
158 158 tstart, tend = match.regs[3]
159 159 token = format[tstart:tend]
160 160 if token == "{": level = level+1
161 161 elif token == "}": level = level-1
162 162 chunks.append((1, format[dollar+2:pos-1]))
163 163
164 164 elif nextchar in namechars:
165 165 chunks.append((0, format[pos:dollar]))
166 166 match, pos = matchorfail(format, dollar+1)
167 167 while pos < len(format):
168 168 if format[pos] == "." and \
169 169 pos+1 < len(format) and format[pos+1] in namechars:
170 170 match, pos = matchorfail(format, pos+1)
171 171 elif format[pos] in "([":
172 172 pos, level = pos+1, 1
173 173 while level:
174 174 match, pos = matchorfail(format, pos)
175 175 tstart, tend = match.regs[3]
176 176 token = format[tstart:tend]
177 177 if token[0] in "([": level = level+1
178 178 elif token[0] in ")]": level = level-1
179 179 else: break
180 180 chunks.append((1, format[dollar+1:pos]))
181 181
182 182 else:
183 183 chunks.append((0, format[pos:dollar+1]))
184 184 pos = dollar + 1 + (nextchar == "$")
185 185
186 186 if pos < len(format): chunks.append((0, format[pos:]))
187 187 self.chunks = chunks
188 188
189 189 def __repr__(self):
190 190 return "<Itpl %s >" % repr(self.format)
191 191
192 192 def _str(self,glob,loc):
193 193 """Evaluate to a string in the given globals/locals.
194 194
195 195 The final output is built by calling str(), but if this fails, the
196 196 result is encoded with the instance's codec and error handling policy,
197 197 via a call to out.encode(self.codec,self.encoding_errors)"""
198 198 result = []
199 199 app = result.append
200 200 for live, chunk in self.chunks:
201 if live: app(str(eval(chunk,glob,loc)))
201 if live:
202 val = eval(chunk,glob,loc)
203 try:
204 app(str(val))
205 except UnicodeEncodeError:
206 app(unicode(val))
207
202 208 else: app(chunk)
203 209 out = ''.join(result)
204 210 try:
205 211 return str(out)
206 212 except UnicodeError:
207 213 return out.encode(self.codec,self.encoding_errors)
208 214
209 215 def __str__(self):
210 216 """Evaluate and substitute the appropriate parts of the string."""
211 217
212 218 # We need to skip enough frames to get to the actual caller outside of
213 219 # Itpl.
214 220 frame = sys._getframe(1)
215 221 while frame.f_globals["__name__"] == __name__: frame = frame.f_back
216 222 loc, glob = frame.f_locals, frame.f_globals
217 223
218 224 return self._str(glob,loc)
219 225
220 226 class ItplNS(Itpl):
221 227 """Class representing a string with interpolation abilities.
222 228
223 229 This inherits from Itpl, but at creation time a namespace is provided
224 230 where the evaluation will occur. The interpolation becomes a bit more
225 231 efficient, as no traceback needs to be extracte. It also allows the
226 232 caller to supply a different namespace for the interpolation to occur than
227 233 its own."""
228 234
229 235 def __init__(self, format,globals,locals=None,
230 236 codec='utf_8',encoding_errors='backslashreplace'):
231 237 """ItplNS(format,globals[,locals]) -> interpolating string instance.
232 238
233 239 This constructor, besides a format string, takes a globals dictionary
234 240 and optionally a locals (which defaults to globals if not provided).
235 241
236 242 For further details, see the Itpl constructor."""
237 243
238 244 if locals is None:
239 245 locals = globals
240 246 self.globals = globals
241 247 self.locals = locals
242 248 Itpl.__init__(self,format,codec,encoding_errors)
243 249
244 250 def __str__(self):
245 251 """Evaluate and substitute the appropriate parts of the string."""
246 252 return self._str(self.globals,self.locals)
247 253
248 254 def __repr__(self):
249 255 return "<ItplNS %s >" % repr(self.format)
250 256
251 257 # utilities for fast printing
252 258 def itpl(text): return str(Itpl(text))
253 259 def printpl(text): print itpl(text)
254 260 # versions with namespace
255 261 def itplns(text,globals,locals=None): return str(ItplNS(text,globals,locals))
256 262 def printplns(text,globals,locals=None): print itplns(text,globals,locals)
257 263
258 264 class ItplFile:
259 265 """A file object that filters each write() through an interpolator."""
260 266 def __init__(self, file): self.file = file
261 267 def __repr__(self): return "<interpolated " + repr(self.file) + ">"
262 268 def __getattr__(self, attr): return getattr(self.file, attr)
263 269 def write(self, text): self.file.write(str(Itpl(text)))
264 270
265 271 def filter(file=sys.stdout):
266 272 """Return an ItplFile that filters writes to the given file object.
267 273
268 274 'file = filter(file)' replaces 'file' with a filtered object that
269 275 has a write() method. When called with no argument, this creates
270 276 a filter to sys.stdout."""
271 277 return ItplFile(file)
272 278
273 279 def unfilter(ifile=None):
274 280 """Return the original file that corresponds to the given ItplFile.
275 281
276 282 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
277 283 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
278 284 return ifile and ifile.file or sys.stdout.file
General Comments 0
You need to be logged in to leave comments. Login now