TemplateNotFound   A
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 3
Duplicated Lines 0 %

Importance

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