File: mako_renderer.py

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (181 lines) | stat: -rw-r--r-- 5,937 bytes parent folder | download | duplicates (8)
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import sys

import mako.runtime
import mako.template
import mako.util

_MAKO_TEMPLATE_PASS_KEY = object()


class MakoTemplate(object):
    """Represents a compiled template object."""

    _mako_template_cache = {}

    def __init__(self, template_text):
        assert isinstance(template_text, str)

        template_params = {
            "strict_undefined": True,
        }

        template = self._mako_template_cache.get(template_text)
        if template is None:
            template = mako.template.Template(
                text=template_text, **template_params)
            self._mako_template_cache[template_text] = template
        self._template = template

    def mako_template(self, pass_key=None):
        assert pass_key is _MAKO_TEMPLATE_PASS_KEY
        return self._template


class MakoRenderer(object):
    """Represents a renderer object implemented with Mako templates."""

    def __init__(self):
        self._text_buffer = None
        self._is_invalidated = False
        self._caller_stack = []
        self._caller_stack_on_error = []

    def reset(self):
        """
        Resets the rendering states of this object.  Must be called before
        the first call to |render| or |render_text|.
        """
        self._text_buffer = mako.util.FastEncodingBuffer()
        self._is_invalidated = False

    def is_rendering_complete(self):
        return not (self._is_invalidated or self._text_buffer is None
                    or self._caller_stack)

    def invalidate_rendering_result(self):
        self._is_invalidated = True

    def to_text(self):
        """Returns the rendering result."""
        assert self._text_buffer is not None
        return self._text_buffer.getvalue()

    def render(self, caller, template, template_vars):
        """
        Renders the template with variable bindings.

        It's okay to invoke |render| method recursively and |caller| is pushed
        onto the call stack, which is accessible via
        |callers_from_first_to_last| method, etc.

        Args:
            caller: An object to be pushed onto the call stack.
            template: A MakoTemplate.
            template_vars: A dict of template variable bindings.
        """
        assert caller is not None
        assert isinstance(template, MakoTemplate)
        assert isinstance(template_vars, dict)

        self._caller_stack.append(caller)

        try:
            mako_template = template.mako_template(
                pass_key=_MAKO_TEMPLATE_PASS_KEY)
            mako_context = mako.runtime.Context(self._text_buffer,
                                                **template_vars)
            mako_template.render_context(mako_context)
        except:
            # Print stacktrace of template rendering.
            sys.stderr.write("\n")
            sys.stderr.write("==== template rendering error ====\n")
            sys.stderr.write("  * name: {}, type: {}\n".format(
                _guess_caller_name(self.last_caller), type(self.last_caller)))
            sys.stderr.write("  * depth: {}, module_id: {}\n".format(
                len(self._caller_stack), mako_template.module_id))
            sys.stderr.write("---- template source ----\n")
            sys.stderr.write(mako_template.source)

            # Save the error state at the deepest call.
            current = self._caller_stack
            on_error = self._caller_stack_on_error
            if (len(current) <= len(on_error)
                    and all(current[i] == on_error[i]
                            for i in range(len(current)))):
                pass  # Error happened in a deeper caller.
            else:
                self._caller_stack_on_error = list(self._caller_stack)

            raise
        finally:
            self._caller_stack.pop()

    def render_text(self, text):
        """Renders a plain text as is."""
        assert isinstance(text, str)
        self._text_buffer.write(text)

    def push_caller(self, caller):
        self._caller_stack.append(caller)

    def pop_caller(self):
        self._caller_stack.pop()

    @property
    def callers_from_first_to_last(self):
        """
        Returns the callers of this renderer in the order from the first caller
        to the last caller.
        """
        return iter(self._caller_stack)

    @property
    def callers_from_last_to_first(self):
        """
        Returns the callers of this renderer in the order from the last caller
        to the first caller.
        """
        return reversed(self._caller_stack)

    @property
    def last_caller(self):
        """Returns the last caller in the call stack of this renderer."""
        return self._caller_stack[-1]

    @property
    def callers_on_error(self):
        """
        Returns the callers of this renderer in the order from the last caller
        to the first caller at the moment when an exception was thrown.
        """
        return reversed(self._caller_stack_on_error)

    @property
    def last_caller_on_error(self):
        """
        Returns the deepest caller at the moment when an exception was thrown.
        """
        return self._caller_stack_on_error[-1]


def _guess_caller_name(caller):
    """Returns the best-guessed name of |caller|."""
    try:
        # Outer CodeNode may have a binding to the caller.
        for name, value in caller.outer.template_vars.items():
            if value is caller:
                return name
        try:
            # Outer ListNode may contain the caller.
            for index, value in enumerate(caller.outer, 1):
                if value is caller:
                    return "{}-of-{}-in-list".format(index, len(caller.outer))
        except:
            pass
        return "<no name>"
    except:
        return "<unknown>"