import sys from ast import literal_eval from itertools import islice, chain from jinja2 import nodes from jinja2._compat import text_type from jinja2.compiler import CodeGenerator, has_safe_repr from jinja2.environment import Environment, Template from jinja2.utils import concat, escape def native_concat(nodes): """Return a native Python type from the list of compiled nodes. If the result is a single node, its value is returned. Otherwise, the nodes are concatenated as strings. If the result can be parsed with :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the string is returned. """ head = list(islice(nodes, 2)) if not head: return None if len(head) == 1: out = head[0] else: out = u''.join([text_type(v) for v in chain(head, nodes)]) try: return literal_eval(out) except (ValueError, SyntaxError, MemoryError): return out class NativeCodeGenerator(CodeGenerator): """A code generator which avoids injecting ``to_string()`` calls around the internal code Jinja uses to render templates. """ def visit_Output(self, node, frame): """Same as :meth:`CodeGenerator.visit_Output`, but do not call ``to_string`` on output nodes in generated code. """ if self.has_known_extends and frame.require_output_check: return finalize = self.environment.finalize finalize_context = getattr(finalize, 'contextfunction', False) finalize_eval = getattr(finalize, 'evalcontextfunction', False) finalize_env = getattr(finalize, 'environmentfunction', False) if finalize is not None: if finalize_context or finalize_eval: const_finalize = None elif finalize_env: def const_finalize(x): return finalize(self.environment, x) else: const_finalize = finalize else: def const_finalize(x): return x # If we are inside a frame that requires output checking, we do so. outdent_later = False if frame.require_output_check: self.writeline('if parent_template is None:') self.indent() outdent_later = True # Try to evaluate as many chunks as possible into a static string at # compile time. body = [] for child in node.nodes: try: if const_finalize is None: raise nodes.Impossible() const = child.as_const(frame.eval_ctx) if not has_safe_repr(const): raise nodes.Impossible() except nodes.Impossible: body.append(child) continue # the frame can't be volatile here, because otherwise the as_const # function would raise an Impossible exception at that point try: if frame.eval_ctx.autoescape: if hasattr(const, '__html__'): const = const.__html__() else: const = escape(const) const = const_finalize(const) except Exception: # if something goes wrong here we evaluate the node at runtime # for easier debugging body.append(child) continue if body and isinstance(body[-1], list): body[-1].append(const) else: body.append([const]) # if we have less than 3 nodes or a buffer we yield or extend/append if len(body) < 3 or frame.buffer is not None: if frame.buffer is not None: # for one item we append, for more we extend if len(body) == 1: self.writeline('%s.append(' % frame.buffer) else: self.writeline('%s.extend((' % frame.buffer) self.indent() for item in body: if isinstance(item, list): val = repr(native_concat(item)) if frame.buffer is None: self.writeline('yield ' + val) else: self.writeline(val + ',') else: if frame.buffer is None: self.writeline('yield ', item) else: self.newline(item) close = 0 if finalize is not None: self.write('environment.finalize(') if finalize_context: self.write('context, ') close += 1 self.visit(item, frame) if close > 0: self.write(')' * close) if frame.buffer is not None: self.write(',') if frame.buffer is not None: # close the open parentheses self.outdent() self.writeline(len(body) == 1 and ')' or '))') # otherwise we create a format string as this is faster in that case else: format = [] arguments = [] for item in body: if isinstance(item, list): format.append(native_concat(item).replace('%', '%%')) else: format.append('%s') arguments.append(item) self.writeline('yield ') self.write(repr(concat(format)) + ' % (') self.indent() for argument in arguments: self.newline(argument) close = 0 if finalize is not None: self.write('environment.finalize(') if finalize_context: self.write('context, ') elif finalize_eval: self.write('context.eval_ctx, ') elif finalize_env: self.write('environment, ') close += 1 self.visit(argument, frame) self.write(')' * close + ', ') self.outdent() self.writeline(')') if outdent_later: self.outdent() class NativeTemplate(Template): def render(self, *args, **kwargs): """Render the template to produce a native Python type. If the result is a single node, its value is returned. Otherwise, the nodes are concatenated as strings. If the result can be parsed with :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the string is returned. """ vars = dict(*args, **kwargs) try: return native_concat(self.root_render_func(self.new_context(vars))) except Exception: exc_info = sys.exc_info() return self.environment.handle_exception(exc_info, True) class NativeEnvironment(Environment): """An environment that renders templates to native Python types.""" code_generator_class = NativeCodeGenerator template_class = NativeTemplate