Completed
Push — master ( 98e1d4...d1821a )
by Satoru
01:27
created

try_render()   B

Complexity

Conditions 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
c 2
b 0
f 0
dl 0
loc 23
rs 8.2508
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