Edgewall Software

Ticket #84: python_pi.5.diff

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

Updated patch

  • genshi/template/base.py

     
    109109    def __repr__(self):
    110110        return repr(list(self.frames))
    111111
     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
    112133    def __setitem__(self, key, value):
    113134        """Set a variable in the current scope."""
    114135        self.frames[0][key] = value
     
    131152            if key in frame:
    132153                return frame[key]
    133154        return default
    134     __getitem__ = get
    135155
     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
    136165    def push(self, data):
    137166        """Push a new scope on the stack."""
    138167
  • genshi/template/tests/markup.py

     
    195195          \xf6
    196196        </div>""", unicode(tmpl.generate()))
    197197
     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
    198220    def test_include_in_loop(self):
    199221        dirname = tempfile.mkdtemp(suffix='genshi_test')
    200222        try:
  • genshi/template/tests/eval.py

     
    1616import unittest
    1717
    1818from genshi.core import Markup
    19 from genshi.template.eval import Expression, Undefined
     19from genshi.template.eval import Expression, Suite, Undefined
    2020
    2121
    2222class ExpressionTestCase(unittest.TestCase):
     
    399399        self.assertRaises(TypeError, expr.evaluate, {'nothing': object()})
    400400
    401401
     402class 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
     419del 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 = []
     439for 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
     461except ImportError:
     462    somemod = None
     463else:
     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
     472finally:
     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
     481while 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
    402491def suite():
    403492    suite = unittest.TestSuite()
    404493    suite.addTest(doctest.DocTestSuite(Expression.__module__))
    405494    suite.addTest(unittest.makeSuite(ExpressionTestCase, 'test'))
     495    suite.addTest(unittest.makeSuite(SuiteTestCase, 'test'))
    406496    return suite
    407497
    408498if __name__ == '__main__':
  • genshi/template/markup.py

     
    1414"""Markup templating engine."""
    1515
    1616from itertools import chain
     17import sys
     18from textwrap import dedent
    1719
    1820from genshi.core import Attrs, Namespace, Stream, StreamEventKind
    19 from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT
     21from genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT
    2022from genshi.input import XMLParser
    2123from genshi.template.base import BadDirectiveError, Template, \
    22                                  _apply_directives, SUB
     24                                 TemplateSyntaxError, _apply_directives, SUB
     25from genshi.template.eval import Suite
    2326from genshi.template.loader import TemplateNotFound
    2427from genshi.template.directives import *
    2528
     29if sys.version_info < (2, 4):
     30    _ctxt2dict = lambda ctxt: ctxt.frames[0]
     31else:
     32    _ctxt2dict = lambda ctxt: ctxt
    2633
     34
    2735class MarkupTemplate(Template):
    2836    """Implementation of the template language for XML-based templates.
    2937   
     
    3543      <li>1</li><li>2</li><li>3</li>
    3644    </ul>
    3745    """
     46    EXEC = StreamEventKind('EXEC')
    3847    INCLUDE = StreamEventKind('INCLUDE')
    3948
    4049    DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/')
     
    5968        Template.__init__(self, source, basedir=basedir, filename=filename,
    6069                          loader=loader, encoding=encoding)
    6170
    62         self.filters.append(self._match)
     71        self.filters += [self._exec, self._match]
    6372        if loader:
    6473            self.filters.append(self._include)
    6574
     
    169178                    stream[start_offset:] = [(SUB, (directives, substream),
    170179                                              pos)]
    171180
     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
    172202            elif kind is TEXT:
    173203                for kind, data, pos in self._interpolate(data, self.basedir,
    174204                                                         *pos):
     
    190220                data = data[0], list(self._prepare(data[1]))
    191221            yield kind, data, pos
    192222
     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
    193233    def _include(self, stream, ctxt):
    194234        """Internal stream filter that performs inclusion of external
    195235        template files.
     
    291331                yield event
    292332
    293333
     334EXEC = MarkupTemplate.EXEC
    294335INCLUDE = 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
    2222except NameError:
    2323    from sets import Set as set
     24import sys
    2425
    2526from genshi.core import Markup
    2627from genshi.util import flatten
    2728
    28 __all__ = ['Expression', 'Undefined']
     29if sys.version_info < (2, 4):
     30    _locals = dict
     31else:
     32    _locals = lambda x: x
    2933
     34__all__ = ['Expression', 'Suite', 'Undefined']
    3035
    31 class Expression(object):
     36
     37class 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
     77class Expression(Code):
    3278    """Evaluates Python expressions used in templates.
    3379
    3480    >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
     
    69115    >>> Expression('len(items)').evaluate(data)
    70116    3
    71117    """
    72     __slots__ = ['source', 'code']
     118    __slots__ = []
     119    mode = 'eval'
    73120
    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 the
    78             expression, or an AST node
    79         @param filename: the (preferably absolute) name of the file containing
    80             the expression
    81         @param lineno: the number of the line on which the expression was found
    82         """
    83         if isinstance(source, basestring):
    84             self.source = source
    85             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 == other
    101 
    102     def __repr__(self):
    103         return 'Expression(%r)' % self.source
    104 
    105121    def evaluate(self, data):
    106122        """Evaluate the expression against the given data dictionary.
    107123       
     
    111127        return eval(self.code, {'data': data,
    112128                                '_lookup_name': _lookup_name,
    113129                                '_lookup_attr': _lookup_attr,
    114                                 '_lookup_item': _lookup_item},
    115                                {'data': data})
     130                                '_lookup_item': _lookup_item}, _locals(data))
    116131
    117132
     133class 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
    118155class Undefined(object):
    119156    """Represents a reference to an undefined variable.
    120157   
     
    177214        source = '\xef\xbb\xbf' + source.encode('utf-8')
    178215    return parse(source, mode)
    179216
    180 def _compile(node, source=None, filename=None, lineno=-1):
    181     tree = ExpressionASTTransformer().visit(node)
     217def _compile(node, source=None, mode='eval', filename=None, lineno=-1):
     218    tree = TemplateASTTransformer().visit(node)
    182219    if isinstance(filename, unicode):
    183220        # unicode file names not allowed for code objects
    184221        filename = filename.encode('utf-8', 'replace')
     
    188225    if lineno <= 0:
    189226        lineno = 1
    190227
    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>'
    192234    gen.optimized = True
    193235    code = gen.getCode()
    194236
     
    196238    # clone the code object while adjusting the line number
    197239    return new.code(0, code.co_nlocals, code.co_stacksize,
    198240                    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, (), ())
    202243
    203244BUILTINS = __builtin__.__dict__.copy()
    204245BUILTINS.update({'Markup': Markup, 'Undefined': Undefined})
     
    232273        key = key[0]
    233274    try:
    234275        return obj[key]
    235     except (KeyError, IndexError, TypeError), e:
     276    except (AttributeError, KeyError, IndexError, TypeError), e:
    236277        if isinstance(key, basestring):
    237278            val = getattr(obj, key, _UNDEF)
    238279            if val is _UNDEF:
     
    250291    _visitors = {}
    251292
    252293    def visit(self, node):
     294        if node is None:
     295            return None
    253296        v = self._visitors.get(node.__class__)
    254297        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)
    258302
     303    def _visitDefault(self, node):
     304        return node
     305
    259306    def visitExpression(self, node):
    260307        node.node = self.visit(node.node)
    261308        return node
    262309
    263     # Functions & Accessors
     310    def visitModule(self, node):
     311        node.node = self.visit(node.node)
     312        return node
    264313
     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
    265320    def visitCallFunc(self, node):
    266321        node.node = self.visit(node.node)
    267322        node.args = [self.visit(x) for x in node.args]
     
    271326            node.dstar_args = self.visit(node.dstar_args)
    272327        return node
    273328
    274     def visitLambda(self, node):
     329    def visitClass(self, node):
     330        node.bases = [self.visit(x) for x in node.bases]
    275331        node.code = self.visit(node.code)
    276332        node.filename = '<string>' # workaround for bug in pycodegen
    277333        return node
    278334
     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
    279343    def visitGetattr(self, node):
    280344        node.expr = self.visit(node.expr)
    281345        return node
    282346
     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
    283352    def visitSubscript(self, node):
    284353        node.expr = self.visit(node.expr)
    285354        node.subs = [self.visit(x) for x in node.subs]
    286355        return node
    287356
     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
    288424    # Operators
    289425
    290426    def _visitBoolOp(self, node):
     
    310446        node.expr = self.visit(node.expr)
    311447        return node
    312448    visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp
    313     visitBackquote = _visitUnaryOp
     449    visitBackquote = visitDiscard = _visitUnaryOp
    314450
    315451    def visitIfExp(self, node):
    316452        node.test = self.visit(node.test)
     
    320456
    321457    # Identifiers, Literals and Comprehensions
    322458
    323     def _visitDefault(self, node):
    324         return node
    325     visitAssName = visitConst = visitName = _visitDefault
    326 
    327459    def visitDict(self, node):
    328460        node.items = [(self.visit(k),
    329461                       self.visit(v)) for k, v in node.items]
     
    389521        return node
    390522
    391523
    392 class ExpressionASTTransformer(ASTTransformer):
     524class TemplateASTTransformer(ASTTransformer):
    393525    """Concrete AST transformer that implements the AST transformations needed
    394     for template expressions.
     526    for code embedded in templates.
    395527    """
    396528
    397529    def __init__(self):
     
    406538        return node
    407539
    408540    def visitAssName(self, node):
    409         self.locals[-1].add(node.name)
     541        if self.locals:
     542            self.locals[-1].add(node.name)
    410543        return node
    411544
     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
    412563    def visitGenExpr(self, node):
    413564        self.locals.append(set())
    414565        node = ASTTransformer.visitGenExpr(self, node)