Edgewall Software

Ticket #84: python_pi.3.diff

File python_pi.3.diff, 23.2 KB (added by cmlenz, 17 years ago)

Updated patch, candidate for merge

  • genshi/tests/builder.py

     
    4545    def test_stream_as_child(self):
    4646        xml = list(tag.span(XML('<b>Foo</b>')).generate())
    4747        self.assertEqual(5, len(xml))
    48         self.assertEqual((Stream.START, ('span', ()), (None, -1, -1)), xml[0])
    49         self.assertEqual((Stream.START, ('b', ()), (None, 1, 0)), xml[1])
    50         self.assertEqual((Stream.TEXT, 'Foo', (None, 1, 3)), xml[2])
    51         self.assertEqual((Stream.END, 'b', (None, 1, 6)), xml[3])
    52         self.assertEqual((Stream.END, 'span', (None, -1, -1)), xml[4])
     48        self.assertEqual((Stream.START, ('span', ())), xml[0][:2])
     49        self.assertEqual((Stream.START, ('b', ())), xml[1][:2])
     50        self.assertEqual((Stream.TEXT, 'Foo'), xml[2][:2])
     51        self.assertEqual((Stream.END, 'b'), xml[3][:2])
     52        self.assertEqual((Stream.END, 'span'), xml[4][:2])
    5353
    5454
    5555def suite():
  • genshi/path.py

     
    156156        >>> test = Path('child').test()
    157157        >>> for event in xml:
    158158        ...     if test(event, {}, {}):
    159         ...         print event
    160         ('START', (QName(u'child'), Attrs([(QName(u'id'), u'2')])), (None, 1, 34))
     159        ...         print event[0], repr(event[1])
     160        START (QName(u'child'), Attrs([(QName(u'id'), u'2')]))
    161161        """
    162162        paths = [(p, len(p), [0], [], [0] * len(p)) for p in [
    163163            (ignore_context and [_DOTSLASHSLASH] or []) + p for p in self.paths
  • 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
     148    def keys(self):
     149        keys = []
     150        for frame in self.frames:
     151            keys += [key for key in frame if key not in keys]
     152        return keys
     153
     154    def items(self):
     155        return [(key, self.get(key)) for key in self.keys()]
     156
    134157    def push(self, data):
    135158        """Push a new scope on the stack."""
    136159
  • 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

     
    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 ifilter")
     416        data = {}
     417        suite.execute(data)
     418        assert 'ifilter' in data
     419
     420    def test_for(self):
     421        suite = Suite("""x = []
     422for i in range(3):
     423    x.append(i**2)
     424""")
     425        data = {}
     426        suite.execute(data)
     427        self.assertEqual([0, 1, 4], data['x'])
     428
     429    def test_if(self):
     430        suite = Suite("""if foo == 42:
     431    x = True
     432""")
     433        data = {'foo': 42}
     434        suite.execute(data)
     435        self.assertEqual(True, data['x'])
     436
     437    def test_raise(self):
     438        suite = Suite("""raise NotImplementedError""")
     439        self.assertRaises(NotImplementedError, suite.execute, {})
     440
     441    def test_try_except(self):
     442        suite = Suite("""try:
     443    import somemod
     444except ImportError:
     445    somemod = None
     446else:
     447    somemod.dosth()""")
     448        data = {}
     449        suite.execute(data)
     450        self.assertEqual(None, data['somemod'])
     451
     452    def test_finally(self):
     453        suite = Suite("""try:
     454    x = 2
     455finally:
     456    x = None
     457""")
     458        data = {}
     459        suite.execute(data)
     460        self.assertEqual(None, data['x'])
     461
     462    def test_while_break(self):
     463        suite = Suite("""x = 0
     464while x < 5:
     465    x += step
     466    if x == 4:
     467        break
     468""")
     469        data = {'step': 2}
     470        suite.execute(data)
     471        self.assertEqual(4, data['x'])
     472
     473
    385474def suite():
    386475    suite = unittest.TestSuite()
    387476    suite.addTest(doctest.DocTestSuite(Expression.__module__))
    388477    suite.addTest(unittest.makeSuite(ExpressionTestCase, 'test'))
     478    suite.addTest(unittest.makeSuite(SuiteTestCase, 'test'))
    389479    return suite
    390480
    391481if __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.core 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.util import flatten
    2627
    27 __all__ = ['Expression', 'Undefined']
     28if sys.version_info < (2, 4):
     29    _locals = dict
     30else:
     31    _locals = lambda x: x
    2832
     33__all__ = ['Expression', 'Suite', 'Undefined']
    2934
    30 class Expression(object):
     35
     36class Code(object):
     37    """Abstract base class for the `Expression` and `Suite` classes."""
     38    __slots__ = ['source', 'code']
     39
     40    def __init__(self, source, filename=None, lineno=-1):
     41        """Create the code object, either from a string, or from an AST node.
     42       
     43        @param source: either a string containing the source code, or an AST
     44            node
     45        @param filename: the (preferably absolute) name of the file containing
     46            the code
     47        @param lineno: the number of the line on which the code was found
     48        """
     49        if isinstance(source, basestring):
     50            self.source = source
     51            node = _parse(source, mode=self.mode)
     52        else:
     53            assert isinstance(source, ast.Node)
     54            self.source = '?'
     55            if self.mode == 'eval':
     56                node = ast.Expression(source)
     57            else:
     58                node = ast.Module(None, source)
     59
     60        self.code = _compile(node, self.source, mode=self.mode,
     61                             filename=filename, lineno=lineno)
     62
     63    def __eq__(self, other):
     64        return (type(other) == type(self)) and (self.code == other.code)
     65
     66    def __hash__(self):
     67        return hash(self.code)
     68
     69    def __ne__(self, other):
     70        return not self == other
     71
     72    def __repr__(self):
     73        return '%s(%r)' % (self.__class__.__name__, self.source)
     74
     75
     76class Expression(Code):
    3177    """Evaluates Python expressions used in templates.
    3278
    3379    >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
     
    68114    >>> Expression('len(items)').evaluate(data)
    69115    3
    70116    """
    71     __slots__ = ['source', 'code']
     117    __slots__ = []
     118    mode = 'eval'
    72119
    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 
    104120    def evaluate(self, data):
    105121        """Evaluate the expression against the given data dictionary.
    106122       
     
    110126        return eval(self.code, {'data': data,
    111127                                '_lookup_name': _lookup_name,
    112128                                '_lookup_attr': _lookup_attr,
    113                                 '_lookup_item': _lookup_item},
    114                                {'data': data})
     129                                '_lookup_item': _lookup_item}, _locals(data))
    115130
    116131
     132class Suite(Code):
     133    """Executes Python statements used in templates.
     134
     135    >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
     136    >>> Suite('foo = dict.some').execute(data)
     137    >>> data['foo']
     138    'thing'
     139    """
     140    __slots__ = []
     141    mode = 'exec'
     142
     143    def execute(self, data):
     144        """Execute the suite in the given data dictionary.
     145       
     146        @param data: a mapping containing the data to execute in
     147        """
     148        exec self.code in {'data': data,
     149                           '_lookup_name': _lookup_name,
     150                           '_lookup_attr': _lookup_attr,
     151                           '_lookup_item': _lookup_item}, data
     152
     153
    117154class Undefined(object):
    118155    """Represents a reference to an undefined variable.
    119156   
     
    176213        source = '\xef\xbb\xbf' + source.encode('utf-8')
    177214    return parse(source, mode)
    178215
    179 def _compile(node, source=None, filename=None, lineno=-1):
    180     tree = ExpressionASTTransformer().visit(node)
     216def _compile(node, source=None, mode='eval', filename=None, lineno=-1):
     217    tree = TemplateASTTransformer().visit(node)
    181218    if isinstance(filename, unicode):
    182219        # unicode file names not allowed for code objects
    183220        filename = filename.encode('utf-8', 'replace')
     
    187224    if lineno <= 0:
    188225        lineno = 1
    189226
    190     gen = ExpressionCodeGenerator(tree)
     227    if mode == 'eval':
     228        gen = ExpressionCodeGenerator(tree)
     229        name = '<Expression %s>' % (repr(source or '?').replace("'", '"'))
     230    else:
     231        gen = ModuleCodeGenerator(tree)
     232        name = '<Suite>'
    191233    gen.optimized = True
    192234    code = gen.getCode()
    193235
     
    195237    # clone the code object while adjusting the line number
    196238    return new.code(0, code.co_nlocals, code.co_stacksize,
    197239                    code.co_flags | 0x0040, code.co_code, code.co_consts,
    198                     code.co_names, code.co_varnames, filename,
    199                     '<Expression %s>' % (repr(source or '?').replace("'", '"')),
    200                     lineno, code.co_lnotab, (), ())
     240                    code.co_names, code.co_varnames, filename, name, lineno,
     241                    code.co_lnotab, (), ())
    201242
    202243BUILTINS = __builtin__.__dict__.copy()
    203244BUILTINS['Undefined'] = Undefined
     
    231272        key = key[0]
    232273    try:
    233274        return obj[key]
    234     except (KeyError, IndexError, TypeError), e:
     275    except (AttributeError, KeyError, IndexError, TypeError), e:
    235276        if isinstance(key, basestring):
    236277            val = getattr(obj, key, _UNDEF)
    237278            if val is _UNDEF:
     
    249290    _visitors = {}
    250291
    251292    def visit(self, node):
     293        if node is None:
     294            return None
    252295        v = self._visitors.get(node.__class__)
    253296        if not v:
    254             v = getattr(self, 'visit%s' % node.__class__.__name__)
     297            v = getattr(self.__class__, 'visit%s' % node.__class__.__name__,
     298                        self.__class__._visitDefault)
    255299            self._visitors[node.__class__] = v
    256         return v(node)
     300        return v(self, node)
    257301
     302    def _visitDefault(self, node):
     303        return node
     304
    258305    def visitExpression(self, node):
    259306        node.node = self.visit(node.node)
    260307        return node
    261308
    262     # Functions & Accessors
     309    def visitModule(self, node):
     310        node.node = self.visit(node.node)
     311        return node
    263312
     313    def visitStmt(self, node):
     314        node.nodes = [self.visit(x) for x in node.nodes]
     315        return node
     316
     317    # Classes, Functions & Accessors
     318
    264319    def visitCallFunc(self, node):
    265320        node.node = self.visit(node.node)
    266321        node.args = [self.visit(x) for x in node.args]
     
    270325            node.dstar_args = self.visit(node.dstar_args)
    271326        return node
    272327
    273     def visitLambda(self, node):
     328    def visitClass(self, node):
     329        node.bases = [self.visit(x) for x in node.bases]
    274330        node.code = self.visit(node.code)
    275331        node.filename = '<string>' # workaround for bug in pycodegen
    276332        return node
    277333
     334    def visitFunction(self, node):
     335        if hasattr(node, 'decorators'):
     336            node.decorators = self.visit(node.decorators)
     337        node.defaults = [self.visit(x) for x in node.defaults]
     338        node.code = self.visit(node.code)
     339        node.filename = '<string>' # workaround for bug in pycodegen
     340        return node
     341
    278342    def visitGetattr(self, node):
    279343        node.expr = self.visit(node.expr)
    280344        return node
    281345
     346    def visitLambda(self, node):
     347        node.code = self.visit(node.code)
     348        node.filename = '<string>' # workaround for bug in pycodegen
     349        return node
     350
    282351    def visitSubscript(self, node):
    283352        node.expr = self.visit(node.expr)
    284353        node.subs = [self.visit(x) for x in node.subs]
    285354        return node
    286355
     356    # Statements
     357
     358    def visitAssert(self, node):
     359        node.test = self.visit(node.test)
     360        node.fail = self.visit(node.fail)
     361        return node
     362
     363    def visitAssign(self, node):
     364        node.nodes = [self.visit(x) for x in node.nodes]
     365        node.expr = self.visit(node.expr)
     366        return node
     367
     368    def visitDecorators(self, node):
     369        node.nodes = [self.visit(x) for x in node.nodes]
     370        return node
     371
     372    def visitFor(self, node):
     373        node.assign = self.visit(node.assign)
     374        node.list = self.visit(node.list)
     375        node.body = self.visit(node.body)
     376        node.else_ = self.visit(node.else_)
     377        return node
     378
     379    def visitIf(self, node):
     380        node.tests = [self.visit(x) for x in node.tests]
     381        node.else_ = self.visit(node.else_)
     382        return node
     383
     384    def _visitPrint(self, node):
     385        node.nodes = [self.visit(x) for x in node.nodes]
     386        node.dest = self.visit(node.dest)
     387        return node
     388    visitPrint = visitPrintnl = _visitPrint
     389
     390    def visitRaise(self, node):
     391        node.expr1 = self.visit(node.expr1)
     392        node.expr2 = self.visit(node.expr2)
     393        node.expr3 = self.visit(node.expr3)
     394        return node
     395
     396    def visitTryExcept(self, node):
     397        node.body = self.visit(node.body)
     398        node.handlers = self.visit(node.handlers)
     399        node.else_ = self.visit(node.else_)
     400        return node
     401
     402    def visitTryFinally(self, node):
     403        node.body = self.visit(node.body)
     404        node.final = self.visit(node.final)
     405        return node
     406
     407    def visitWhile(self, node):
     408        node.test = self.visit(node.test)
     409        node.body = self.visit(node.body)
     410        node.else_ = self.visit(node.else_)
     411        return node
     412
     413    def visitWith(self, node):
     414        node.expr = self.visit(node.expr)
     415        node.vars = [self.visit(x) for x in node.vars]
     416        node.body = self.visit(node.body)
     417        return node
     418
     419    def visitYield(self, node):
     420        node.value = self.visit(node.value)
     421        return node
     422
    287423    # Operators
    288424
    289425    def _visitBoolOp(self, node):
     
    308444        node.expr = self.visit(node.expr)
    309445        return node
    310446    visitUnaryAdd = visitUnarySub = visitNot = visitInvert = _visitUnaryOp
    311     visitBackquote = _visitUnaryOp
     447    visitBackquote = visitDiscard = _visitUnaryOp
    312448
    313449    # Identifiers, Literals and Comprehensions
    314450
    315     def _visitDefault(self, node):
    316         return node
    317     visitAssName = visitConst = visitName = _visitDefault
    318 
    319451    def visitDict(self, node):
    320452        node.items = [(self.visit(k),
    321453                       self.visit(v)) for k, v in node.items]
     
    381513        return node
    382514
    383515
    384 class ExpressionASTTransformer(ASTTransformer):
     516class TemplateASTTransformer(ASTTransformer):
    385517    """Concrete AST transformer that implements the AST transformations needed
    386     for template expressions.
     518    for code embedded in templates.
    387519    """
    388520
    389521    def __init__(self):
     
    398530        return node
    399531
    400532    def visitAssName(self, node):
    401         self.locals[-1].add(node.name)
     533        if self.locals:
     534            self.locals[-1].add(node.name)
    402535        return node
    403536
     537    def visitClass(self, node):
     538        self.locals.append(set())
     539        node = ASTTransformer.visitClass(self, node)
     540        self.locals.pop()
     541        return node
     542
     543    def visitFor(self, node):
     544        self.locals.append(set())
     545        node = ASTTransformer.visitFor(self, node)
     546        self.locals.pop()
     547        return node
     548
     549    def visitFunction(self, node):
     550        self.locals.append(set(node.argnames))
     551        node = ASTTransformer.visitFunction(self, node)
     552        self.locals.pop()
     553        return node
     554
    404555    def visitGenExpr(self, node):
    405556        self.locals.append(set())
    406557        node = ASTTransformer.visitGenExpr(self, node)