| # -*- coding: utf-8 -*- |
| """ |
| jinja.translators.python |
| ~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| This module translates a jinja ast into python code. |
| |
| :copyright: 2006 by Armin Ronacher. |
| :license: BSD, see LICENSE for more details. |
| """ |
| from compiler import ast |
| from jinja import nodes |
| from jinja.parser import Parser |
| from jinja.datastructure import Context |
| from jinja.exceptions import TemplateSyntaxError |
| from jinja.translators import Translator |
| |
| |
| class Template(object): |
| """ |
| Represents a finished template. |
| """ |
| |
| def __init__(self, environment, generate_func): |
| self.environment = environment |
| self.generate_func = generate_func |
| |
| def render(self, *args, **kwargs): |
| """ |
| Render a template. |
| """ |
| result = [] |
| ctx = Context(self.environment, *args, **kwargs) |
| self.generate_func(ctx, result.append) |
| return u''.join(result) |
| |
| |
| class PythonTranslator(Translator): |
| """ |
| Pass this translator a ast tree to get valid python code. |
| """ |
| |
| def __init__(self, environment, node): |
| self.environment = environment |
| self.node = node |
| |
| self.constants = { |
| 'true': 'True', |
| 'false': 'False', |
| 'none': 'None', |
| 'undefined': 'Undefined' |
| } |
| |
| self.handlers = { |
| # jinja nodes |
| nodes.Template: self.handle_template, |
| nodes.Text: self.handle_template_text, |
| nodes.NodeList: self.handle_node_list, |
| nodes.ForLoop: self.handle_for_loop, |
| nodes.IfCondition: self.handle_if_condition, |
| nodes.Cycle: self.handle_cycle, |
| nodes.Print: self.handle_print, |
| nodes.Macro: self.handle_macro, |
| nodes.Block: self.handle_block, |
| # used python nodes |
| ast.Name: self.handle_name, |
| ast.AssName: self.handle_name, |
| ast.Compare: self.handle_compare, |
| ast.Const: self.handle_const, |
| ast.Subscript: self.handle_subscript, |
| ast.Getattr: self.handle_getattr, |
| ast.AssTuple: self.handle_ass_tuple, |
| ast.Bitor: self.handle_bitor, |
| ast.CallFunc: self.handle_call_func, |
| ast.Add: self.handle_add, |
| ast.Sub: self.handle_sub, |
| ast.Div: self.handle_div, |
| ast.Mul: self.handle_mul, |
| ast.Mod: self.handle_mod, |
| ast.UnaryAdd: self.handle_unary_add, |
| ast.UnarySub: self.handle_unary_sub, |
| ast.Power: self.handle_power, |
| ast.Dict: self.handle_dict, |
| ast.List: self.handle_list, |
| ast.Tuple: self.handle_list, |
| ast.And: self.handle_and, |
| ast.Or: self.handle_or, |
| ast.Not: self.handle_not, |
| ast.Slice: self.handle_slice, |
| ast.Sliceobj: self.handle_sliceobj |
| } |
| |
| self.unsupported = { |
| ast.ListComp: 'list comprehensions', |
| ast.From: 'imports', |
| ast.Import: 'imports', |
| } |
| if hasattr(ast, 'GenExpr'): |
| self.unsupported.update({ |
| ast.GenExpr: 'generator expressions' |
| }) |
| |
| # -- public methods |
| |
| def process(environment, node): |
| translator = PythonTranslator(environment, node) |
| source = translator.translate() |
| ns = {} |
| exec source in ns |
| return Template(environment, ns['generate']) |
| process = staticmethod(process) |
| |
| # -- private methods |
| |
| def indent(self, text): |
| """ |
| Indent the current text. |
| """ |
| return (' ' * (self.indention * 4)) + text |
| |
| def handle_node(self, node): |
| """ |
| Handle one node |
| """ |
| if node.__class__ in self.handlers: |
| out = self.handlers[node.__class__](node) |
| elif node.__class__ in self.unsupported: |
| raise TemplateSyntaxError('unsupported syntax element %r found.' |
| % self.unsupported[node.__class__], |
| node.lineno) |
| else: |
| raise AssertionError('unhandled node %r' % node.__class__) |
| return out |
| |
| # -- jinja nodes |
| |
| def handle_template(self, node): |
| """ |
| Handle the overall template node. This node is the first node and ensures |
| that we get the bootstrapping code. It also knows about inheritance |
| information. It only occours as outer node, never in the tree itself. |
| |
| Nevertheless we call indent here to simplify futur changes. |
| """ |
| # if there is a parent template we parse the parent template and |
| # update the blocks there. Once this is done we drop the current |
| # template in favor of the new one. Do that until we found the |
| # root template. |
| while node.extends is not None: |
| tmpl = self.environment.loader.parse(node.extends.template, |
| node.filename) |
| for block in tmpl.blocks.itervalues(): |
| if block.name in node.blocks: |
| block.replace(node.blocks[block.name]) |
| node = tmpl |
| |
| lines = [self.indent( |
| 'from jinja.datastructure import Undefined, LoopContext, CycleContext\n' |
| 'def generate(context, write, write_var=None):\n' |
| ' environment = context.environment\n' |
| ' if write_var is None:\n' |
| ' write_var = lambda x: write(environment.finish_var(x))' |
| )] |
| write = lambda x: lines.append(self.indent(x)) |
| self.indention += 1 |
| lines.append(self.handle_node_list(node)) |
| |
| return '\n'.join(lines) |
| |
| def handle_template_text(self, node): |
| """ |
| Handle data around nodes. |
| """ |
| return self.indent('write(%r)' % node.text) |
| |
| def handle_node_list(self, node): |
| """ |
| In some situations we might have a node list. It's just |
| a collection of multiple statements. |
| """ |
| buf = [] |
| for n in node: |
| buf.append(self.handle_node(n)) |
| return '\n'.join(buf) |
| |
| def handle_for_loop(self, node): |
| """ |
| Handle a for loop. Pretty basic, just that we give the else |
| clause a different behavior. |
| """ |
| buf = [] |
| write = lambda x: buf.append(self.indent(x)) |
| write('context.push()') |
| |
| # recursive loops |
| if node.recursive: |
| write('def forloop(seq):') |
| self.indention += 1 |
| write('context[\'loop\'].push(seq)') |
| write('for %s in context[\'loop\']:' % |
| self.handle_node(node.item), |
| ) |
| |
| # simple loops |
| else: |
| write('context[\'loop\'] = LoopContext(%s, context[\'loop\'], None)' % |
| self.handle_node(node.seq)) |
| write('for %s in context[\'loop\']:' % |
| self.handle_node(node.item) |
| ) |
| |
| # handle real loop code |
| self.indention += 1 |
| buf.append(self.handle_node(node.body)) |
| self.indention -= 1 |
| |
| # else part of loop |
| if node.else_ is not None: |
| write('if not context[\'loop\'].iterated:') |
| self.indention += 1 |
| buf.append(self.handle_node(node.else_)) |
| self.indention -= 1 |
| |
| # call recursive for loop! |
| if node.recursive: |
| write('context[\'loop\'].pop()') |
| self.indention -= 1 |
| write('context[\'loop\'] = LoopContext(None, context[\'loop\'], forloop)') |
| write('forloop(%s)' % self.handle_node(node.seq)) |
| |
| write('context.pop()') |
| return '\n'.join(buf) |
| |
| def handle_if_condition(self, node): |
| """ |
| Handle an if condition node. |
| """ |
| buf = [] |
| write = lambda x: buf.append(self.indent(x)) |
| for idx, (test, body) in enumerate(node.tests): |
| write('%sif %s:' % ( |
| idx and 'el' or '', |
| self.handle_node(test) |
| )) |
| self.indention += 1 |
| buf.append(self.handle_node(body)) |
| self.indention -= 1 |
| if node.else_ is not None: |
| write('else:') |
| self.indention += 1 |
| buf.append(self.handle_node(node.else_)) |
| self.indention -= 1 |
| return '\n'.join(buf) |
| |
| def handle_cycle(self, node): |
| """ |
| Handle the cycle tag. |
| """ |
| name = '::cycle_%x' % self.last_cycle_id |
| self.last_cycle_id += 1 |
| buf = [] |
| write = lambda x: buf.append(self.indent(x)) |
| |
| write('if not %r in context.current:' % name) |
| self.indention += 1 |
| if node.seq.__class__ in (ast.Tuple, ast.List): |
| write('context.current[%r] = CycleContext([%s])' % ( |
| name, |
| ', '.join([self.handle_node(n) for n in node.seq.nodes]) |
| )) |
| hardcoded = True |
| else: |
| write('context.current[%r] = CycleContext()' % name) |
| hardcoded = False |
| self.indention -= 1 |
| |
| if hardcoded: |
| write('write_var(context.current[%r].cycle())' % name) |
| else: |
| write('write_var(context.current[%r].cycle(%s))' % ( |
| name, |
| self.handle_node(node.seq) |
| )) |
| |
| return '\n'.join(buf) |
| |
| def handle_print(self, node): |
| """ |
| Handle a print statement. |
| """ |
| return self.indent('write_var(%s)' % self.handle_node(node.variable)) |
| |
| def handle_macro(self, node): |
| """ |
| Handle macro declarations. |
| """ |
| buf = [] |
| |
| args = [] |
| for name, n in node.arguments: |
| if n is None: |
| args.append('%s=Undefined' % name) |
| else: |
| args.append('%s=%s' % (name, self.handle_node(n))) |
| buf.append(self.indent('def macro(%s):' % ', '.join(args))) |
| self.indention += 1 |
| buf.append(self.handle_node(node.body)) |
| self.indention -= 1 |
| buf.append(self.indent('context[%r] = macro' % node.name)) |
| |
| return '\n'.join(buf) |
| |
| def handle_block(self, node): |
| """ |
| Handle blocks in the sourcecode. We only use them to |
| call the current block implementation that is stored somewhere |
| else. |
| """ |
| rv = self.handle_node(node.body) |
| if not rv: |
| return self.indent('# empty block from %r, line %s' % ( |
| node.filename or '?', |
| node.lineno |
| )) |
| |
| buf = [] |
| write = lambda x: buf.append(self.indent(x)) |
| |
| write('# block from %r, line %s' % ( |
| node.filename or '?', |
| node.lineno |
| )) |
| write('context.push()') |
| buf.append(self.handle_node(node.body)) |
| write('context.pop()') |
| buf.append(self.indent('# end of block')) |
| return '\n'.join(buf) |
| |
| # -- python nodes |
| |
| def handle_name(self, node): |
| """ |
| Handle name assignments and name retreivement. |
| """ |
| if node.name in self.constants: |
| return self.constants[node.name] |
| return 'context[%r]' % node.name |
| |
| def handle_compare(self, node): |
| """ |
| Any sort of comparison |
| """ |
| # the semantic for the is operator is different. |
| # for jinja the is operator performs tests and must |
| # be the only operator |
| if node.ops[0][0] == 'is': |
| if len(node.ops) > 1: |
| raise TemplateSyntaxError('is operator must not be chained', |
| node.lineno) |
| elif node.ops[0][1].__class__ is not ast.Name: |
| raise TemplateSyntaxError('is operator requires a test name', |
| ' as operand', node.lineno) |
| return 'environment.perform_test(%s, context, %r)' % ( |
| self.handle_node(node.expr), |
| node.ops[0][1].name |
| ) |
| |
| # normal operators |
| buf = [] |
| buf.append(self.handle_node(node.expr)) |
| for op, n in node.ops: |
| if op == 'is': |
| raise TemplateSyntaxError('is operator must not be chained', |
| node.lineno) |
| buf.append(op) |
| buf.append(self.handle_node(n)) |
| return ' '.join(buf) |
| |
| def handle_const(self, node): |
| """ |
| Constant values in expressions. |
| """ |
| return repr(node.value) |
| |
| def handle_subscript(self, node): |
| """ |
| Handle variable based attribute access foo['bar']. |
| """ |
| if len(node.subs) != 1: |
| raise TemplateSyntaxError('attribute access requires one argument', |
| node.lineno) |
| assert node.flags != 'OP_DELETE', 'wtf? do we support that?' |
| if node.subs[0].__class__ is ast.Sliceobj: |
| return '%s[%s]' % ( |
| self.handle_node(node.expr), |
| self.handle_node(node.subs[0]) |
| ) |
| return 'environment.get_attribute(%s, %s)' % ( |
| self.handle_node(node.expr), |
| self.handle_node(node.subs[0]) |
| ) |
| |
| def handle_getattr(self, node): |
| """ |
| Handle hardcoded attribute access. foo.bar |
| """ |
| return 'environment.get_attribute(%s, %r)' % ( |
| self.handle_node(node.expr), |
| node.attrname |
| ) |
| |
| def handle_ass_tuple(self, node): |
| """ |
| Tuple unpacking loops. |
| """ |
| return '(%s)' % ', '.join([self.handle_node(n) for n in node.nodes]) |
| |
| def handle_bitor(self, node): |
| """ |
| We use the pipe operator for filtering. |
| """ |
| filters = [] |
| for n in node.nodes[1:]: |
| if n.__class__ is ast.CallFunc: |
| if n.node.__class__ is not ast.Name: |
| raise TemplateSyntaxError('invalid filter. filter must ' |
| 'be a hardcoded function name ' |
| 'from the filter namespace', |
| n.lineno) |
| args = [] |
| for arg in n.args: |
| if arg.__class__ is ast.Keyword: |
| raise TemplateSyntaxError('keyword arguments for ' |
| 'filters are not supported.', |
| n.lineno) |
| args.append(self.handle_node(arg)) |
| if n.star_args is not None or n.dstar_args is not None: |
| raise TemplateSynaxError('*args / **kwargs is not supported ' |
| 'for filters', n.lineno) |
| if args: |
| args = ', ' + ', '.join(args) |
| filters.append('environment.prepare_filter(%r%s)' % ( |
| n.node.name, |
| args or '' |
| )) |
| elif n.__class__ is ast.Name: |
| filters.append('environment.prepare_filter(%r)' % |
| n.name) |
| else: |
| raise TemplateSyntaxError('invalid filter. filter must be a ' |
| 'hardcoded function name from the ' |
| 'filter namespace', |
| n.lineno) |
| return 'environment.apply_filters(%s, context, [%s])' % ( |
| self.handle_node(node.nodes[0]), |
| ', '.join(filters) |
| ) |
| |
| def handle_call_func(self, node): |
| """ |
| Handle function calls. |
| """ |
| args = [] |
| kwargs = {} |
| star_args = dstar_args = None |
| if node.star_args is not None: |
| star_args = self.handle_node(node.star_args) |
| if node.dstar_args is not None: |
| dstar_args = self.handle_node(node.dstar_args) |
| for arg in node.args: |
| if arg.__class__ is ast.Keyword: |
| kwargs[arg.name] = self.handle_node(arg.expr) |
| else: |
| args.append(self.handle_node(arg)) |
| return 'environment.call_function(%s, [%s], {%s}, %s, %s)' % ( |
| self.handle_node(node.node), |
| ', '.join(args), |
| ', '.join(['%r: %s' % i for i in kwargs.iteritems()]), |
| star_args, |
| dstar_args |
| ) |
| |
| def handle_add(self, node): |
| """ |
| Add two items. |
| """ |
| return '(%s + %s)' % ( |
| self.handle_node(node.left), |
| self.handle_node(node.right) |
| ) |
| |
| def handle_sub(self, node): |
| """ |
| Sub two items. |
| """ |
| return '(%s - %s)' % ( |
| self.handle_node(node.left), |
| self.handle_node(node.right) |
| ) |
| |
| def handle_div(self, node): |
| """ |
| Divide two items. |
| """ |
| return '(%s / %s)' % ( |
| self.handle_node(node.left), |
| self.handle_node(node.right) |
| ) |
| |
| def handle_mul(self, node): |
| """ |
| Multiply two items. |
| """ |
| return '(%s * %s)' % ( |
| self.handle_node(node.left), |
| self.handle_node(node.right) |
| ) |
| |
| def handle_mod(self, node): |
| """ |
| Apply modulo. |
| """ |
| return '(%s %% %s)' % ( |
| self.handle_node(node.left), |
| self.handle_node(node.right) |
| ) |
| |
| def handle_unary_add(self, node): |
| """ |
| One of the more or less unused nodes. |
| """ |
| return '(+%s)' % self.handle_node(node.expr) |
| |
| def handle_unary_sub(self, node): |
| """ |
| Make a number negative. |
| """ |
| return '(-%s)' % self.handle_node(node.expr) |
| |
| def handle_power(self, node): |
| """ |
| handle foo**bar |
| """ |
| return '(%s**%s)' % ( |
| self.handle_node(node.left), |
| self.handle_node(node.right) |
| ) |
| |
| def handle_dict(self, node): |
| """ |
| Dict constructor syntax. |
| """ |
| return '{%s}' % ', '.join([ |
| '%s: %s' % ( |
| self.handle_node(key), |
| self.handle_node(value) |
| ) for key, value in node.items |
| ]) |
| |
| def handle_list(self, node): |
| """ |
| We don't know tuples, tuples are lists for jinja. |
| """ |
| return '[%s]' % ', '.join([ |
| self.handle_node(n) for n in node.nodes |
| ]) |
| |
| def handle_and(self, node): |
| """ |
| Handle foo and bar. |
| """ |
| return ' and '.join([ |
| self.handle_node(n) for n in node.nodes |
| ]) |
| |
| def handle_or(self, node): |
| """ |
| handle foo or bar. |
| """ |
| return ' or '.join([ |
| self.handle_node(n) for n in self.nodse |
| ]) |
| |
| def handle_not(self, node): |
| """ |
| handle not operator. |
| """ |
| return 'not %s' % self.handle_node(node.expr) |
| |
| def handle_slice(self, node): |
| """ |
| Slice access. |
| """ |
| if node.lower is None: |
| lower = '' |
| else: |
| lower = self.handle_node(node.lower) |
| if node.upper is None: |
| upper = '' |
| else: |
| upper = self.handle_node(node.upper) |
| assert node.flags != 'OP_DELETE', 'wtf? shouldn\'t happen' |
| return '%s[%s:%s]' % ( |
| self.handle_node(node.expr), |
| lower, |
| upper |
| ) |
| |
| def handle_sliceobj(self, node): |
| """ |
| Extended Slice access. |
| """ |
| args = [] |
| for n in node.nodes: |
| args.append(self.handle_node(n)) |
| return '[%s]' % ':'.join(args) |
| |
| def reset(self): |
| self.indention = 0 |
| self.last_cycle_id = 0 |
| |
| def translate(self): |
| self.reset() |
| return self.handle_node(self.node) |