# -*- coding: utf-8 -*- """ jinja.translators.python ~~~~~~~~~~~~~~~~~~~~~~~~ This module translates a jinja ast into python code. This translator tries hard to keep Jinja sandboxed. All security relevant calls are wrapped by methods defined in the environment. This affects: - method calls - attribute access - name resolution It also adds debug symbols used by the traceback toolkit implemented in `jinja.utils`. Implementation Details ====================== It might sound strange but the translator tries to keep the generated code readable as much as possible. This simplifies debugging the Jinja core a lot. The additional processing overhead is just relevant for the translation process, the additional comments and whitespace won't appear in the saved bytecode. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import re import sys from jinja import nodes from jinja.nodes import get_nodes from jinja.parser import Parser from jinja.exceptions import TemplateSyntaxError from jinja.translators import Translator from jinja.datastructure import TemplateStream from jinja.utils import set, capture_generator #: regular expression for the debug symbols _debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P.*?), ' r'lineno=(?P\d+)\)$') # For Python versions without generator exit exceptions try: GeneratorExit = GeneratorExit except NameError: class GeneratorExit(Exception): pass # For Pythons without conditional expressions try: exec '0 if 0 else 0' have_conditional_expr = True except SyntaxError: have_conditional_expr = False class Template(object): """ Represents a finished template. """ def __init__(self, environment, code): self.environment = environment self.code = code self.generate_func = None def dump(self, stream=None): """Dump the template into python bytecode.""" if stream is not None: from marshal import dump dump(self.code, stream) else: from marshal import dumps return dumps(self.code) def load(environment, data): """Load the template from python bytecode.""" if isinstance(data, basestring): from marshal import loads code = loads(data) else: from marshal import load code = load(data) return Template(environment, code) load = staticmethod(load) def render(self, *args, **kwargs): """Render a template.""" __traceback_hide__ = True ctx = self._prepare(*args, **kwargs) try: return capture_generator(self.generate_func(ctx)) except: self._debug(ctx, *sys.exc_info()) def stream(self, *args, **kwargs): """Render a template as stream.""" def proxy(ctx): try: for item in self.generate_func(ctx): yield item except GeneratorExit: return except: self._debug(ctx, *sys.exc_info()) return TemplateStream(proxy(self._prepare(*args, **kwargs))) def _prepare(self, *args, **kwargs): """Prepare the template execution.""" # if there is no generation function we execute the code # in a new namespace and save the generation function and # debug information. env = self.environment if self.generate_func is None: ns = {'environment': env} exec self.code in ns self.generate_func = ns['generate'] return env.context_class(env, *args, **kwargs) def _debug(self, ctx, exc_type, exc_value, traceback): """Debugging Helper""" # just modify traceback if we have that feature enabled from traceback import print_exception print_exception(exc_type, exc_value, traceback) if self.environment.friendly_traceback: # hook the debugger in from jinja.debugger import translate_exception exc_type, exc_value, traceback = translate_exception( self, ctx, exc_type, exc_value, traceback) print_exception(exc_type, exc_value, traceback) raise exc_type, exc_value, traceback class PythonTranslator(Translator): """ Pass this translator a ast tree to get valid python code. """ def __init__(self, environment, node, source): self.environment = environment self.loader = environment.loader.get_controlled_loader() self.node = node self.source = source self.closed = False #: current level of indention self.indention = 0 #: each {% cycle %} tag has a unique ID which increments #: automatically for each tag. self.last_cycle_id = 0 #: set of used shortcuts jinja has to make local automatically self.used_shortcuts = set(['undefined_singleton']) #: set of used datastructures jinja has to import self.used_data_structures = set() #: set of used utils jinja has to import self.used_utils = set() #: flags for runtime error self.require_runtime_error = False #: do wee need a "set" object? self.need_set_import = False #: flag for regular expressions self.compiled_regular_expressions = {} #: bind the nodes to the callback functions. There are #: some missing! A few are specified in the `unhandled` #: mapping in order to disallow their usage, some of them #: will not appear in the jinja parser output because #: they are filtered out. self.handlers = { # block 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.Call: self.handle_call, nodes.Set: self.handle_set, nodes.Filter: self.handle_filter, nodes.Block: self.handle_block, nodes.Include: self.handle_include, nodes.Trans: self.handle_trans, # expression nodes nodes.NameExpression: self.handle_name, nodes.CompareExpression: self.handle_compare, nodes.TestExpression: self.handle_test, nodes.ConstantExpression: self.handle_const, nodes.RegexExpression: self.handle_regex, nodes.SubscriptExpression: self.handle_subscript, nodes.FilterExpression: self.handle_filter_expr, nodes.CallExpression: self.handle_call_expr, nodes.AddExpression: self.handle_add, nodes.SubExpression: self.handle_sub, nodes.ConcatExpression: self.handle_concat, nodes.DivExpression: self.handle_div, nodes.FloorDivExpression: self.handle_floor_div, nodes.MulExpression: self.handle_mul, nodes.ModExpression: self.handle_mod, nodes.PosExpression: self.handle_pos, nodes.NegExpression: self.handle_neg, nodes.PowExpression: self.handle_pow, nodes.DictExpression: self.handle_dict, nodes.SetExpression: self.handle_set_expr, nodes.ListExpression: self.handle_list, nodes.TupleExpression: self.handle_tuple, nodes.UndefinedExpression: self.handle_undefined, nodes.AndExpression: self.handle_and, nodes.OrExpression: self.handle_or, nodes.NotExpression: self.handle_not, nodes.SliceExpression: self.handle_slice, nodes.ConditionalExpression: self.handle_conditional_expr } # -- public methods def process(environment, node, source=None): """ The only public method. Creates a translator instance, translates the code and returns it in form of an `Template` instance. """ translator = PythonTranslator(environment, node, source) filename = node.filename or '