|
1
|
|
|
#!/usr/bin/env python3 |
|
2
|
|
|
|
|
3
|
|
|
# -*- coding: utf-8 -*- |
|
4
|
|
|
|
|
5
|
|
|
from jinja2 import DebugUndefined, StrictUndefined, Undefined, make_logging_undefined |
|
6
|
|
|
from jinja2 import Environment, FileSystemLoader |
|
7
|
|
|
from jinja2.exceptions import TemplateNotFound, UndefinedError |
|
8
|
|
|
import sys |
|
9
|
|
|
import logging |
|
10
|
|
|
import inspect |
|
11
|
|
|
|
|
12
|
|
|
from . import utils |
|
13
|
|
|
from . import filters |
|
14
|
|
|
from . import tests |
|
15
|
|
|
from . import globals |
|
16
|
|
|
|
|
17
|
|
|
def get_symbols(mod): |
|
18
|
|
|
return { k:v for k,v |
|
19
|
|
|
in inspect.getmembers(mod) |
|
20
|
|
|
if not (k.startswith('_')) } |
|
21
|
|
|
|
|
22
|
|
|
class TemplateEngine(object): |
|
23
|
|
|
|
|
24
|
|
|
def __init__( self, |
|
25
|
|
|
undefined_variables_mode_behaviour='strict', |
|
26
|
|
|
j2_env_params={}, |
|
27
|
|
|
): |
|
28
|
|
|
|
|
29
|
|
|
m = undefined_variables_mode_behaviour |
|
30
|
|
|
if m in ['strict', 'StrictUndefined']: |
|
31
|
|
|
Handler = StrictUndefined |
|
32
|
|
|
elif m in ['empty', 'Undefined']: |
|
33
|
|
|
Handler = Undefined |
|
34
|
|
|
elif m in ['keep', 'DebugUndefined']: |
|
35
|
|
|
Handler = DebugUndefined |
|
36
|
|
|
|
|
37
|
|
|
# Setup debug logging on STDERR to have the jinja2 engine emit |
|
38
|
|
|
# its activities |
|
39
|
|
|
root = logging.getLogger(__name__) |
|
40
|
|
|
root.setLevel(logging.DEBUG) |
|
41
|
|
|
handler = logging.StreamHandler(sys.stderr) |
|
42
|
|
|
handler.setFormatter(logging.Formatter('%(name)s %(levelname)s: %(message)s')) |
|
43
|
|
|
root.addHandler(handler) |
|
44
|
|
|
|
|
45
|
|
|
UndefinedHandler = make_logging_undefined( logger=root, base=Handler ) |
|
|
|
|
|
|
46
|
|
|
|
|
47
|
|
|
j2_env_params.setdefault('undefined', UndefinedHandler) |
|
48
|
|
|
j2_env_params.setdefault('trim_blocks', True) |
|
49
|
|
|
j2_env_params.setdefault('keep_trailing_newline', False) |
|
50
|
|
|
j2_env_params.setdefault('extensions', [ |
|
51
|
|
|
'jinja2.ext.i18n', |
|
52
|
|
|
'jinja2.ext.do', |
|
53
|
|
|
'jinja2.ext.loopcontrols', |
|
54
|
|
|
]) |
|
55
|
|
|
|
|
56
|
|
|
self.j2_env_params = j2_env_params |
|
57
|
|
|
|
|
58
|
|
|
self.filters = get_symbols(filters) |
|
59
|
|
|
self.tests = get_symbols(tests) |
|
60
|
|
|
self.globals = get_symbols(globals) |
|
61
|
|
|
|
|
62
|
|
|
def render( |
|
63
|
|
|
self, |
|
64
|
|
|
template, |
|
65
|
|
|
context |
|
66
|
|
|
): |
|
67
|
|
|
""" Render the template """ |
|
68
|
|
|
|
|
69
|
|
|
# We don't assume that includes and other sourceables reside relative |
|
70
|
|
|
# to the current directory but instead relative to the "master" template |
|
71
|
|
|
# we are processing. We deviate from jinja tradition this way. |
|
72
|
|
|
# This is because. |
|
73
|
|
|
# 1. We process template from stdin in temporary directories |
|
74
|
|
|
# 2. We should be free to change the process CWD and not break rendering |
|
75
|
|
|
rootdir = utils.dirname(template) |
|
76
|
|
|
|
|
77
|
|
|
self.j2_env_params.setdefault('loader', FileSystemLoader(rootdir)) |
|
78
|
|
|
j2_env = Environment(**self.j2_env_params) |
|
79
|
|
|
|
|
80
|
|
|
j2_env.globals.update(self.globals) |
|
81
|
|
|
j2_env.filters.update(self.filters) |
|
82
|
|
|
j2_env.tests.update(self.tests) |
|
83
|
|
|
|
|
84
|
|
|
try: |
|
85
|
|
|
template = utils.basename(template) |
|
86
|
|
|
yield j2_env.get_template(template).render(context) |
|
87
|
|
|
except UndefinedError as e: |
|
88
|
|
|
raise UndefinedError( "variable {} in template '{}'".format( |
|
89
|
|
|
str(e), template) ) from e |
|
90
|
|
|
|
|
91
|
|
|
|