# # Jinja2 (http://jinja.pocoo.org) based template renderer. # # Author: Satoru SATOH # License: MIT # # pylint: disable=unused-argument,wrong-import-position,wrong-import-order """anyconfig.template module Template rendering module for jinja2-based template config files. """ from __future__ import absolute_import import codecs import locale import logging import os import anyconfig.compat LOGGER = logging.getLogger(__name__) RENDER_S_OPTS = ['ctx', 'paths', 'filters'] RENDER_OPTS = RENDER_S_OPTS + ['ask'] SUPPORTED = False try: import jinja2 from jinja2.exceptions import TemplateNotFound SUPPORTED = True def tmpl_env(paths): """ :param paths: A list of template search paths """ return jinja2.Environment(loader=jinja2.FileSystemLoader(paths)) except ImportError: LOGGER.warning("Jinja2 is not available on your system, so " "template support will be disabled.") class TemplateNotFound(RuntimeError): """Dummy exception""" pass def tmpl_env(*args): """Dummy function""" return None def copen(filepath, flag='r', encoding=None): """ FIXME: How to test this ? >>> c = copen(__file__) >>> c is not None True """ if encoding is None: encoding = locale.getdefaultlocale()[1] return codecs.open(filepath, flag, encoding) def make_template_paths(template_file, paths=None): """ Make up a list of template search paths from given `template_file` (absolute or relative path to the template file) and/or `paths` (a list of template search paths given by user). NOTE: User-given `paths` will take higher priority over a dir of template_file. :param template_file: Absolute or relative path to the template file :param paths: A list of template search paths :return: List of template paths ([str]) >>> make_template_paths("/path/to/a/template") ['/path/to/a'] >>> make_template_paths("/path/to/a/template", ["/tmp"]) ['/tmp', '/path/to/a'] >>> os.chdir("/tmp") >>> make_template_paths("./path/to/a/template") ['/tmp/path/to/a'] >>> make_template_paths("./path/to/a/template", ["/tmp"]) ['/tmp', '/tmp/path/to/a'] """ tmpldir = os.path.abspath(os.path.dirname(template_file)) return [tmpldir] if paths is None else paths + [tmpldir] def render_s(tmpl_s, ctx=None, paths=None, filters=None): """ Compile and render given template string `tmpl_s` with context `context`. :param tmpl_s: Template string :param ctx: Context dict needed to instantiate templates :param paths: Template search paths :param filters: Custom filters to add into template engine :return: Compiled result (str) >>> render_s("aaa") == "aaa" True >>> s = render_s('a = {{ a }}, b = "{{ b }}"', {'a': 1, 'b': 'bbb'}) >>> if SUPPORTED: ... assert s == 'a = 1, b = "bbb"' """ if paths is None: paths = [os.curdir] env = tmpl_env(paths) if env is None: return tmpl_s if filters is not None: env.filters.update(filters) if ctx is None: ctx = {} return tmpl_env(paths).from_string(tmpl_s).render(**ctx) def render_impl(template_file, ctx=None, paths=None, filters=None): """ :param template_file: Absolute or relative path to the template file :param ctx: Context dict needed to instantiate templates :param filters: Custom filters to add into template engine :return: Compiled result (str) """ env = tmpl_env(make_template_paths(template_file, paths)) if env is None: return copen(template_file).read() if filters is not None: env.filters.update(filters) if ctx is None: ctx = {} return env.get_template(os.path.basename(template_file)).render(**ctx) def render(filepath, ctx=None, paths=None, ask=False, filters=None): """ Compile and render template and return the result as a string. :param template_file: Absolute or relative path to the template file :param ctx: Context dict needed to instantiate templates :param paths: Template search paths :param ask: Ask user for missing template location if True :param filters: Custom filters to add into template engine :return: Compiled result (str) """ try: return render_impl(filepath, ctx, paths, filters) except TemplateNotFound as mtmpl: if not ask: raise usr_tmpl = anyconfig.compat.raw_input(os.linesep + "" "*** Missing template " "'%s'. Please enter absolute " "or relative path starting from " "'.' to the template file: " % mtmpl) usr_tmpl = os.path.normpath(usr_tmpl.strip()) paths = make_template_paths(usr_tmpl, paths) return render_impl(usr_tmpl, ctx, paths, filters) def try_render(filepath=None, content=None, **options): """ Compile and render template and return the result as a string. :param filepath: Absolute or relative path to the template file :param content: Template content (str) :param options: Keyword options passed to :func:`render` defined above. :return: Compiled result (str) or None """ if filepath is None and content is None: raise ValueError("Either 'path' or 'content' must be some value!") tmpl_s = filepath or content[:10] + " ..." LOGGER.debug("Compiling: %s", tmpl_s) try: if content is None: render_opts = anyconfig.utils.filter_options(RENDER_OPTS, options) return render(filepath, **render_opts) render_s_opts = anyconfig.utils.filter_options(RENDER_S_OPTS, options) return render_s(content, **render_s_opts) except Exception as exc: LOGGER.warning("Failed to compile '%s'. It may not be a template.%s" "exc=%r", tmpl_s, os.linesep, exc) return None # vim:sw=4:ts=4:et: