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= |
|
|
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