Ticket #84: python_pi.3.diff
| File python_pi.3.diff, 23.2 KB (added by cmlenz, 17 years ago) |
|---|
-
genshi/tests/builder.py
45 45 def test_stream_as_child(self): 46 46 xml = list(tag.span(XML('<b>Foo</b>')).generate()) 47 47 self.assertEqual(5, len(xml)) 48 self.assertEqual((Stream.START, ('span', ()) , (None, -1, -1)), xml[0])49 self.assertEqual((Stream.START, ('b', ()) , (None, 1, 0)), xml[1])50 self.assertEqual((Stream.TEXT, 'Foo' , (None, 1, 3)), xml[2])51 self.assertEqual((Stream.END, 'b' , (None, 1, 6)), xml[3])52 self.assertEqual((Stream.END, 'span' , (None, -1, -1)), xml[4])48 self.assertEqual((Stream.START, ('span', ())), xml[0][:2]) 49 self.assertEqual((Stream.START, ('b', ())), xml[1][:2]) 50 self.assertEqual((Stream.TEXT, 'Foo'), xml[2][:2]) 51 self.assertEqual((Stream.END, 'b'), xml[3][:2]) 52 self.assertEqual((Stream.END, 'span'), xml[4][:2]) 53 53 54 54 55 55 def suite(): -
genshi/path.py
156 156 >>> test = Path('child').test() 157 157 >>> for event in xml: 158 158 ... if test(event, {}, {}): 159 ... print event 160 ('START', (QName(u'child'), Attrs([(QName(u'id'), u'2')])), (None, 1, 34))159 ... print event[0], repr(event[1]) 160 START (QName(u'child'), Attrs([(QName(u'id'), u'2')])) 161 161 """ 162 162 paths = [(p, len(p), [0], [], [0] * len(p)) for p in [ 163 163 (ignore_context and [_DOTSLASHSLASH] or []) + p for p in self.paths -
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 148 def keys(self): 149 keys = [] 150 for frame in self.frames: 151 keys += [key for key in frame if key not in keys] 152 return keys 153 154 def items(self): 155 return [(key, self.get(key)) for key in self.keys()] 156 134 157 def push(self, data): 135 158 """Push a new scope on the stack.""" 136 159 -
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
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 ifilter") 416 data = {} 417 suite.execute(data) 418 assert 'ifilter' 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
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.core 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.util import flatten 26 27 27 __all__ = ['Expression', 'Undefined'] 28 if sys.version_info < (2, 4): 29 _locals = dict 30 else: 31 _locals = lambda x: x 28 32 33 __all__ = ['Expression', 'Suite', 'Undefined'] 29 34 30 class Expression(object): 35 36 class Code(object): 37 """Abstract base class for the `Expression` and `Suite` classes.""" 38 __slots__ = ['source', 'code'] 39 40 def __init__(self, source, filename=None, lineno=-1): 41 """Create the code object, either from a string, or from an AST node. 42 43 @param source: either a string containing the source code, or an AST 44 node 45 @param filename: the (preferably absolute) name of the file containing 46 the code 47 @param lineno: the number of the line on which the code was found 48 """ 49 if isinstance(source, basestring): 50 self.source = source 51 node = _parse(source, mode=self.mode) 52 else: 53 assert isinstance(source, ast.Node) 54 self.source = '?' 55 if self.mode == 'eval': 56 node = ast.Expression(source) 57 else: 58 node = ast.Module(None, source) 59 60 self.code = _compile(node, self.source, mode=self.mode, 61 filename=filename, lineno=lineno) 62 63 def __eq__(self, other): 64 return (type(other) == type(self)) and (self.code == other.code) 65 66 def __hash__(self): 67 return hash(self.code) 68 69 def __ne__(self, other): 70 return not self == other 71 72 def __repr__(self): 73 return '%s(%r)' % (self.__class__.__name__, self.source) 74 75 76 class Expression(Code): 31 77 """Evaluates Python expressions used in templates. 32 78 33 79 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) … … 68 114 >>> Expression('len(items)').evaluate(data) 69 115 3 70 116 """ 71 __slots__ = ['source', 'code'] 117 __slots__ = [] 118 mode = 'eval' 72 119 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 120 def evaluate(self, data): 105 121 """Evaluate the expression against the given data dictionary. 106 122 … … 110 126 return eval(self.code, {'data': data, 111 127 '_lookup_name': _lookup_name, 112 128 '_lookup_attr': _lookup_attr, 113 '_lookup_item': _lookup_item}, 114 {'data': data}) 129 '_lookup_item': _lookup_item}, _locals(data)) 115 130 116 131 132 class Suite(Code): 133 """Executes Python statements used in templates. 134 135 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) 136 >>> Suite('foo = dict.some').execute(data) 137 >>> data['foo'] 138 'thing' 139 """ 140 __slots__ = [] 141 mode = 'exec' 142 143 def execute(self, data): 144 """Execute the suite in the given data dictionary. 145 146 @param data: a mapping containing the data to execute in 147 """ 148 exec self.code in {'data': data, 149 '_lookup_name': _lookup_name, 150 '_lookup_attr': _lookup_attr, 151 '_lookup_item': _lookup_item}, data 152 153 117 154 class Undefined(object): 118 155 """Represents a reference to an undefined variable. 119 156 … … 176 213 source = '\xef\xbb\xbf' + source.encode('utf-8') 177 214 return parse(source, mode) 178 215 179 def _compile(node, source=None, filename=None, lineno=-1):180 tree = ExpressionASTTransformer().visit(node)216 def _compile(node, source=None, mode='eval', filename=None, lineno=-1): 217 tree = TemplateASTTransformer().visit(node) 181 218 if isinstance(filename, unicode): 182 219 # unicode file names not allowed for code objects 183 220 filename = filename.encode('utf-8', 'replace') … … 187 224 if lineno <= 0: 188 225 lineno = 1 189 226 190 gen = ExpressionCodeGenerator(tree) 227 if mode == 'eval': 228 gen = ExpressionCodeGenerator(tree) 229 name = '<Expression %s>' % (repr(source or '?').replace("'", '"')) 230 else: 231 gen = ModuleCodeGenerator(tree) 232 name = '<Suite>' 191 233 gen.optimized = True 192 234 code = gen.getCode() 193 235 … … 195 237 # clone the code object while adjusting the line number 196 238 return new.code(0, code.co_nlocals, code.co_stacksize, 197 239 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, (), ()) 240 code.co_names, code.co_varnames, filename, name, lineno, 241 code.co_lnotab, (), ()) 201 242 202 243 BUILTINS = __builtin__.__dict__.copy() 203 244 BUILTINS['Undefined'] = Undefined … … 231 272 key = key[0] 232 273 try: 233 274 return obj[key] 234 except ( KeyError, IndexError, TypeError), e:275 except (AttributeError, KeyError, IndexError, TypeError), e: 235 276 if isinstance(key, basestring): 236 277 val = getattr(obj, key, _UNDEF) 237 278 if val is _UNDEF: … … 249 290 _visitors = {} 250 291 251 292 def visit(self, node): 293 if node is None: 294 return None 252 295 v = self._visitors.get(node.__class__) 253 296 if not v: 254 v = getattr(self, 'visit%s' % node.__class__.__name__) 297 v = getattr(self.__class__, 'visit%s' % node.__class__.__name__, 298 self.__class__._visitDefault) 255 299 self._visitors[node.__class__] = v 256 return v( node)300 return v(self, node) 257 301 302 def _visitDefault(self, node): 303 return node 304 258 305 def visitExpression(self, node): 259 306 node.node = self.visit(node.node) 260 307 return node 261 308 262 # Functions & Accessors 309 def visitModule(self, node): 310 node.node = self.visit(node.node) 311 return node 263 312 313 def visitStmt(self, node): 314 node.nodes = [self.visit(x) for x in node.nodes] 315 return node 316 317 # Classes, Functions & Accessors 318 264 319 def visitCallFunc(self, node): 265 320 node.node = self.visit(node.node) 266 321 node.args = [self.visit(x) for x in node.args] … … 270 325 node.dstar_args = self.visit(node.dstar_args) 271 326 return node 272 327 273 def visitLambda(self, node): 328 def visitClass(self, node): 329 node.bases = [self.visit(x) for x in node.bases] 274 330 node.code = self.visit(node.code) 275 331 node.filename = '<string>' # workaround for bug in pycodegen 276 332 return node 277 333 334 def visitFunction(self, node): 335 if hasattr(node, 'decorators'): 336 node.decorators = self.visit(node.decorators) 337 node.defaults = [self.visit(x) for x in node.defaults] 338 node.code = self.visit(node.code) 339 node.filename = '<string>' # workaround for bug in pycodegen 340 return node 341 278 342 def visitGetattr(self, node): 279 343 node.expr = self.visit(node.expr) 280 344 return node 281 345 346 def visitLambda(self, node): 347 node.code = self.visit(node.code) 348 node.filename = '<string>' # workaround for bug in pycodegen 349 return node 350 282 351 def visitSubscript(self, node): 283 352 node.expr = self.visit(node.expr) 284 353 node.subs = [self.visit(x) for x in node.subs] 285 354 return node 286 355 356 # Statements 357 358 def visitAssert(self, node): 359 node.test = self.visit(node.test) 360 node.fail = self.visit(node.fail) 361 return node 362 363 def visitAssign(self, node): 364 node.nodes = [self.visit(x) for x in node.nodes] 365 node.expr = self.visit(node.expr) 366 return node 367 368 def visitDecorators(self, node): 369 node.nodes = [self.visit(x) for x in node.nodes] 370 return node 371 372 def visitFor(self, node): 373 node.assign = self.visit(node.assign) 374 node.list = self.visit(node.list) 375 node.body = self.visit(node.body) 376 node.else_ = self.visit(node.else_) 377 return node 378 379 def visitIf(self, node): 380 node.tests = [self.visit(x) for x in node.tests] 381 node.else_ = self.visit(node.else_) 382 return node 383 384 def _visitPrint(self, node): 385 node.nodes = [self.visit(x) for x in node.nodes] 386 node.dest = self.visit(node.dest) 387 return node 388 visitPrint = visitPrintnl = _visitPrint 389 390 def visitRaise(self, node): 391 node.expr1 = self.visit(node.expr1) 392 node.expr2 = self.visit(node.expr2) 393 node.expr3 = self.visit(node.expr3) 394 return node 395 396 def visitTryExcept(self, node): 397 node.body = self.visit(node.body) 398 node.handlers = self.visit(node.handlers) 399 node.else_ = self.visit(node.else_) 400 return node 401 402 def visitTryFinally(self, node): 403 node.body = self.visit(node.body) 404 node.final = self.visit(node.final) 405 return node 406 407 def visitWhile(self, node): 408 node.test = self.visit(node.test) 409 node.body = self.visit(node.body) 410 node.else_ = self.visit(node.else_) 411 return node 412 413 def visitWith(self, node): 414 node.expr = self.visit(node.expr) 415 node.vars = [self.visit(x) for x in node.vars] 416 node.body = self.visit(node.body) 417 return node 418 419 def visitYield(self, node): 420 node.value = self.visit(node.value) 421 return node 422 287 423 # Operators 288 424 289 425 def _visitBoolOp(self, node): … … 308 444 node.expr = self.visit(node.expr) 309 445 return node 310 446 visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp 311 visitBackquote = _visitUnaryOp447 visitBackquote = visitDiscard = _visitUnaryOp 312 448 313 449 # Identifiers, Literals and Comprehensions 314 450 315 def _visitDefault(self, node):316 return node317 visitAssName = visitConst = visitName = _visitDefault318 319 451 def visitDict(self, node): 320 452 node.items = [(self.visit(k), 321 453 self.visit(v)) for k, v in node.items] … … 381 513 return node 382 514 383 515 384 class ExpressionASTTransformer(ASTTransformer):516 class TemplateASTTransformer(ASTTransformer): 385 517 """Concrete AST transformer that implements the AST transformations needed 386 for template expressions.518 for code embedded in templates. 387 519 """ 388 520 389 521 def __init__(self): … … 398 530 return node 399 531 400 532 def visitAssName(self, node): 401 self.locals[-1].add(node.name) 533 if self.locals: 534 self.locals[-1].add(node.name) 402 535 return node 403 536 537 def visitClass(self, node): 538 self.locals.append(set()) 539 node = ASTTransformer.visitClass(self, node) 540 self.locals.pop() 541 return node 542 543 def visitFor(self, node): 544 self.locals.append(set()) 545 node = ASTTransformer.visitFor(self, node) 546 self.locals.pop() 547 return node 548 549 def visitFunction(self, node): 550 self.locals.append(set(node.argnames)) 551 node = ASTTransformer.visitFunction(self, node) 552 self.locals.pop() 553 return node 554 404 555 def visitGenExpr(self, node): 405 556 self.locals.append(set()) 406 557 node = ASTTransformer.visitGenExpr(self, node)
