Edgewall Software

Ticket #84: python_pi.diff

File python_pi.diff, 16.4 KB (added by cmlenz, 17 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)