Edgewall Software

Ticket #84: python_pi.diff

File python_pi.diff, 16.4 KB (added by cmlenz, 5 years ago)

First draft of the implementation

  • genshi/template/core.py

     
    107107    def __repr__(self): 
    108108        return repr(list(self.frames)) 
    109109 
     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 
    110125    def __setitem__(self, key, value): 
    111126        """Set a variable in the current scope.""" 
    112127        self.frames[0][key] = value 
     
    129144            if key in frame: 
    130145                return frame[key] 
    131146        return default 
    132     __getitem__ = get 
    133147 
    134148    def push(self, data): 
    135149        """Push a new scope on the stack.""" 
  • genshi/template/tests/eval.py

     
    1515import sys 
    1616import unittest 
    1717 
    18 from genshi.template.eval import Expression, Undefined 
     18from genshi.template.eval import Expression, Suite, Undefined 
    1919 
    2020 
    2121class ExpressionTestCase(unittest.TestCase): 
     
    382382        self.assertRaises(TypeError, expr.evaluate, {'nothing': object()}) 
    383383 
    384384 
     385class 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 
     402del 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 
     422while 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 
    385432def suite(): 
    386433    suite = unittest.TestSuite() 
    387434    suite.addTest(doctest.DocTestSuite(Expression.__module__)) 
    388435    suite.addTest(unittest.makeSuite(ExpressionTestCase, 'test')) 
     436    suite.addTest(unittest.makeSuite(SuiteTestCase, 'test')) 
    389437    return suite 
    390438 
    391439if __name__ == '__main__': 
  • genshi/template/markup.py

     
    1616from itertools import chain 
    1717 
    1818from genshi.core import Attrs, Namespace, Stream, StreamEventKind 
    19 from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT 
     19from genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT 
    2020from genshi.input import XMLParser 
    2121from genshi.template.core import BadDirectiveError, Template, \ 
    22                                  _apply_directives, SUB 
     22                                 TemplateSyntaxError, _apply_directives, SUB 
     23from genshi.template.eval import Suite 
    2324from genshi.template.loader import TemplateNotFound 
    2425from genshi.template.directives import * 
    2526 
     
    3536      <li>1</li><li>2</li><li>3</li> 
    3637    </ul> 
    3738    """ 
     39    EXEC = StreamEventKind('EXEC') 
    3840    INCLUDE = StreamEventKind('INCLUDE') 
    3941 
    4042    DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/') 
     
    5961        Template.__init__(self, source, basedir=basedir, filename=filename, 
    6062                          loader=loader, encoding=encoding) 
    6163 
    62         self.filters.append(self._match) 
     64        self.filters += [self._exec, self._match] 
    6365        if loader: 
    6466            self.filters.append(self._include) 
    6567 
     
    169171                    stream[start_offset:] = [(SUB, (directives, substream), 
    170172                                              pos)] 
    171173 
     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 
    172183            elif kind is TEXT: 
    173184                for kind, data, pos in self._interpolate(data, self.basedir, 
    174185                                                         *pos): 
     
    190201                data = data[0], list(self._prepare(data[1])) 
    191202            yield kind, data, pos 
    192203 
     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 
    193214    def _include(self, stream, ctxt): 
    194215        """Internal stream filter that performs inclusion of external 
    195216        template files. 
     
    291312                yield event 
    292313 
    293314 
     315EXEC = MarkupTemplate.EXEC 
    294316INCLUDE = MarkupTemplate.INCLUDE 
  • genshi/template/eval.py

     
    1515 
    1616import __builtin__ 
    1717from compiler import ast, parse 
    18 from compiler.pycodegen import ExpressionCodeGenerator 
     18from compiler.pycodegen import ExpressionCodeGenerator, ModuleCodeGenerator 
    1919import new 
    2020try: 
    2121    set 
     
    2424 
    2525from genshi.util import flatten 
    2626 
    27 __all__ = ['Expression', 'Undefined'] 
     27__all__ = ['Expression', 'Suite', 'Undefined'] 
    2828 
    2929 
    30 class Expression(object): 
     30class 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 
     69class Expression(Code): 
    3170    """Evaluates Python expressions used in templates. 
    3271 
    3372    >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) 
     
    68107    >>> Expression('len(items)').evaluate(data) 
    69108    3 
    70109    """ 
    71     __slots__ = ['source', 'code'] 
     110    __slots__ = [] 
     111    mode = 'eval' 
    72112 
    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 the 
    77             expression, or an AST node 
    78         @param filename: the (preferably absolute) name of the file containing 
    79             the expression 
    80         @param lineno: the number of the line on which the expression was found 
    81         """ 
    82         if isinstance(source, basestring): 
    83             self.source = source 
    84             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 == other 
    100  
    101     def __repr__(self): 
    102         return 'Expression(%r)' % self.source 
    103  
    104113    def evaluate(self, data): 
    105114        """Evaluate the expression against the given data dictionary. 
    106115         
     
    114123                               {'data': data}) 
    115124 
    116125 
     126class 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 
    117148class Undefined(object): 
    118149    """Represents a reference to an undefined variable. 
    119150     
     
    176207        source = '\xef\xbb\xbf' + source.encode('utf-8') 
    177208    return parse(source, mode) 
    178209 
    179 def _compile(node, source=None, filename=None, lineno=-1): 
    180     tree = ExpressionASTTransformer().visit(node) 
     210def _compile(node, source=None, mode='eval', filename=None, lineno=-1): 
     211    tree = TemplateASTTransformer().visit(node) 
    181212    if isinstance(filename, unicode): 
    182213        # unicode file names not allowed for code objects 
    183214        filename = filename.encode('utf-8', 'replace') 
     
    187218    if lineno <= 0: 
    188219        lineno = 1 
    189220 
    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>' 
    191227    gen.optimized = True 
    192228    code = gen.getCode() 
    193229 
     230 
    194231    # We'd like to just set co_firstlineno, but it's readonly. So we need to 
    195232    # clone the code object while adjusting the line number 
    196233    return new.code(0, code.co_nlocals, code.co_stacksize, 
    197234                    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, (), ()) 
    201237 
    202238BUILTINS = __builtin__.__dict__.copy() 
    203239BUILTINS['Undefined'] = Undefined 
     
    231267        key = key[0] 
    232268    try: 
    233269        return obj[key] 
    234     except (KeyError, IndexError, TypeError), e: 
     270    except (AttributeError, KeyError, IndexError, TypeError), e: 
    235271        if isinstance(key, basestring): 
    236272            val = getattr(obj, key, _UNDEF) 
    237273            if val is _UNDEF: 
     
    246282    Every visitor method can be overridden to return an AST node that has been 
    247283    altered or replaced in some way. 
    248284    """ 
    249     _visitors = {} 
     285    def __init__(self): 
     286        self._visitors = {} 
    250287 
    251288    def visit(self, node): 
     289        if node is None: 
     290            return None 
    252291        v = self._visitors.get(node.__class__) 
    253292        if not v: 
    254             v = getattr(self, 'visit%s' % node.__class__.__name__) 
     293            v = getattr(self, 'visit%s' % node.__class__.__name__, 
     294                        self._visitDefault) 
    255295            self._visitors[node.__class__] = v 
    256296        return v(node) 
    257297 
     298    def _visitDefault(self, node): 
     299        return node 
     300 
    258301    def visitExpression(self, node): 
    259302        node.node = self.visit(node.node) 
    260303        return node 
    261304 
    262     # Functions & Accessors 
     305    def visitModule(self, node): 
     306        node.node = self.visit(node.node) 
     307        return node 
    263308 
     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 
    264315    def visitCallFunc(self, node): 
    265316        node.node = self.visit(node.node) 
    266317        node.args = [self.visit(x) for x in node.args] 
     
    270321            node.dstar_args = self.visit(node.dstar_args) 
    271322        return node 
    272323 
    273     def visitLambda(self, node): 
     324    def visitClass(self, node): 
     325        node.bases = [self.visit(x) for x in node.bases] 
    274326        node.code = self.visit(node.code) 
    275327        node.filename = '<string>' # workaround for bug in pycodegen 
    276328        return node 
    277329 
     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 
    278336    def visitGetattr(self, node): 
    279337        node.expr = self.visit(node.expr) 
    280338        return node 
    281339 
     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 
    282345    def visitSubscript(self, node): 
    283346        node.expr = self.visit(node.expr) 
    284347        node.subs = [self.visit(x) for x in node.subs] 
    285348        return node 
    286349 
     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 
    287373    # Operators 
    288374 
    289375    def _visitBoolOp(self, node): 
     
    308394        node.expr = self.visit(node.expr) 
    309395        return node 
    310396    visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp 
    311     visitBackquote = _visitUnaryOp 
     397    visitBackquote = visitDiscard = _visitUnaryOp 
    312398 
    313399    # Identifiers, Literals and Comprehensions 
    314400 
    315     def _visitDefault(self, node): 
    316         return node 
    317     visitAssName = visitConst = visitName = _visitDefault 
    318  
    319401    def visitDict(self, node): 
    320402        node.items = [(self.visit(k), 
    321403                       self.visit(v)) for k, v in node.items] 
     
    381463        return node 
    382464 
    383465 
    384 class ExpressionASTTransformer(ASTTransformer): 
     466class TemplateASTTransformer(ASTTransformer): 
    385467    """Concrete AST transformer that implements the AST transformations needed 
    386     for template expressions. 
     468    for code embedded in templates. 
    387469    """ 
    388470 
    389471    def __init__(self): 
     472        ASTTransformer.__init__(self) 
    390473        self.locals = [] 
    391474 
    392475    def visitConst(self, node): 
     
    398481        return node 
    399482 
    400483    def visitAssName(self, node): 
    401         self.locals[-1].add(node.name) 
     484        if self.locals: 
     485            self.locals[-1].add(node.name) 
    402486        return node 
    403487 
     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 
    404500    def visitGenExpr(self, node): 
    405501        self.locals.append(set()) 
    406502        node = ASTTransformer.visitGenExpr(self, node)