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