Ticket #84: python_pi.diff
| File python_pi.diff, 16.4 KB (added by cmlenz, 17 years ago) |
|---|
-
genshi/template/core.py
107 107 def __repr__(self): 108 108 return repr(list(self.frames)) 109 109 110 def __contains__(self, key): 111 """Return whether a variable exists in any of the scopes.""" 112 return self._find(key)[1] is not None 113 114 def __getitem__(self, key): 115 """Get a variables's value, starting at the current scope and going 116 upward. 117 118 Raises `KeyError` if the requested variable wasn't found in any scope. 119 """ 120 value, frame = self._find(key) 121 if frame is None: 122 raise KeyError(key) 123 return value 124 110 125 def __setitem__(self, key, value): 111 126 """Set a variable in the current scope.""" 112 127 self.frames[0][key] = value … … 129 144 if key in frame: 130 145 return frame[key] 131 146 return default 132 __getitem__ = get133 147 134 148 def push(self, data): 135 149 """Push a new scope on the stack.""" -
genshi/template/tests/eval.py
15 15 import sys 16 16 import unittest 17 17 18 from genshi.template.eval import Expression, Undefined18 from genshi.template.eval import Expression, Suite, Undefined 19 19 20 20 21 21 class ExpressionTestCase(unittest.TestCase): … … 382 382 self.assertRaises(TypeError, expr.evaluate, {'nothing': object()}) 383 383 384 384 385 class SuiteTestCase(unittest.TestCase): 386 387 def test_assign(self): 388 suite = Suite("foo = 42") 389 data = {} 390 suite.execute(data) 391 self.assertEqual(42, data['foo']) 392 393 def test_def(self): 394 suite = Suite("def donothing(): pass") 395 data = {} 396 suite.execute(data) 397 assert 'donothing' in data 398 self.assertEqual(None, data['donothing']()) 399 400 def test_delete(self): 401 suite = Suite("""foo = 42 402 del foo 403 """) 404 data = {} 405 suite.execute(data) 406 assert 'foo' not in data 407 408 def test_class(self): 409 suite = Suite("class plain(object): pass") 410 data = {} 411 suite.execute(data) 412 assert 'plain' in data 413 414 def test_import(self): 415 suite = Suite("from itertools import groupby") 416 data = {} 417 suite.execute(data) 418 assert 'groupby' in data 419 420 def test_while_break(self): 421 suite = Suite("""x = 0 422 while x < 5: 423 x += step 424 if x == 4: 425 break 426 """) 427 data = {'step': 2} 428 suite.execute(data) 429 self.assertEqual(4, data['x']) 430 431 385 432 def suite(): 386 433 suite = unittest.TestSuite() 387 434 suite.addTest(doctest.DocTestSuite(Expression.__module__)) 388 435 suite.addTest(unittest.makeSuite(ExpressionTestCase, 'test')) 436 suite.addTest(unittest.makeSuite(SuiteTestCase, 'test')) 389 437 return suite 390 438 391 439 if __name__ == '__main__': -
genshi/template/markup.py
16 16 from itertools import chain 17 17 18 18 from genshi.core import Attrs, Namespace, Stream, StreamEventKind 19 from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT19 from genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT 20 20 from genshi.input import XMLParser 21 21 from genshi.template.core import BadDirectiveError, Template, \ 22 _apply_directives, SUB 22 TemplateSyntaxError, _apply_directives, SUB 23 from genshi.template.eval import Suite 23 24 from genshi.template.loader import TemplateNotFound 24 25 from genshi.template.directives import * 25 26 … … 35 36 <li>1</li><li>2</li><li>3</li> 36 37 </ul> 37 38 """ 39 EXEC = StreamEventKind('EXEC') 38 40 INCLUDE = StreamEventKind('INCLUDE') 39 41 40 42 DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/') … … 59 61 Template.__init__(self, source, basedir=basedir, filename=filename, 60 62 loader=loader, encoding=encoding) 61 63 62 self.filters .append(self._match)64 self.filters += [self._exec, self._match] 63 65 if loader: 64 66 self.filters.append(self._include) 65 67 … … 169 171 stream[start_offset:] = [(SUB, (directives, substream), 170 172 pos)] 171 173 174 elif kind is PI and data[0] == 'python': 175 try: 176 suite = Suite(data[1], self.filepath, pos[1]) 177 except SyntaxError, err: 178 raise TemplateSyntaxError(err, self.filepath, 179 pos[1] + (err.lineno or 1) - 1, 180 pos[2] + (err.offset or 0)) 181 stream.append((EXEC, suite, pos)) 182 172 183 elif kind is TEXT: 173 184 for kind, data, pos in self._interpolate(data, self.basedir, 174 185 *pos): … … 190 201 data = data[0], list(self._prepare(data[1])) 191 202 yield kind, data, pos 192 203 204 def _exec(self, stream, ctxt): 205 """Internal stream filter that executes code in <?python ?> processing 206 instructions. 207 """ 208 for event in stream: 209 if event[0] is EXEC: 210 event[1].execute(ctxt.frames[0]) 211 else: 212 yield event 213 193 214 def _include(self, stream, ctxt): 194 215 """Internal stream filter that performs inclusion of external 195 216 template files. … … 291 312 yield event 292 313 293 314 315 EXEC = MarkupTemplate.EXEC 294 316 INCLUDE = MarkupTemplate.INCLUDE -
genshi/template/eval.py
15 15 16 16 import __builtin__ 17 17 from compiler import ast, parse 18 from compiler.pycodegen import ExpressionCodeGenerator 18 from compiler.pycodegen import ExpressionCodeGenerator, ModuleCodeGenerator 19 19 import new 20 20 try: 21 21 set … … 24 24 25 25 from genshi.util import flatten 26 26 27 __all__ = ['Expression', ' Undefined']27 __all__ = ['Expression', 'Suite', 'Undefined'] 28 28 29 29 30 class Expression(object): 30 class Code(object): 31 __slots__ = ['source', 'code'] 32 33 def __init__(self, source, filename=None, lineno=-1): 34 """Create the code object, either from a string, or from an AST node. 35 36 @param source: either a string containing the source code, or an AST 37 node 38 @param filename: the (preferably absolute) name of the file containing 39 the code 40 @param lineno: the number of the line on which the code was found 41 """ 42 if isinstance(source, basestring): 43 self.source = source 44 node = _parse(source, mode=self.mode) 45 else: 46 assert isinstance(source, ast.Node) 47 self.source = '?' 48 if self.mode == 'eval': 49 node = ast.Expression(source) 50 else: 51 node = ast.Module(None, source) 52 53 self.code = _compile(node, self.source, mode=self.mode, 54 filename=filename, lineno=lineno) 55 56 def __eq__(self, other): 57 return (type(other) == type(self)) and (self.code == other.code) 58 59 def __hash__(self): 60 return hash(self.code) 61 62 def __ne__(self, other): 63 return not self == other 64 65 def __repr__(self): 66 return '%s(%r)' % (self.__class__.__name__, self.source) 67 68 69 class Expression(Code): 31 70 """Evaluates Python expressions used in templates. 32 71 33 72 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) … … 68 107 >>> Expression('len(items)').evaluate(data) 69 108 3 70 109 """ 71 __slots__ = ['source', 'code'] 110 __slots__ = [] 111 mode = 'eval' 72 112 73 def __init__(self, source, filename=None, lineno=-1):74 """Create the expression, either from a string, or from an AST node.75 76 @param source: either a string containing the source code of the77 expression, or an AST node78 @param filename: the (preferably absolute) name of the file containing79 the expression80 @param lineno: the number of the line on which the expression was found81 """82 if isinstance(source, basestring):83 self.source = source84 self.code = _compile(_parse(source), self.source, filename=filename,85 lineno=lineno)86 else:87 assert isinstance(source, ast.Node)88 self.source = '?'89 self.code = _compile(ast.Expression(source), filename=filename,90 lineno=lineno)91 92 def __eq__(self, other):93 return (type(other) == Expression) and (self.code == other.code)94 95 def __hash__(self):96 return hash(self.code)97 98 def __ne__(self, other):99 return not self == other100 101 def __repr__(self):102 return 'Expression(%r)' % self.source103 104 113 def evaluate(self, data): 105 114 """Evaluate the expression against the given data dictionary. 106 115 … … 114 123 {'data': data}) 115 124 116 125 126 class Suite(Code): 127 """Executes Python statements used in templates. 128 129 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) 130 >>> Suite('foo = dict.some').execute(data) 131 >>> data['foo'] 132 'thing' 133 """ 134 __slots__ = [] 135 mode = 'exec' 136 137 def execute(self, data): 138 """Execute the suite in the given data dictionary. 139 140 @param data: a mapping containing the data to execute in 141 """ 142 exec self.code in {'data': data, 143 '_lookup_name': _lookup_name, 144 '_lookup_attr': _lookup_attr, 145 '_lookup_item': _lookup_item}, data 146 147 117 148 class Undefined(object): 118 149 """Represents a reference to an undefined variable. 119 150 … … 176 207 source = '\xef\xbb\xbf' + source.encode('utf-8') 177 208 return parse(source, mode) 178 209 179 def _compile(node, source=None, filename=None, lineno=-1):180 tree = ExpressionASTTransformer().visit(node)210 def _compile(node, source=None, mode='eval', filename=None, lineno=-1): 211 tree = TemplateASTTransformer().visit(node) 181 212 if isinstance(filename, unicode): 182 213 # unicode file names not allowed for code objects 183 214 filename = filename.encode('utf-8', 'replace') … … 187 218 if lineno <= 0: 188 219 lineno = 1 189 220 190 gen = ExpressionCodeGenerator(tree) 221 if mode == 'eval': 222 gen = ExpressionCodeGenerator(tree) 223 name = '<Expression %s>' % (repr(source or '?').replace("'", '"')) 224 else: 225 gen = ModuleCodeGenerator(tree) 226 name = '<Suite>' 191 227 gen.optimized = True 192 228 code = gen.getCode() 193 229 230 194 231 # We'd like to just set co_firstlineno, but it's readonly. So we need to 195 232 # clone the code object while adjusting the line number 196 233 return new.code(0, code.co_nlocals, code.co_stacksize, 197 234 code.co_flags | 0x0040, code.co_code, code.co_consts, 198 code.co_names, code.co_varnames, filename, 199 '<Expression %s>' % (repr(source or '?').replace("'", '"')), 200 lineno, code.co_lnotab, (), ()) 235 code.co_names, code.co_varnames, filename, name, lineno, 236 code.co_lnotab, (), ()) 201 237 202 238 BUILTINS = __builtin__.__dict__.copy() 203 239 BUILTINS['Undefined'] = Undefined … … 231 267 key = key[0] 232 268 try: 233 269 return obj[key] 234 except ( KeyError, IndexError, TypeError), e:270 except (AttributeError, KeyError, IndexError, TypeError), e: 235 271 if isinstance(key, basestring): 236 272 val = getattr(obj, key, _UNDEF) 237 273 if val is _UNDEF: … … 246 282 Every visitor method can be overridden to return an AST node that has been 247 283 altered or replaced in some way. 248 284 """ 249 _visitors = {} 285 def __init__(self): 286 self._visitors = {} 250 287 251 288 def visit(self, node): 289 if node is None: 290 return None 252 291 v = self._visitors.get(node.__class__) 253 292 if not v: 254 v = getattr(self, 'visit%s' % node.__class__.__name__) 293 v = getattr(self, 'visit%s' % node.__class__.__name__, 294 self._visitDefault) 255 295 self._visitors[node.__class__] = v 256 296 return v(node) 257 297 298 def _visitDefault(self, node): 299 return node 300 258 301 def visitExpression(self, node): 259 302 node.node = self.visit(node.node) 260 303 return node 261 304 262 # Functions & Accessors 305 def visitModule(self, node): 306 node.node = self.visit(node.node) 307 return node 263 308 309 def visitStmt(self, node): 310 node.nodes = [self.visit(x) for x in node.nodes] 311 return node 312 313 # Classes, Functions & Accessors 314 264 315 def visitCallFunc(self, node): 265 316 node.node = self.visit(node.node) 266 317 node.args = [self.visit(x) for x in node.args] … … 270 321 node.dstar_args = self.visit(node.dstar_args) 271 322 return node 272 323 273 def visitLambda(self, node): 324 def visitClass(self, node): 325 node.bases = [self.visit(x) for x in node.bases] 274 326 node.code = self.visit(node.code) 275 327 node.filename = '<string>' # workaround for bug in pycodegen 276 328 return node 277 329 330 def visitFunction(self, node): 331 node.defaults = [self.visit(x) for x in node.defaults] 332 node.code = self.visit(node.code) 333 node.filename = '<string>' # workaround for bug in pycodegen 334 return node 335 278 336 def visitGetattr(self, node): 279 337 node.expr = self.visit(node.expr) 280 338 return node 281 339 340 def visitLambda(self, node): 341 node.code = self.visit(node.code) 342 node.filename = '<string>' # workaround for bug in pycodegen 343 return node 344 282 345 def visitSubscript(self, node): 283 346 node.expr = self.visit(node.expr) 284 347 node.subs = [self.visit(x) for x in node.subs] 285 348 return node 286 349 350 # Statements 351 352 def visitAssign(self, node): 353 node.nodes = [self.visit(x) for x in node.nodes] 354 node.expr = self.visit(node.expr) 355 return node 356 357 def _visitPrint(self, node): 358 node.nodes = [self.visit(x) for x in node.nodes] 359 node.dest = self.visit(node.dest) 360 return node 361 visitPrint = visitPrintnl = _visitPrint 362 363 def visitWhile(self, node): 364 node.test = self.visit(node.test) 365 node.body = self.visit(node.body) 366 node.else_ = self.visit(node.else_) 367 return node 368 369 def visitYield(self, node): 370 node.value = self.visit(node.value) 371 return node 372 287 373 # Operators 288 374 289 375 def _visitBoolOp(self, node): … … 308 394 node.expr = self.visit(node.expr) 309 395 return node 310 396 visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp 311 visitBackquote = _visitUnaryOp397 visitBackquote = visitDiscard = _visitUnaryOp 312 398 313 399 # Identifiers, Literals and Comprehensions 314 400 315 def _visitDefault(self, node):316 return node317 visitAssName = visitConst = visitName = _visitDefault318 319 401 def visitDict(self, node): 320 402 node.items = [(self.visit(k), 321 403 self.visit(v)) for k, v in node.items] … … 381 463 return node 382 464 383 465 384 class ExpressionASTTransformer(ASTTransformer):466 class TemplateASTTransformer(ASTTransformer): 385 467 """Concrete AST transformer that implements the AST transformations needed 386 for template expressions.468 for code embedded in templates. 387 469 """ 388 470 389 471 def __init__(self): 472 ASTTransformer.__init__(self) 390 473 self.locals = [] 391 474 392 475 def visitConst(self, node): … … 398 481 return node 399 482 400 483 def visitAssName(self, node): 401 self.locals[-1].add(node.name) 484 if self.locals: 485 self.locals[-1].add(node.name) 402 486 return node 403 487 488 def visitClass(self, node): 489 self.locals.append(set()) 490 node = ASTTransformer.visitClass(self, node) 491 self.locals.pop() 492 return node 493 494 def visitFunction(self, node): 495 self.locals.append(set(node.argnames)) 496 node = ASTTransformer.visitFunction(self, node) 497 self.locals.pop() 498 return node 499 404 500 def visitGenExpr(self, node): 405 501 self.locals.append(set()) 406 502 node = ASTTransformer.visitGenExpr(self, node)
