1from ast import literal_eval
2from itertools import chain
3from itertools import islice
4
5from . import nodes
6from ._compat import text_type
7from .compiler import CodeGenerator
8from .compiler import has_safe_repr
9from .environment import Environment
10from .environment import Template
11
12
13def native_concat(nodes):
14    """Return a native Python type from the list of compiled nodes. If
15    the result is a single node, its value is returned. Otherwise, the
16    nodes are concatenated as strings. If the result can be parsed with
17    :func:`ast.literal_eval`, the parsed value is returned. Otherwise,
18    the string is returned.
19
20    :param nodes: Iterable of nodes to concatenate.
21    """
22    head = list(islice(nodes, 2))
23
24    if not head:
25        return None
26
27    if len(head) == 1:
28        raw = head[0]
29    else:
30        raw = u"".join([text_type(v) for v in chain(head, nodes)])
31
32    try:
33        return literal_eval(raw)
34    except (ValueError, SyntaxError, MemoryError):
35        return raw
36
37
38class NativeCodeGenerator(CodeGenerator):
39    """A code generator which renders Python types by not adding
40    ``to_string()`` around output nodes.
41    """
42
43    @staticmethod
44    def _default_finalize(value):
45        return value
46
47    def _output_const_repr(self, group):
48        return repr(u"".join([text_type(v) for v in group]))
49
50    def _output_child_to_const(self, node, frame, finalize):
51        const = node.as_const(frame.eval_ctx)
52
53        if not has_safe_repr(const):
54            raise nodes.Impossible()
55
56        if isinstance(node, nodes.TemplateData):
57            return const
58
59        return finalize.const(const)
60
61    def _output_child_pre(self, node, frame, finalize):
62        if finalize.src is not None:
63            self.write(finalize.src)
64
65    def _output_child_post(self, node, frame, finalize):
66        if finalize.src is not None:
67            self.write(")")
68
69
70class NativeEnvironment(Environment):
71    """An environment that renders templates to native Python types."""
72
73    code_generator_class = NativeCodeGenerator
74
75
76class NativeTemplate(Template):
77    environment_class = NativeEnvironment
78
79    def render(self, *args, **kwargs):
80        """Render the template to produce a native Python type. If the
81        result is a single node, its value is returned. Otherwise, the
82        nodes are concatenated as strings. If the result can be parsed
83        with :func:`ast.literal_eval`, the parsed value is returned.
84        Otherwise, the string is returned.
85        """
86        vars = dict(*args, **kwargs)
87
88        try:
89            return native_concat(self.root_render_func(self.new_context(vars)))
90        except Exception:
91            return self.environment.handle_exception()
92
93
94NativeEnvironment.template_class = NativeTemplate
95