|
1
|
|
|
# |
|
2
|
|
|
# Jinja2 (http://jinja.pocoo.org) based template renderer. |
|
3
|
|
|
# |
|
4
|
|
|
# Author: Satoru SATOH <ssato redhat.com> |
|
5
|
|
|
# License: MIT |
|
6
|
|
|
# |
|
7
|
|
|
# pylint: disable=unused-argument,wrong-import-position,wrong-import-order |
|
8
|
|
|
"""Template rendering module for jinja2-based template config files. |
|
9
|
|
|
""" |
|
10
|
|
|
from __future__ import absolute_import |
|
11
|
|
|
|
|
12
|
|
|
import codecs |
|
13
|
|
|
import locale |
|
14
|
|
|
import logging |
|
15
|
|
|
import os |
|
16
|
|
|
|
|
17
|
|
|
import anyconfig.compat |
|
18
|
|
|
|
|
19
|
|
|
LOGGER = logging.getLogger(__name__) |
|
20
|
|
|
SUPPORTED = False |
|
21
|
|
|
try: |
|
22
|
|
|
import jinja2 |
|
23
|
|
|
from jinja2.exceptions import TemplateNotFound |
|
24
|
|
|
|
|
25
|
|
|
SUPPORTED = True |
|
26
|
|
|
|
|
27
|
|
|
def tmpl_env(paths): |
|
28
|
|
|
""" |
|
29
|
|
|
:param paths: A list of template search paths |
|
30
|
|
|
""" |
|
31
|
|
|
return jinja2.Environment(loader=jinja2.FileSystemLoader(paths)) |
|
32
|
|
|
|
|
33
|
|
|
except ImportError: |
|
34
|
|
|
LOGGER.warning("Jinja2 is not available on your system, so " |
|
35
|
|
|
"template support will be disabled.") |
|
36
|
|
|
|
|
37
|
|
|
class TemplateNotFound(RuntimeError): |
|
38
|
|
|
"""Dummy exception""" |
|
39
|
|
|
pass |
|
40
|
|
|
|
|
41
|
|
|
def tmpl_env(*args): |
|
42
|
|
|
"""Dummy function""" |
|
43
|
|
|
return None |
|
44
|
|
|
|
|
45
|
|
|
|
|
46
|
|
|
def copen(filepath, flag='r', encoding=None): |
|
47
|
|
|
|
|
48
|
|
|
""" |
|
49
|
|
|
FIXME: How to test this ? |
|
50
|
|
|
|
|
51
|
|
|
>>> c = copen(__file__) |
|
52
|
|
|
>>> c is not None |
|
53
|
|
|
True |
|
54
|
|
|
""" |
|
55
|
|
|
if encoding is None: |
|
56
|
|
|
encoding = locale.getdefaultlocale()[1] |
|
57
|
|
|
|
|
58
|
|
|
return codecs.open(filepath, flag, encoding) |
|
59
|
|
|
|
|
60
|
|
|
|
|
61
|
|
|
def make_template_paths(template_file, paths=None): |
|
62
|
|
|
""" |
|
63
|
|
|
Make up a list of template search paths from given `template_file` |
|
64
|
|
|
(absolute or relative path to the template file) and/or `paths` (a list of |
|
65
|
|
|
template search paths given by user). |
|
66
|
|
|
|
|
67
|
|
|
NOTE: User-given `paths` will take higher priority over a dir of |
|
68
|
|
|
template_file. |
|
69
|
|
|
|
|
70
|
|
|
:param template_file: Absolute or relative path to the template file |
|
71
|
|
|
:param paths: A list of template search paths |
|
72
|
|
|
:return: List of template paths ([str]) |
|
73
|
|
|
|
|
74
|
|
|
>>> make_template_paths("/path/to/a/template") |
|
75
|
|
|
['/path/to/a'] |
|
76
|
|
|
>>> make_template_paths("/path/to/a/template", ["/tmp"]) |
|
77
|
|
|
['/tmp', '/path/to/a'] |
|
78
|
|
|
>>> os.chdir("/tmp") |
|
79
|
|
|
>>> make_template_paths("./path/to/a/template") |
|
80
|
|
|
['/tmp/path/to/a'] |
|
81
|
|
|
>>> make_template_paths("./path/to/a/template", ["/tmp"]) |
|
82
|
|
|
['/tmp', '/tmp/path/to/a'] |
|
83
|
|
|
""" |
|
84
|
|
|
tmpldir = os.path.abspath(os.path.dirname(template_file)) |
|
85
|
|
|
return [tmpldir] if paths is None else paths + [tmpldir] |
|
86
|
|
|
|
|
87
|
|
|
|
|
88
|
|
|
def render_s(tmpl_s, ctx=None, paths=None): |
|
89
|
|
|
""" |
|
90
|
|
|
Compile and render given template string `tmpl_s` with context `context`. |
|
91
|
|
|
|
|
92
|
|
|
:param tmpl_s: Template string |
|
93
|
|
|
:param ctx: Context dict needed to instantiate templates |
|
94
|
|
|
:param paths: Template search paths |
|
95
|
|
|
:return: Compiled result (str) |
|
96
|
|
|
|
|
97
|
|
|
>>> render_s("aaa") == "aaa" |
|
98
|
|
|
True |
|
99
|
|
|
>>> s = render_s('a = {{ a }}, b = "{{ b }}"', {'a': 1, 'b': 'bbb'}) |
|
100
|
|
|
>>> if SUPPORTED: |
|
101
|
|
|
... assert s == 'a = 1, b = "bbb"' |
|
102
|
|
|
""" |
|
103
|
|
|
if paths is None: |
|
104
|
|
|
paths = [os.curdir] |
|
105
|
|
|
|
|
106
|
|
|
env = tmpl_env(paths) |
|
107
|
|
|
|
|
108
|
|
|
if env is None: |
|
109
|
|
|
return tmpl_s |
|
110
|
|
|
|
|
111
|
|
|
if ctx is None: |
|
112
|
|
|
ctx = {} |
|
113
|
|
|
|
|
114
|
|
|
return tmpl_env(paths).from_string(tmpl_s).render(**ctx) |
|
115
|
|
|
|
|
116
|
|
|
|
|
117
|
|
|
def render_impl(template_file, ctx=None, paths=None): |
|
118
|
|
|
""" |
|
119
|
|
|
:param template_file: Absolute or relative path to the template file |
|
120
|
|
|
:param ctx: Context dict needed to instantiate templates |
|
121
|
|
|
:return: Compiled result (str) |
|
122
|
|
|
""" |
|
123
|
|
|
env = tmpl_env(make_template_paths(template_file, paths)) |
|
124
|
|
|
|
|
125
|
|
|
if env is None: |
|
126
|
|
|
return copen(template_file).read() |
|
127
|
|
|
|
|
128
|
|
|
if ctx is None: |
|
129
|
|
|
ctx = {} |
|
130
|
|
|
|
|
131
|
|
|
return env.get_template(os.path.basename(template_file)).render(**ctx) |
|
132
|
|
|
|
|
133
|
|
|
|
|
134
|
|
|
def render(filepath, ctx=None, paths=None, ask=False): |
|
135
|
|
|
""" |
|
136
|
|
|
Compile and render template and return the result as a string. |
|
137
|
|
|
|
|
138
|
|
|
:param template_file: Absolute or relative path to the template file |
|
139
|
|
|
:param ctx: Context dict needed to instantiate templates |
|
140
|
|
|
:param paths: Template search paths |
|
141
|
|
|
:param ask: Ask user for missing template location if True |
|
142
|
|
|
:return: Compiled result (str) |
|
143
|
|
|
""" |
|
144
|
|
|
try: |
|
145
|
|
|
return render_impl(filepath, ctx, paths) |
|
146
|
|
|
except TemplateNotFound as mtmpl: |
|
147
|
|
|
if not ask: |
|
148
|
|
|
raise |
|
149
|
|
|
|
|
150
|
|
|
usr_tmpl = anyconfig.compat.raw_input("\n*** Missing template " |
|
151
|
|
|
"'%s'. Please enter absolute " |
|
152
|
|
|
"or relative path starting from " |
|
153
|
|
|
"'.' to the template file: " % |
|
154
|
|
|
mtmpl) |
|
155
|
|
|
usr_tmpl = os.path.normpath(usr_tmpl.strip()) |
|
156
|
|
|
paths = make_template_paths(usr_tmpl, paths) |
|
157
|
|
|
|
|
158
|
|
|
return render_impl(usr_tmpl, ctx, paths) |
|
159
|
|
|
|
|
160
|
|
|
|
|
161
|
|
|
def try_render(filepath=None, content=None, **options): |
|
162
|
|
|
""" |
|
163
|
|
|
Compile and render template and return the result as a string. |
|
164
|
|
|
|
|
165
|
|
|
:param filepath: Absolute or relative path to the template file |
|
166
|
|
|
:param content: Template content (str) |
|
167
|
|
|
:param options: Keyword options passed to :func:`render` defined above. |
|
168
|
|
|
:return: Compiled result (str) or None |
|
169
|
|
|
""" |
|
170
|
|
|
if filepath is None and content is None: |
|
171
|
|
|
raise ValueError("Either 'path' or 'content' must be some value!") |
|
172
|
|
|
|
|
173
|
|
|
tmpl_s = filepath or content[:10] + " ..." |
|
174
|
|
|
LOGGER.debug("Compiling: %s", tmpl_s) |
|
175
|
|
|
try: |
|
176
|
|
|
if content is None: |
|
177
|
|
|
return render(filepath, **options) |
|
178
|
|
|
else: |
|
179
|
|
|
return render_s(content, **options) |
|
180
|
|
|
except Exception as exc: |
|
181
|
|
|
LOGGER.warning("Failed to compile '%s'. It may not be a template.\n" |
|
182
|
|
|
"exc=%r", tmpl_s, exc) |
|
183
|
|
|
return None |
|
184
|
|
|
|
|
185
|
|
|
# vim:sw=4:ts=4:et: |
|
186
|
|
|
|