Home | History | Annotate | Download | only in jinja2
      1 import sys
      2 from ast import literal_eval
      3 from itertools import islice, chain
      4 from jinja2 import nodes
      5 from jinja2._compat import text_type
      6 from jinja2.compiler import CodeGenerator, has_safe_repr
      7 from jinja2.environment import Environment, Template
      8 from jinja2.utils import concat, escape
      9 
     10 
     11 def native_concat(nodes):
     12     """Return a native Python type from the list of compiled nodes. If the
     13     result is a single node, its value is returned. Otherwise, the nodes are
     14     concatenated as strings. If the result can be parsed with
     15     :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
     16     string is returned.
     17     """
     18     head = list(islice(nodes, 2))
     19 
     20     if not head:
     21         return None
     22 
     23     if len(head) == 1:
     24         out = head[0]
     25     else:
     26         out = u''.join([text_type(v) for v in chain(head, nodes)])
     27 
     28     try:
     29         return literal_eval(out)
     30     except (ValueError, SyntaxError, MemoryError):
     31         return out
     32 
     33 
     34 class NativeCodeGenerator(CodeGenerator):
     35     """A code generator which avoids injecting ``to_string()`` calls around the
     36     internal code Jinja uses to render templates.
     37     """
     38 
     39     def visit_Output(self, node, frame):
     40         """Same as :meth:`CodeGenerator.visit_Output`, but do not call
     41         ``to_string`` on output nodes in generated code.
     42         """
     43         if self.has_known_extends and frame.require_output_check:
     44             return
     45 
     46         finalize = self.environment.finalize
     47         finalize_context = getattr(finalize, 'contextfunction', False)
     48         finalize_eval = getattr(finalize, 'evalcontextfunction', False)
     49         finalize_env = getattr(finalize, 'environmentfunction', False)
     50 
     51         if finalize is not None:
     52             if finalize_context or finalize_eval:
     53                 const_finalize = None
     54             elif finalize_env:
     55                 def const_finalize(x):
     56                     return finalize(self.environment, x)
     57             else:
     58                 const_finalize = finalize
     59         else:
     60             def const_finalize(x):
     61                 return x
     62 
     63         # If we are inside a frame that requires output checking, we do so.
     64         outdent_later = False
     65 
     66         if frame.require_output_check:
     67             self.writeline('if parent_template is None:')
     68             self.indent()
     69             outdent_later = True
     70 
     71         # Try to evaluate as many chunks as possible into a static string at
     72         # compile time.
     73         body = []
     74 
     75         for child in node.nodes:
     76             try:
     77                 if const_finalize is None:
     78                     raise nodes.Impossible()
     79 
     80                 const = child.as_const(frame.eval_ctx)
     81                 if not has_safe_repr(const):
     82                     raise nodes.Impossible()
     83             except nodes.Impossible:
     84                 body.append(child)
     85                 continue
     86 
     87             # the frame can't be volatile here, because otherwise the as_const
     88             # function would raise an Impossible exception at that point
     89             try:
     90                 if frame.eval_ctx.autoescape:
     91                     if hasattr(const, '__html__'):
     92                         const = const.__html__()
     93                     else:
     94                         const = escape(const)
     95 
     96                 const = const_finalize(const)
     97             except Exception:
     98                 # if something goes wrong here we evaluate the node at runtime
     99                 # for easier debugging
    100                 body.append(child)
    101                 continue
    102 
    103             if body and isinstance(body[-1], list):
    104                 body[-1].append(const)
    105             else:
    106                 body.append([const])
    107 
    108         # if we have less than 3 nodes or a buffer we yield or extend/append
    109         if len(body) < 3 or frame.buffer is not None:
    110             if frame.buffer is not None:
    111                 # for one item we append, for more we extend
    112                 if len(body) == 1:
    113                     self.writeline('%s.append(' % frame.buffer)
    114                 else:
    115                     self.writeline('%s.extend((' % frame.buffer)
    116 
    117                 self.indent()
    118 
    119             for item in body:
    120                 if isinstance(item, list):
    121                     val = repr(native_concat(item))
    122 
    123                     if frame.buffer is None:
    124                         self.writeline('yield ' + val)
    125                     else:
    126                         self.writeline(val + ',')
    127                 else:
    128                     if frame.buffer is None:
    129                         self.writeline('yield ', item)
    130                     else:
    131                         self.newline(item)
    132 
    133                     close = 0
    134 
    135                     if finalize is not None:
    136                         self.write('environment.finalize(')
    137 
    138                         if finalize_context:
    139                             self.write('context, ')
    140 
    141                         close += 1
    142 
    143                     self.visit(item, frame)
    144 
    145                     if close > 0:
    146                         self.write(')' * close)
    147 
    148                     if frame.buffer is not None:
    149                         self.write(',')
    150 
    151             if frame.buffer is not None:
    152                 # close the open parentheses
    153                 self.outdent()
    154                 self.writeline(len(body) == 1 and ')' or '))')
    155 
    156         # otherwise we create a format string as this is faster in that case
    157         else:
    158             format = []
    159             arguments = []
    160 
    161             for item in body:
    162                 if isinstance(item, list):
    163                     format.append(native_concat(item).replace('%', '%%'))
    164                 else:
    165                     format.append('%s')
    166                     arguments.append(item)
    167 
    168             self.writeline('yield ')
    169             self.write(repr(concat(format)) + ' % (')
    170             self.indent()
    171 
    172             for argument in arguments:
    173                 self.newline(argument)
    174                 close = 0
    175 
    176                 if finalize is not None:
    177                     self.write('environment.finalize(')
    178 
    179                     if finalize_context:
    180                         self.write('context, ')
    181                     elif finalize_eval:
    182                         self.write('context.eval_ctx, ')
    183                     elif finalize_env:
    184                         self.write('environment, ')
    185 
    186                     close += 1
    187 
    188                 self.visit(argument, frame)
    189                 self.write(')' * close + ', ')
    190 
    191             self.outdent()
    192             self.writeline(')')
    193 
    194         if outdent_later:
    195             self.outdent()
    196 
    197 
    198 class NativeTemplate(Template):
    199     def render(self, *args, **kwargs):
    200         """Render the template to produce a native Python type. If the result
    201         is a single node, its value is returned. Otherwise, the nodes are
    202         concatenated as strings. If the result can be parsed with
    203         :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
    204         string is returned.
    205         """
    206         vars = dict(*args, **kwargs)
    207 
    208         try:
    209             return native_concat(self.root_render_func(self.new_context(vars)))
    210         except Exception:
    211             exc_info = sys.exc_info()
    212 
    213         return self.environment.handle_exception(exc_info, True)
    214 
    215 
    216 class NativeEnvironment(Environment):
    217     """An environment that renders templates to native Python types."""
    218 
    219     code_generator_class = NativeCodeGenerator
    220     template_class = NativeTemplate
    221