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>"
|