Ticket #84: python_pi.2.diff
| File python_pi.2.diff, 18.1 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_for(self): 421 suite = Suite("""x = [] 422 for i in range(3): 423 x.append(i**2) 424 """) 425 data = {} 426 suite.execute(data) 427 self.assertEqual([0, 1, 4], data['x']) 428 429 def test_if(self): 430 suite = Suite("""if foo == 42: 431 x = True 432 """) 433 data = {'foo': 42} 434 suite.execute(data) 435 self.assertEqual(True, data['x']) 436 437 def test_raise(self): 438 suite = Suite("""raise NotImplementedError""") 439 self.assertRaises(NotImplementedError, suite.execute, {}) 440 441 def test_try_except(self): 442 suite = Suite("""try: 443 import somemod 444 except ImportError: 445 somemod = None 446 else: 447 somemod.dosth()""") 448 data = {} 449 suite.execute(data) 450 self.assertEqual(None, data['somemod']) 451 452 def test_finally(self): 453 suite = Suite("""try: 454 x = 2 455 finally: 456 x = None 457 """) 458 data = {} 459 suite.execute(data) 460 self.assertEqual(None, data['x']) 461 462 def test_while_break(self): 463 suite = Suite("""x = 0 464 while x < 5: 465 x += step 466 if x == 4: 467 break 468 """) 469 data = {'step': 2} 470 suite.execute(data) 471 self.assertEqual(4, data['x']) 472 473 385 474 def suite(): 386 475 suite = unittest.TestSuite() 387 476 suite.addTest(doctest.DocTestSuite(Expression.__module__)) 388 477 suite.addTest(unittest.makeSuite(ExpressionTestCase, 'test')) 478 suite.addTest(unittest.makeSuite(SuiteTestCase, 'test')) 389 479 return suite 390 480 391 481 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 """Abstract base class for the `Expression` and `Suite` classes.""" 32 __slots__ = ['source', 'code'] 33 34 def __init__(self, source, filename=None, lineno=-1): 35 """Create the code object, either from a string, or from an AST node. 36 37 @param source: either a string containing the source code, or an AST 38 node 39 @param filename: the (preferably absolute) name of the file containing 40 the code 41 @param lineno: the number of the line on which the code was found 42 """ 43 if isinstance(source, basestring): 44 self.source = source 45 node = _parse(source, mode=self.mode) 46 else: 47 assert isinstance(source, ast.Node) 48 self.source = '?' 49 if self.mode == 'eval': 50 node = ast.Expression(source) 51 else: 52 node = ast.Module(None, source) 53 54 self.code = _compile(node, self.source, mode=self.mode, 55 filename=filename, lineno=lineno) 56 57 def __eq__(self, other): 58 return (type(other) == type(self)) and (self.code == other.code) 59 60 def __hash__(self): 61 return hash(self.code) 62 63 def __ne__(self, other): 64 return not self == other 65 66 def __repr__(self): 67 return '%s(%r)' % (self.__class__.__name__, self.source) 68 69 70 class Expression(Code): 31 71 """Evaluates Python expressions used in templates. 32 72 33 73 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) … … 68 108 >>> Expression('len(items)').evaluate(data) 69 109 3 70 110 """ 71 __slots__ = ['source', 'code'] 111 __slots__ = [] 112 mode = 'eval' 72 113 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 114 def evaluate(self, data): 105 115 """Evaluate the expression against the given data dictionary. 106 116 … … 114 124 {'data': data}) 115 125 116 126 127 class Suite(Code): 128 """Executes Python statements used in templates. 129 130 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) 131 >>> Suite('foo = dict.some').execute(data) 132 >>> data['foo'] 133 'thing' 134 """ 135 __slots__ = [] 136 mode = 'exec' 137 138 def execute(self, data): 139 """Execute the suite in the given data dictionary. 140 141 @param data: a mapping containing the data to execute in 142 """ 143 exec self.code in {'data': data, 144 '_lookup_name': _lookup_name, 145 '_lookup_attr': _lookup_attr, 146 '_lookup_item': _lookup_item}, data 147 148 117 149 class Undefined(object): 118 150 """Represents a reference to an undefined variable. 119 151 … … 176 208 source = '\xef\xbb\xbf' + source.encode('utf-8') 177 209 return parse(source, mode) 178 210 179 def _compile(node, source=None, filename=None, lineno=-1):180 tree = ExpressionASTTransformer().visit(node)211 def _compile(node, source=None, mode='eval', filename=None, lineno=-1): 212 tree = TemplateASTTransformer().visit(node) 181 213 if isinstance(filename, unicode): 182 214 # unicode file names not allowed for code objects 183 215 filename = filename.encode('utf-8', 'replace') … … 187 219 if lineno <= 0: 188 220 lineno = 1 189 221 190 gen = ExpressionCodeGenerator(tree) 222 if mode == 'eval': 223 gen = ExpressionCodeGenerator(tree) 224 name = '<Expression %s>' % (repr(source or '?').replace("'", '"')) 225 else: 226 gen = ModuleCodeGenerator(tree) 227 name = '<Suite>' 191 228 gen.optimized = True 192 229 code = gen.getCode() 193 230 … … 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: … … 249 285 _visitors = {} 250 286 251 287 def visit(self, node): 288 if node is None: 289 return None 252 290 v = self._visitors.get(node.__class__) 253 291 if not v: 254 v = getattr(self, 'visit%s' % node.__class__.__name__) 292 v = getattr(self.__class__, 'visit%s' % node.__class__.__name__, 293 self.__class__._visitDefault) 255 294 self._visitors[node.__class__] = v 256 return v( node)295 return v(self, node) 257 296 297 def _visitDefault(self, node): 298 return node 299 258 300 def visitExpression(self, node): 259 301 node.node = self.visit(node.node) 260 302 return node 261 303 262 # Functions & Accessors 304 def visitModule(self, node): 305 node.node = self.visit(node.node) 306 return node 263 307 308 def visitStmt(self, node): 309 node.nodes = [self.visit(x) for x in node.nodes] 310 return node 311 312 # Classes, Functions & Accessors 313 264 314 def visitCallFunc(self, node): 265 315 node.node = self.visit(node.node) 266 316 node.args = [self.visit(x) for x in node.args] … … 270 320 node.dstar_args = self.visit(node.dstar_args) 271 321 return node 272 322 273 def visitLambda(self, node): 323 def visitClass(self, node): 324 node.bases = [self.visit(x) for x in node.bases] 274 325 node.code = self.visit(node.code) 275 326 node.filename = '<string>' # workaround for bug in pycodegen 276 327 return node 277 328 329 def visitFunction(self, node): 330 node.defaults = [self.visit(x) for x in node.defaults] 331 node.code = self.visit(node.code) 332 node.filename = '<string>' # workaround for bug in pycodegen 333 return node 334 278 335 def visitGetattr(self, node): 279 336 node.expr = self.visit(node.expr) 280 337 return node 281 338 339 def visitLambda(self, node): 340 node.code = self.visit(node.code) 341 node.filename = '<string>' # workaround for bug in pycodegen 342 return node 343 282 344 def visitSubscript(self, node): 283 345 node.expr = self.visit(node.expr) 284 346 node.subs = [self.visit(x) for x in node.subs] 285 347 return node 286 348 349 # Statements 350 351 def visitAssign(self, node): 352 node.nodes = [self.visit(x) for x in node.nodes] 353 node.expr = self.visit(node.expr) 354 return node 355 356 def visitFor(self, node): 357 node.assign = self.visit(node.assign) 358 node.list = self.visit(node.list) 359 node.body = self.visit(node.body) 360 node.else_ = self.visit(node.else_) 361 return node 362 363 def _visitPrint(self, node): 364 node.nodes = [self.visit(x) for x in node.nodes] 365 node.dest = self.visit(node.dest) 366 return node 367 visitPrint = visitPrintnl = _visitPrint 368 369 def visitRaise(self, node): 370 node.expr1 = self.visit(node.expr1) 371 node.expr2 = self.visit(node.expr2) 372 node.expr3 = self.visit(node.expr3) 373 return node 374 375 def visitTryExcept(self, node): 376 node.body = self.visit(node.body) 377 node.handlers = self.visit(node.handlers) 378 node.else_ = self.visit(node.else_) 379 return node 380 381 def visitTryFinally(self, node): 382 node.body = self.visit(node.body) 383 node.final = self.visit(node.final) 384 return node 385 386 def visitWhile(self, node): 387 node.test = self.visit(node.test) 388 node.body = self.visit(node.body) 389 node.else_ = self.visit(node.else_) 390 return node 391 392 def visitYield(self, node): 393 node.value = self.visit(node.value) 394 return node 395 287 396 # Operators 288 397 289 398 def _visitBoolOp(self, node): … … 308 417 node.expr = self.visit(node.expr) 309 418 return node 310 419 visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp 311 visitBackquote = _visitUnaryOp420 visitBackquote = visitDiscard = _visitUnaryOp 312 421 313 422 # Identifiers, Literals and Comprehensions 314 423 315 def _visitDefault(self, node):316 return node317 visitAssName = visitConst = visitName = _visitDefault318 319 424 def visitDict(self, node): 320 425 node.items = [(self.visit(k), 321 426 self.visit(v)) for k, v in node.items] … … 381 486 return node 382 487 383 488 384 class ExpressionASTTransformer(ASTTransformer):489 class TemplateASTTransformer(ASTTransformer): 385 490 """Concrete AST transformer that implements the AST transformations needed 386 for template expressions.491 for code embedded in templates. 387 492 """ 388 493 389 494 def __init__(self): … … 398 503 return node 399 504 400 505 def visitAssName(self, node): 401 self.locals[-1].add(node.name) 506 if self.locals: 507 self.locals[-1].add(node.name) 402 508 return node 403 509 510 def visitClass(self, node): 511 self.locals.append(set()) 512 node = ASTTransformer.visitClass(self, node) 513 self.locals.pop() 514 return node 515 516 def visitFor(self, node): 517 self.locals.append(set()) 518 node = ASTTransformer.visitFor(self, node) 519 self.locals.pop() 520 return node 521 522 def visitFunction(self, node): 523 self.locals.append(set(node.argnames)) 524 node = ASTTransformer.visitFunction(self, node) 525 self.locals.pop() 526 return node 527 404 528 def visitGenExpr(self, node): 405 529 self.locals.append(set()) 406 530 node = ASTTransformer.visitGenExpr(self, node)
