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