Edgewall Software

Ticket #84: python_pi.5.diff

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