# HG changeset patch # User cmlenz # Date 1187819446 0 # Node ID 9ae986bcba9a1dc40cae0348b33299c6e30676ce # Parent 0d802a7e363002d097746853f34b7c741d7b0513 Simplify implementation of `py:with` directive by compiling to a `Suite`, instead of manually breaking up the statement and compiling each part to an `Expression`. Also, the first line of code in a `Suite` is now stored as the "function name" of the bytecode, so that it shows up in tracebacks. diff --git a/genshi/template/directives.py b/genshi/template/directives.py --- a/genshi/template/directives.py +++ b/genshi/template/directives.py @@ -19,7 +19,7 @@ from genshi.path import Path from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \ EXPR, _apply_directives -from genshi.template.eval import Expression, _parse +from genshi.template.eval import Expression, Suite, _parse __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective', 'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective', @@ -680,30 +680,26 @@ 42 7 52 """ - __slots__ = ['vars'] + __slots__ = ['suite'] def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): Directive.__init__(self, None, template, namespaces, lineno, offset) - self.vars = [] - value = value.strip() try: - ast = _parse(value, 'exec').node - for node in ast.nodes: - if isinstance(node, compiler.ast.Discard): - continue - elif not isinstance(node, compiler.ast.Assign): - raise TemplateSyntaxError('only assignment allowed in ' - 'value of the "with" directive', - template.filepath, lineno, offset) - self.vars.append(([_assignment(n) for n in node.nodes], - Expression(node.expr, template.filepath, - lineno, lookup=template.lookup))) + self.suite = Suite(value, template.filepath, lineno, + lookup=template.lookup) except SyntaxError, err: err.msg += ' in expression "%s" of "%s" directive' % (value, self.tagname) raise TemplateSyntaxError(err, template.filepath, lineno, offset + (err.offset or 0)) + for node in self.suite.ast.node.nodes: + if not isinstance(node, (compiler.ast.Discard, + compiler.ast.Assign)): + raise TemplateSyntaxError('only assignment allowed in value of ' + 'the "with" directive', + template.filepath, lineno, offset) + def attach(cls, template, stream, value, namespaces, pos): if type(value) is dict: value = value.get('vars') @@ -714,10 +710,7 @@ def __call__(self, stream, ctxt, directives): frame = {} ctxt.push(frame) - for targets, expr in self.vars: - value = expr.evaluate(ctxt) - for assign in targets: - assign(frame, value) + self.suite.execute(ctxt) for event in _apply_directives(stream, ctxt, directives): yield event ctxt.pop() diff --git a/genshi/template/eval.py b/genshi/template/eval.py --- a/genshi/template/eval.py +++ b/genshi/template/eval.py @@ -23,6 +23,7 @@ from sets import ImmutableSet as frozenset from sets import Set as set import sys +from textwrap import dedent from genshi.core import Markup from genshi.template.base import TemplateRuntimeError @@ -53,7 +54,8 @@ self.source = source node = _parse(source, mode=self.mode) else: - assert isinstance(source, ast.Node) + assert isinstance(source, ast.Node), \ + 'Expected string or AST node, but got %r' % source self.source = '?' if self.mode == 'eval': node = ast.Expression(source) @@ -360,6 +362,14 @@ def _parse(source, mode='eval'): + source = source.strip() + if mode == 'exec': + lines = [line.expandtabs() for line in source.splitlines()] + first = lines[0] + rest = dedent('\n'.join(lines[1:])).rstrip() + if first.rstrip().endswith(':') and not rest[0].isspace(): + rest = '\n'.join([' %s' % line for line in rest.splitlines()]) + source = '\n'.join([first, rest]) if isinstance(source, unicode): source = '\xef\xbb\xbf' + source.encode('utf-8') return parse(source, mode) @@ -378,10 +388,14 @@ if mode == 'eval': gen = ExpressionCodeGenerator(tree) - name = '' % (repr(source or '?')) + name = '' % (source or '?') else: gen = ModuleCodeGenerator(tree) - name = '' + lines = source.splitlines() + extract = lines[0] + if len(lines) > 1: + extract += ' ...' + name = '' % (extract) gen.optimized = True code = gen.getCode() diff --git a/genshi/template/interpolation.py b/genshi/template/interpolation.py --- a/genshi/template/interpolation.py +++ b/genshi/template/interpolation.py @@ -73,7 +73,7 @@ if chunk: try: expr = Expression(chunk.strip(), pos[0], pos[1], - lookup=lookup) + lookup=lookup) yield EXPR, expr, tuple(pos) except SyntaxError, err: raise TemplateSyntaxError(err, filepath, pos[1], diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -15,7 +15,6 @@ from itertools import chain import sys -from textwrap import dedent from genshi.core import Attrs, Namespace, Stream, StreamEventKind from genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT @@ -197,19 +196,7 @@ raise TemplateSyntaxError('Python code blocks not allowed', self.filepath, *pos[1:]) try: - # As Expat doesn't report whitespace between the PI target - # and the data, we have to jump through some hoops here to - # get correctly indented Python code - # Unfortunately, we'll still probably not get the line - # number quite right - lines = [line.expandtabs() for line in data[1].splitlines()] - first = lines[0] - rest = dedent('\n'.join(lines[1:])).rstrip() - if first.rstrip().endswith(':') and not rest[0].isspace(): - rest = '\n'.join([' ' + line for line - in rest.splitlines()]) - source = '\n'.join([first, rest]) - suite = Suite(source, self.filepath, pos[1], + suite = Suite(data[1], self.filepath, pos[1], lookup=self.lookup) except SyntaxError, err: raise TemplateSyntaxError(err, self.filepath,