summaryrefslogtreecommitdiff
path: root/jinja2/optimizer.py
blob: b12b1f4bcea424dde35fd31102a802552ccd6388 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# -*- coding: utf-8 -*-
"""
    jinja2.optimizer
    ~~~~~~~~~~~~~~~~

    This module tries to optimize template trees by:

        * eliminating constant nodes
        * evaluating filters and macros on constant nodes
        * unroll loops on constant values
        * replace variables which are already known (because the doesn't
          change often and you want to prerender a template) with constants

    After the optimation you will get a new, simplier template which can
    be saved again for later rendering. But even if you don't want to
    prerender a template, this module might speed up your templates a bit
    if you are using a lot of constants.

    :copyright: Copyright 2008 by Christoph Hack.
    :license: GNU GPL.
"""
from copy import deepcopy
from jinja2 import nodes
from jinja2.visitor import NodeVisitor, NodeTransformer


class Optimizer(NodeVisitor):

    def __init__(self, environment, context={}):
        self.environment = environment
        self.context = context

    def visit_Output(self, node):
        node.nodes = [self.visit(n) for n in node.nodes]
        return node

    def visit_Template(self, node):
        body = []
        for n in node.body:
            x = self.visit(n)
            if isinstance(x, list):
                body.extend(x)
            else:
                body.append(x)
        node.body = body
        return node

    def visit_Filter(self, node):
        """Try to evaluate filters if possible."""
        try:
            x = self.visit(node.node).as_const()
        except nodes.Impossible:
            return node
        for filter in reversed(node.filters):
            # XXX: call filters with arguments
            x = self.environment.filters[filter.name](self.environment, x)
            # XXX: don't optimize context dependent filters
        return nodes.Const(x)

    def visit_For(self, node):
        """Loop unrolling for constant values."""
        try:
            iter = self.visit(node.iter).as_const()
        except nodes.Impossible:
            return node
        result = []
        target = node.target.name
        for item in iter:
            # XXX: take care of variable scopes
            self.context[target] = item
            result.extend(self.visit(n) for n in deepcopy(node.body))
        return result

    def visit_Name(self, node):
        # XXX: take care of variable scopes!
        if node.name not in self.context:
            return node
        return nodes.Const(self.context[node.name])

    def generic_visit(self, node, *args, **kwargs):
        NodeVisitor.generic_visit(self, node, *args, **kwargs)
        return node


def optimize(node, environment):
    optimizer = Optimizer(environment)
    return optimizer.visit(node)