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