1import sys 2from ast import literal_eval 3from itertools import islice, chain 4from jinja2 import nodes 5from jinja2._compat import text_type 6from jinja2.compiler import CodeGenerator, has_safe_repr 7from jinja2.environment import Environment, Template 8from jinja2.utils import concat, escape 9 10 11def 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 34class 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 198class 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 216class NativeEnvironment(Environment): 217 """An environment that renders templates to native Python types.""" 218 219 code_generator_class = NativeCodeGenerator 220 template_class = NativeTemplate 221