• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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