##// END OF EJS Templates
itpl.py: do not crash if no encoding set (e.g. eric ide)
Ville M. Vainio -
Show More
@@ -1,284 +1,292 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 2918 2007-12-31 14:34:47Z vivainio $
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
97 return match, match.end()
98 return match, match.end()
98
99
100 try:
101 itpl_encoding = sys.stdin.encoding or 'ascii'
102 except AttributeError:
103 itpl_encoding = 'ascii'
104
105
106
99 class Itpl:
107 class Itpl:
100 """Class representing a string with interpolation abilities.
108 """Class representing a string with interpolation abilities.
101
109
102 Upon creation, an instance works out what parts of the format
110 Upon creation, an instance works out what parts of the format
103 string are literal and what parts need to be evaluated. The
111 string are literal and what parts need to be evaluated. The
104 evaluation and substitution happens in the namespace of the
112 evaluation and substitution happens in the namespace of the
105 caller when str(instance) is called."""
113 caller when str(instance) is called."""
106
114
107 def __init__(self, format,codec=sys.stdin.encoding,encoding_errors='backslashreplace'):
115 def __init__(self, format,codec=itpl_encoding,encoding_errors='backslashreplace'):
108 """The single mandatory argument to this constructor is a format
116 """The single mandatory argument to this constructor is a format
109 string.
117 string.
110
118
111 The format string is parsed according to the following rules:
119 The format string is parsed according to the following rules:
112
120
113 1. A dollar sign and a name, possibly followed by any of:
121 1. A dollar sign and a name, possibly followed by any of:
114 - an open-paren, and anything up to the matching paren
122 - an open-paren, and anything up to the matching paren
115 - an open-bracket, and anything up to the matching bracket
123 - an open-bracket, and anything up to the matching bracket
116 - a period and a name
124 - a period and a name
117 any number of times, is evaluated as a Python expression.
125 any number of times, is evaluated as a Python expression.
118
126
119 2. A dollar sign immediately followed by an open-brace, and
127 2. A dollar sign immediately followed by an open-brace, and
120 anything up to the matching close-brace, is evaluated as
128 anything up to the matching close-brace, is evaluated as
121 a Python expression.
129 a Python expression.
122
130
123 3. Outside of the expressions described in the above two rules,
131 3. Outside of the expressions described in the above two rules,
124 two dollar signs in a row give you one literal dollar sign.
132 two dollar signs in a row give you one literal dollar sign.
125
133
126 Optional arguments:
134 Optional arguments:
127
135
128 - codec('utf_8'): a string containing the name of a valid Python
136 - codec('utf_8'): a string containing the name of a valid Python
129 codec.
137 codec.
130
138
131 - encoding_errors('backslashreplace'): a string with a valid error handling
139 - encoding_errors('backslashreplace'): a string with a valid error handling
132 policy. See the codecs module documentation for details.
140 policy. See the codecs module documentation for details.
133
141
134 These are used to encode the format string if a call to str() fails on
142 These are used to encode the format string if a call to str() fails on
135 the expanded result."""
143 the expanded result."""
136
144
137 if not isinstance(format,basestring):
145 if not isinstance(format,basestring):
138 raise TypeError, "needs string initializer"
146 raise TypeError, "needs string initializer"
139 self.format = format
147 self.format = format
140 self.codec = codec
148 self.codec = codec
141 self.encoding_errors = encoding_errors
149 self.encoding_errors = encoding_errors
142
150
143 namechars = "abcdefghijklmnopqrstuvwxyz" \
151 namechars = "abcdefghijklmnopqrstuvwxyz" \
144 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
152 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
145 chunks = []
153 chunks = []
146 pos = 0
154 pos = 0
147
155
148 while 1:
156 while 1:
149 dollar = string.find(format, "$", pos)
157 dollar = string.find(format, "$", pos)
150 if dollar < 0: break
158 if dollar < 0: break
151 nextchar = format[dollar+1]
159 nextchar = format[dollar+1]
152
160
153 if nextchar == "{":
161 if nextchar == "{":
154 chunks.append((0, format[pos:dollar]))
162 chunks.append((0, format[pos:dollar]))
155 pos, level = dollar+2, 1
163 pos, level = dollar+2, 1
156 while level:
164 while level:
157 match, pos = matchorfail(format, pos)
165 match, pos = matchorfail(format, pos)
158 tstart, tend = match.regs[3]
166 tstart, tend = match.regs[3]
159 token = format[tstart:tend]
167 token = format[tstart:tend]
160 if token == "{": level = level+1
168 if token == "{": level = level+1
161 elif token == "}": level = level-1
169 elif token == "}": level = level-1
162 chunks.append((1, format[dollar+2:pos-1]))
170 chunks.append((1, format[dollar+2:pos-1]))
163
171
164 elif nextchar in namechars:
172 elif nextchar in namechars:
165 chunks.append((0, format[pos:dollar]))
173 chunks.append((0, format[pos:dollar]))
166 match, pos = matchorfail(format, dollar+1)
174 match, pos = matchorfail(format, dollar+1)
167 while pos < len(format):
175 while pos < len(format):
168 if format[pos] == "." and \
176 if format[pos] == "." and \
169 pos+1 < len(format) and format[pos+1] in namechars:
177 pos+1 < len(format) and format[pos+1] in namechars:
170 match, pos = matchorfail(format, pos+1)
178 match, pos = matchorfail(format, pos+1)
171 elif format[pos] in "([":
179 elif format[pos] in "([":
172 pos, level = pos+1, 1
180 pos, level = pos+1, 1
173 while level:
181 while level:
174 match, pos = matchorfail(format, pos)
182 match, pos = matchorfail(format, pos)
175 tstart, tend = match.regs[3]
183 tstart, tend = match.regs[3]
176 token = format[tstart:tend]
184 token = format[tstart:tend]
177 if token[0] in "([": level = level+1
185 if token[0] in "([": level = level+1
178 elif token[0] in ")]": level = level-1
186 elif token[0] in ")]": level = level-1
179 else: break
187 else: break
180 chunks.append((1, format[dollar+1:pos]))
188 chunks.append((1, format[dollar+1:pos]))
181
189
182 else:
190 else:
183 chunks.append((0, format[pos:dollar+1]))
191 chunks.append((0, format[pos:dollar+1]))
184 pos = dollar + 1 + (nextchar == "$")
192 pos = dollar + 1 + (nextchar == "$")
185
193
186 if pos < len(format): chunks.append((0, format[pos:]))
194 if pos < len(format): chunks.append((0, format[pos:]))
187 self.chunks = chunks
195 self.chunks = chunks
188
196
189 def __repr__(self):
197 def __repr__(self):
190 return "<Itpl %s >" % repr(self.format)
198 return "<Itpl %s >" % repr(self.format)
191
199
192 def _str(self,glob,loc):
200 def _str(self,glob,loc):
193 """Evaluate to a string in the given globals/locals.
201 """Evaluate to a string in the given globals/locals.
194
202
195 The final output is built by calling str(), but if this fails, the
203 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,
204 result is encoded with the instance's codec and error handling policy,
197 via a call to out.encode(self.codec,self.encoding_errors)"""
205 via a call to out.encode(self.codec,self.encoding_errors)"""
198 result = []
206 result = []
199 app = result.append
207 app = result.append
200 for live, chunk in self.chunks:
208 for live, chunk in self.chunks:
201 if live:
209 if live:
202 val = eval(chunk,glob,loc)
210 val = eval(chunk,glob,loc)
203 try:
211 try:
204 app(str(val))
212 app(str(val))
205 except UnicodeEncodeError:
213 except UnicodeEncodeError:
206 app(unicode(val))
214 app(unicode(val))
207
215
208 else: app(chunk)
216 else: app(chunk)
209 out = ''.join(result)
217 out = ''.join(result)
210 try:
218 try:
211 return str(out)
219 return str(out)
212 except UnicodeError:
220 except UnicodeError:
213 return out.encode(self.codec,self.encoding_errors)
221 return out.encode(self.codec,self.encoding_errors)
214
222
215 def __str__(self):
223 def __str__(self):
216 """Evaluate and substitute the appropriate parts of the string."""
224 """Evaluate and substitute the appropriate parts of the string."""
217
225
218 # We need to skip enough frames to get to the actual caller outside of
226 # We need to skip enough frames to get to the actual caller outside of
219 # Itpl.
227 # Itpl.
220 frame = sys._getframe(1)
228 frame = sys._getframe(1)
221 while frame.f_globals["__name__"] == __name__: frame = frame.f_back
229 while frame.f_globals["__name__"] == __name__: frame = frame.f_back
222 loc, glob = frame.f_locals, frame.f_globals
230 loc, glob = frame.f_locals, frame.f_globals
223
231
224 return self._str(glob,loc)
232 return self._str(glob,loc)
225
233
226 class ItplNS(Itpl):
234 class ItplNS(Itpl):
227 """Class representing a string with interpolation abilities.
235 """Class representing a string with interpolation abilities.
228
236
229 This inherits from Itpl, but at creation time a namespace is provided
237 This inherits from Itpl, but at creation time a namespace is provided
230 where the evaluation will occur. The interpolation becomes a bit more
238 where the evaluation will occur. The interpolation becomes a bit more
231 efficient, as no traceback needs to be extracte. It also allows the
239 efficient, as no traceback needs to be extracte. It also allows the
232 caller to supply a different namespace for the interpolation to occur than
240 caller to supply a different namespace for the interpolation to occur than
233 its own."""
241 its own."""
234
242
235 def __init__(self, format,globals,locals=None,
243 def __init__(self, format,globals,locals=None,
236 codec='utf_8',encoding_errors='backslashreplace'):
244 codec='utf_8',encoding_errors='backslashreplace'):
237 """ItplNS(format,globals[,locals]) -> interpolating string instance.
245 """ItplNS(format,globals[,locals]) -> interpolating string instance.
238
246
239 This constructor, besides a format string, takes a globals dictionary
247 This constructor, besides a format string, takes a globals dictionary
240 and optionally a locals (which defaults to globals if not provided).
248 and optionally a locals (which defaults to globals if not provided).
241
249
242 For further details, see the Itpl constructor."""
250 For further details, see the Itpl constructor."""
243
251
244 if locals is None:
252 if locals is None:
245 locals = globals
253 locals = globals
246 self.globals = globals
254 self.globals = globals
247 self.locals = locals
255 self.locals = locals
248 Itpl.__init__(self,format,codec,encoding_errors)
256 Itpl.__init__(self,format,codec,encoding_errors)
249
257
250 def __str__(self):
258 def __str__(self):
251 """Evaluate and substitute the appropriate parts of the string."""
259 """Evaluate and substitute the appropriate parts of the string."""
252 return self._str(self.globals,self.locals)
260 return self._str(self.globals,self.locals)
253
261
254 def __repr__(self):
262 def __repr__(self):
255 return "<ItplNS %s >" % repr(self.format)
263 return "<ItplNS %s >" % repr(self.format)
256
264
257 # utilities for fast printing
265 # utilities for fast printing
258 def itpl(text): return str(Itpl(text))
266 def itpl(text): return str(Itpl(text))
259 def printpl(text): print itpl(text)
267 def printpl(text): print itpl(text)
260 # versions with namespace
268 # versions with namespace
261 def itplns(text,globals,locals=None): return str(ItplNS(text,globals,locals))
269 def itplns(text,globals,locals=None): return str(ItplNS(text,globals,locals))
262 def printplns(text,globals,locals=None): print itplns(text,globals,locals)
270 def printplns(text,globals,locals=None): print itplns(text,globals,locals)
263
271
264 class ItplFile:
272 class ItplFile:
265 """A file object that filters each write() through an interpolator."""
273 """A file object that filters each write() through an interpolator."""
266 def __init__(self, file): self.file = file
274 def __init__(self, file): self.file = file
267 def __repr__(self): return "<interpolated " + repr(self.file) + ">"
275 def __repr__(self): return "<interpolated " + repr(self.file) + ">"
268 def __getattr__(self, attr): return getattr(self.file, attr)
276 def __getattr__(self, attr): return getattr(self.file, attr)
269 def write(self, text): self.file.write(str(Itpl(text)))
277 def write(self, text): self.file.write(str(Itpl(text)))
270
278
271 def filter(file=sys.stdout):
279 def filter(file=sys.stdout):
272 """Return an ItplFile that filters writes to the given file object.
280 """Return an ItplFile that filters writes to the given file object.
273
281
274 'file = filter(file)' replaces 'file' with a filtered object that
282 'file = filter(file)' replaces 'file' with a filtered object that
275 has a write() method. When called with no argument, this creates
283 has a write() method. When called with no argument, this creates
276 a filter to sys.stdout."""
284 a filter to sys.stdout."""
277 return ItplFile(file)
285 return ItplFile(file)
278
286
279 def unfilter(ifile=None):
287 def unfilter(ifile=None):
280 """Return the original file that corresponds to the given ItplFile.
288 """Return the original file that corresponds to the given ItplFile.
281
289
282 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
290 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
283 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
291 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
284 return ifile and ifile.file or sys.stdout.file
292 return ifile and ifile.file or sys.stdout.file
General Comments 0
You need to be logged in to leave comments. Login now