Ticket #84: python_pi.5.diff
| File python_pi.5.diff, 22.0 KB (added by cmlenz, 17 years ago) |
|---|
-
genshi/template/base.py
109 109 def __repr__(self): 110 110 return repr(list(self.frames)) 111 111 112 def __contains__(self, key): 113 """Return whether a variable exists in any of the scopes.""" 114 return self._find(key)[1] is not None 115 116 def __delitem__(self, key): 117 """Set a variable in the current scope.""" 118 for frame in self.frames: 119 if key in frame: 120 del frame[key] 121 122 def __getitem__(self, key): 123 """Get a variables's value, starting at the current scope and going 124 upward. 125 126 Raises `KeyError` if the requested variable wasn't found in any scope. 127 """ 128 value, frame = self._find(key) 129 if frame is None: 130 raise KeyError(key) 131 return value 132 112 133 def __setitem__(self, key, value): 113 134 """Set a variable in the current scope.""" 114 135 self.frames[0][key] = value … … 131 152 if key in frame: 132 153 return frame[key] 133 154 return default 134 __getitem__ = get135 155 156 def keys(self): 157 keys = [] 158 for frame in self.frames: 159 keys += [key for key in frame if key not in keys] 160 return keys 161 162 def items(self): 163 return [(key, self.get(key)) for key in self.keys()] 164 136 165 def push(self, data): 137 166 """Push a new scope on the stack.""" 138 167 -
genshi/template/tests/markup.py
195 195 \xf6 196 196 </div>""", unicode(tmpl.generate())) 197 197 198 def test_exec_import(self): 199 tmpl = MarkupTemplate(u"""<?python from datetime import timedelta ?> 200 <div xmlns:py="http://genshi.edgewall.org/"> 201 ${timedelta(days=2)} 202 </div>""") 203 self.assertEqual(u"""<div> 204 2 days, 0:00:00 205 </div>""", str(tmpl.generate())) 206 207 def test_exec_def(self): 208 tmpl = MarkupTemplate(u""" 209 <?python 210 def foo(): 211 return 42 212 ?> 213 <div xmlns:py="http://genshi.edgewall.org/"> 214 ${foo()} 215 </div>""") 216 self.assertEqual(u"""<div> 217 42 218 </div>""", str(tmpl.generate())) 219 198 220 def test_include_in_loop(self): 199 221 dirname = tempfile.mkdtemp(suffix='genshi_test') 200 222 try: -
genshi/template/tests/eval.py
16 16 import unittest 17 17 18 18 from genshi.core import Markup 19 from genshi.template.eval import Expression, Undefined19 from genshi.template.eval import Expression, Suite, Undefined 20 20 21 21 22 22 class ExpressionTestCase(unittest.TestCase): … … 399 399 self.assertRaises(TypeError, expr.evaluate, {'nothing': object()}) 400 400 401 401 402 class SuiteTestCase(unittest.TestCase): 403 404 def test_assign(self): 405 suite = Suite("foo = 42") 406 data = {} 407 suite.execute(data) 408 self.assertEqual(42, data['foo']) 409 410 def test_def(self): 411 suite = Suite("def donothing(): pass") 412 data = {} 413 suite.execute(data) 414 assert 'donothing' in data 415 self.assertEqual(None, data['donothing']()) 416 417 def test_delete(self): 418 suite = Suite("""foo = 42 419 del foo 420 """) 421 data = {} 422 suite.execute(data) 423 assert 'foo' not in data 424 425 def test_class(self): 426 suite = Suite("class plain(object): pass") 427 data = {} 428 suite.execute(data) 429 assert 'plain' in data 430 431 def test_import(self): 432 suite = Suite("from itertools import ifilter") 433 data = {} 434 suite.execute(data) 435 assert 'ifilter' in data 436 437 def test_for(self): 438 suite = Suite("""x = [] 439 for i in range(3): 440 x.append(i**2) 441 """) 442 data = {} 443 suite.execute(data) 444 self.assertEqual([0, 1, 4], data['x']) 445 446 def test_if(self): 447 suite = Suite("""if foo == 42: 448 x = True 449 """) 450 data = {'foo': 42} 451 suite.execute(data) 452 self.assertEqual(True, data['x']) 453 454 def test_raise(self): 455 suite = Suite("""raise NotImplementedError""") 456 self.assertRaises(NotImplementedError, suite.execute, {}) 457 458 def test_try_except(self): 459 suite = Suite("""try: 460 import somemod 461 except ImportError: 462 somemod = None 463 else: 464 somemod.dosth()""") 465 data = {} 466 suite.execute(data) 467 self.assertEqual(None, data['somemod']) 468 469 def test_finally(self): 470 suite = Suite("""try: 471 x = 2 472 finally: 473 x = None 474 """) 475 data = {} 476 suite.execute(data) 477 self.assertEqual(None, data['x']) 478 479 def test_while_break(self): 480 suite = Suite("""x = 0 481 while x < 5: 482 x += step 483 if x == 4: 484 break 485 """) 486 data = {'step': 2} 487 suite.execute(data) 488 self.assertEqual(4, data['x']) 489 490 402 491 def suite(): 403 492 suite = unittest.TestSuite() 404 493 suite.addTest(doctest.DocTestSuite(Expression.__module__)) 405 494 suite.addTest(unittest.makeSuite(ExpressionTestCase, 'test')) 495 suite.addTest(unittest.makeSuite(SuiteTestCase, 'test')) 406 496 return suite 407 497 408 498 if __name__ == '__main__': -
genshi/template/markup.py
14 14 """Markup templating engine.""" 15 15 16 16 from itertools import chain 17 import sys 18 from textwrap import dedent 17 19 18 20 from genshi.core import Attrs, Namespace, Stream, StreamEventKind 19 from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT21 from genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT 20 22 from genshi.input import XMLParser 21 23 from genshi.template.base import BadDirectiveError, Template, \ 22 _apply_directives, SUB 24 TemplateSyntaxError, _apply_directives, SUB 25 from genshi.template.eval import Suite 23 26 from genshi.template.loader import TemplateNotFound 24 27 from genshi.template.directives import * 25 28 29 if sys.version_info < (2, 4): 30 _ctxt2dict = lambda ctxt: ctxt.frames[0] 31 else: 32 _ctxt2dict = lambda ctxt: ctxt 26 33 34 27 35 class MarkupTemplate(Template): 28 36 """Implementation of the template language for XML-based templates. 29 37 … … 35 43 <li>1</li><li>2</li><li>3</li> 36 44 </ul> 37 45 """ 46 EXEC = StreamEventKind('EXEC') 38 47 INCLUDE = StreamEventKind('INCLUDE') 39 48 40 49 DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/') … … 59 68 Template.__init__(self, source, basedir=basedir, filename=filename, 60 69 loader=loader, encoding=encoding) 61 70 62 self.filters .append(self._match)71 self.filters += [self._exec, self._match] 63 72 if loader: 64 73 self.filters.append(self._include) 65 74 … … 169 178 stream[start_offset:] = [(SUB, (directives, substream), 170 179 pos)] 171 180 181 elif kind is PI and data[0] == 'python': 182 try: 183 # As Expat doesn't report whitespace between the PI target 184 # and the data, we have to jump through some hoops here to 185 # get correctly indented Python code 186 # Unfortunately, we'll still probably not get the line 187 # number quite right 188 lines = [line.expandtabs() for line in data[1].splitlines()] 189 first = lines[0] 190 rest = dedent('\n'.join(lines[1:])) 191 if first.rstrip().endswith(':') and not rest[0].isspace(): 192 rest = '\n'.join([' ' + line for line 193 in rest.splitlines()]) 194 source = '\n'.join([first, rest]) 195 suite = Suite(source, self.filepath, pos[1]) 196 except SyntaxError, err: 197 raise TemplateSyntaxError(err, self.filepath, 198 pos[1] + (err.lineno or 1) - 1, 199 pos[2] + (err.offset or 0)) 200 stream.append((EXEC, suite, pos)) 201 172 202 elif kind is TEXT: 173 203 for kind, data, pos in self._interpolate(data, self.basedir, 174 204 *pos): … … 190 220 data = data[0], list(self._prepare(data[1])) 191 221 yield kind, data, pos 192 222 223 def _exec(self, stream, ctxt): 224 """Internal stream filter that executes code in <?python ?> processing 225 instructions. 226 """ 227 for event in stream: 228 if event[0] is EXEC: 229 event[1].execute(_ctxt2dict(ctxt)) 230 else: 231 yield event 232 193 233 def _include(self, stream, ctxt): 194 234 """Internal stream filter that performs inclusion of external 195 235 template files. … … 291 331 yield event 292 332 293 333 334 EXEC = MarkupTemplate.EXEC 294 335 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 22 22 except NameError: 23 23 from sets import Set as set 24 import sys 24 25 25 26 from genshi.core import Markup 26 27 from genshi.util import flatten 27 28 28 __all__ = ['Expression', 'Undefined'] 29 if sys.version_info < (2, 4): 30 _locals = dict 31 else: 32 _locals = lambda x: x 29 33 34 __all__ = ['Expression', 'Suite', 'Undefined'] 30 35 31 class Expression(object): 36 37 class Code(object): 38 """Abstract base class for the `Expression` and `Suite` classes.""" 39 __slots__ = ['source', 'code'] 40 41 def __init__(self, source, filename=None, lineno=-1): 42 """Create the code object, either from a string, or from an AST node. 43 44 @param source: either a string containing the source code, or an AST 45 node 46 @param filename: the (preferably absolute) name of the file containing 47 the code 48 @param lineno: the number of the line on which the code was found 49 """ 50 if isinstance(source, basestring): 51 self.source = source 52 node = _parse(source, mode=self.mode) 53 else: 54 assert isinstance(source, ast.Node) 55 self.source = '?' 56 if self.mode == 'eval': 57 node = ast.Expression(source) 58 else: 59 node = ast.Module(None, source) 60 61 self.code = _compile(node, self.source, mode=self.mode, 62 filename=filename, lineno=lineno) 63 64 def __eq__(self, other): 65 return (type(other) == type(self)) and (self.code == other.code) 66 67 def __hash__(self): 68 return hash(self.code) 69 70 def __ne__(self, other): 71 return not self == other 72 73 def __repr__(self): 74 return '%s(%r)' % (self.__class__.__name__, self.source) 75 76 77 class Expression(Code): 32 78 """Evaluates Python expressions used in templates. 33 79 34 80 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) … … 69 115 >>> Expression('len(items)').evaluate(data) 70 116 3 71 117 """ 72 __slots__ = ['source', 'code'] 118 __slots__ = [] 119 mode = 'eval' 73 120 74 def __init__(self, source, filename=None, lineno=-1):75 """Create the expression, either from a string, or from an AST node.76 77 @param source: either a string containing the source code of the78 expression, or an AST node79 @param filename: the (preferably absolute) name of the file containing80 the expression81 @param lineno: the number of the line on which the expression was found82 """83 if isinstance(source, basestring):84 self.source = source85 self.code = _compile(_parse(source), self.source, filename=filename,86 lineno=lineno)87 else:88 assert isinstance(source, ast.Node)89 self.source = '?'90 self.code = _compile(ast.Expression(source), filename=filename,91 lineno=lineno)92 93 def __eq__(self, other):94 return (type(other) == Expression) and (self.code == other.code)95 96 def __hash__(self):97 return hash(self.code)98 99 def __ne__(self, other):100 return not self == other101 102 def __repr__(self):103 return 'Expression(%r)' % self.source104 105 121 def evaluate(self, data): 106 122 """Evaluate the expression against the given data dictionary. 107 123 … … 111 127 return eval(self.code, {'data': data, 112 128 '_lookup_name': _lookup_name, 113 129 '_lookup_attr': _lookup_attr, 114 '_lookup_item': _lookup_item}, 115 {'data': data}) 130 '_lookup_item': _lookup_item}, _locals(data)) 116 131 117 132 133 class Suite(Code): 134 """Executes Python statements used in templates. 135 136 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) 137 >>> Suite('foo = dict.some').execute(data) 138 >>> data['foo'] 139 'thing' 140 """ 141 __slots__ = [] 142 mode = 'exec' 143 144 def execute(self, data): 145 """Execute the suite in the given data dictionary. 146 147 @param data: a mapping containing the data to execute in 148 """ 149 exec self.code in {'data': data, 150 '_lookup_name': _lookup_name, 151 '_lookup_attr': _lookup_attr, 152 '_lookup_item': _lookup_item}, data 153 154 118 155 class Undefined(object): 119 156 """Represents a reference to an undefined variable. 120 157 … … 177 214 source = '\xef\xbb\xbf' + source.encode('utf-8') 178 215 return parse(source, mode) 179 216 180 def _compile(node, source=None, filename=None, lineno=-1):181 tree = ExpressionASTTransformer().visit(node)217 def _compile(node, source=None, mode='eval', filename=None, lineno=-1): 218 tree = TemplateASTTransformer().visit(node) 182 219 if isinstance(filename, unicode): 183 220 # unicode file names not allowed for code objects 184 221 filename = filename.encode('utf-8', 'replace') … … 188 225 if lineno <= 0: 189 226 lineno = 1 190 227 191 gen = ExpressionCodeGenerator(tree) 228 if mode == 'eval': 229 gen = ExpressionCodeGenerator(tree) 230 name = '<Expression %s>' % (repr(source or '?').replace("'", '"')) 231 else: 232 gen = ModuleCodeGenerator(tree) 233 name = '<Suite>' 192 234 gen.optimized = True 193 235 code = gen.getCode() 194 236 … … 196 238 # clone the code object while adjusting the line number 197 239 return new.code(0, code.co_nlocals, code.co_stacksize, 198 240 code.co_flags | 0x0040, code.co_code, code.co_consts, 199 code.co_names, code.co_varnames, filename, 200 '<Expression %s>' % (repr(source or '?').replace("'", '"')), 201 lineno, code.co_lnotab, (), ()) 241 code.co_names, code.co_varnames, filename, name, lineno, 242 code.co_lnotab, (), ()) 202 243 203 244 BUILTINS = __builtin__.__dict__.copy() 204 245 BUILTINS.update({'Markup': Markup, 'Undefined': Undefined}) … … 232 273 key = key[0] 233 274 try: 234 275 return obj[key] 235 except ( KeyError, IndexError, TypeError), e:276 except (AttributeError, KeyError, IndexError, TypeError), e: 236 277 if isinstance(key, basestring): 237 278 val = getattr(obj, key, _UNDEF) 238 279 if val is _UNDEF: … … 250 291 _visitors = {} 251 292 252 293 def visit(self, node): 294 if node is None: 295 return None 253 296 v = self._visitors.get(node.__class__) 254 297 if not v: 255 v = getattr(self, 'visit%s' % node.__class__.__name__) 256 self._visitors[node.__class__] = v 257 return v(node) 298 v = getattr(self.__class__, 'visit%s' % node.__class__.__name__, 299 self.__class__._visitDefault) 300 #self._visitors[node.__class__] = v 301 return v(self, node) 258 302 303 def _visitDefault(self, node): 304 return node 305 259 306 def visitExpression(self, node): 260 307 node.node = self.visit(node.node) 261 308 return node 262 309 263 # Functions & Accessors 310 def visitModule(self, node): 311 node.node = self.visit(node.node) 312 return node 264 313 314 def visitStmt(self, node): 315 node.nodes = [self.visit(x) for x in node.nodes] 316 return node 317 318 # Classes, Functions & Accessors 319 265 320 def visitCallFunc(self, node): 266 321 node.node = self.visit(node.node) 267 322 node.args = [self.visit(x) for x in node.args] … … 271 326 node.dstar_args = self.visit(node.dstar_args) 272 327 return node 273 328 274 def visitLambda(self, node): 329 def visitClass(self, node): 330 node.bases = [self.visit(x) for x in node.bases] 275 331 node.code = self.visit(node.code) 276 332 node.filename = '<string>' # workaround for bug in pycodegen 277 333 return node 278 334 335 def visitFunction(self, node): 336 if hasattr(node, 'decorators'): 337 node.decorators = self.visit(node.decorators) 338 node.defaults = [self.visit(x) for x in node.defaults] 339 node.code = self.visit(node.code) 340 node.filename = '<string>' # workaround for bug in pycodegen 341 return node 342 279 343 def visitGetattr(self, node): 280 344 node.expr = self.visit(node.expr) 281 345 return node 282 346 347 def visitLambda(self, node): 348 node.code = self.visit(node.code) 349 node.filename = '<string>' # workaround for bug in pycodegen 350 return node 351 283 352 def visitSubscript(self, node): 284 353 node.expr = self.visit(node.expr) 285 354 node.subs = [self.visit(x) for x in node.subs] 286 355 return node 287 356 357 # Statements 358 359 def visitAssert(self, node): 360 node.test = self.visit(node.test) 361 node.fail = self.visit(node.fail) 362 return node 363 364 def visitAssign(self, node): 365 node.nodes = [self.visit(x) for x in node.nodes] 366 node.expr = self.visit(node.expr) 367 return node 368 369 def visitDecorators(self, node): 370 node.nodes = [self.visit(x) for x in node.nodes] 371 return node 372 373 def visitFor(self, node): 374 node.assign = self.visit(node.assign) 375 node.list = self.visit(node.list) 376 node.body = self.visit(node.body) 377 node.else_ = self.visit(node.else_) 378 return node 379 380 def visitIf(self, node): 381 node.tests = [self.visit(x) for x in node.tests] 382 node.else_ = self.visit(node.else_) 383 return node 384 385 def _visitPrint(self, node): 386 node.nodes = [self.visit(x) for x in node.nodes] 387 node.dest = self.visit(node.dest) 388 return node 389 visitPrint = visitPrintnl = _visitPrint 390 391 def visitRaise(self, node): 392 node.expr1 = self.visit(node.expr1) 393 node.expr2 = self.visit(node.expr2) 394 node.expr3 = self.visit(node.expr3) 395 return node 396 397 def visitTryExcept(self, node): 398 node.body = self.visit(node.body) 399 node.handlers = self.visit(node.handlers) 400 node.else_ = self.visit(node.else_) 401 return node 402 403 def visitTryFinally(self, node): 404 node.body = self.visit(node.body) 405 node.final = self.visit(node.final) 406 return node 407 408 def visitWhile(self, node): 409 node.test = self.visit(node.test) 410 node.body = self.visit(node.body) 411 node.else_ = self.visit(node.else_) 412 return node 413 414 def visitWith(self, node): 415 node.expr = self.visit(node.expr) 416 node.vars = [self.visit(x) for x in node.vars] 417 node.body = self.visit(node.body) 418 return node 419 420 def visitYield(self, node): 421 node.value = self.visit(node.value) 422 return node 423 288 424 # Operators 289 425 290 426 def _visitBoolOp(self, node): … … 310 446 node.expr = self.visit(node.expr) 311 447 return node 312 448 visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp 313 visitBackquote = _visitUnaryOp449 visitBackquote = visitDiscard = _visitUnaryOp 314 450 315 451 def visitIfExp(self, node): 316 452 node.test = self.visit(node.test) … … 320 456 321 457 # Identifiers, Literals and Comprehensions 322 458 323 def _visitDefault(self, node):324 return node325 visitAssName = visitConst = visitName = _visitDefault326 327 459 def visitDict(self, node): 328 460 node.items = [(self.visit(k), 329 461 self.visit(v)) for k, v in node.items] … … 389 521 return node 390 522 391 523 392 class ExpressionASTTransformer(ASTTransformer):524 class TemplateASTTransformer(ASTTransformer): 393 525 """Concrete AST transformer that implements the AST transformations needed 394 for template expressions.526 for code embedded in templates. 395 527 """ 396 528 397 529 def __init__(self): … … 406 538 return node 407 539 408 540 def visitAssName(self, node): 409 self.locals[-1].add(node.name) 541 if self.locals: 542 self.locals[-1].add(node.name) 410 543 return node 411 544 545 def visitClass(self, node): 546 self.locals.append(set()) 547 node = ASTTransformer.visitClass(self, node) 548 self.locals.pop() 549 return node 550 551 def visitFor(self, node): 552 self.locals.append(set()) 553 node = ASTTransformer.visitFor(self, node) 554 self.locals.pop() 555 return node 556 557 def visitFunction(self, node): 558 self.locals.append(set(node.argnames)) 559 node = ASTTransformer.visitFunction(self, node) 560 self.locals.pop() 561 return node 562 412 563 def visitGenExpr(self, node): 413 564 self.locals.append(set()) 414 565 node = ASTTransformer.visitGenExpr(self, node)
