126 lines
3.6 KiB
Python
126 lines
3.6 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Templating module."""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import json
|
|
import logging
|
|
|
|
import jinja2
|
|
from jinja2 import sandbox
|
|
import six
|
|
|
|
CODE_CACHE = {}
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
if six.PY3:
|
|
StandardError = Exception
|
|
|
|
|
|
class TemplateException(Exception):
|
|
|
|
"""Error applying template."""
|
|
|
|
|
|
class CompilerCache(jinja2.BytecodeCache):
|
|
|
|
"""Cache for compiled template code.
|
|
|
|
This is leveraged when satori is used from within a long-running process,
|
|
like a server. It does not speed things up for command-line use.
|
|
"""
|
|
|
|
def load_bytecode(self, bucket):
|
|
"""Load compiled code from cache."""
|
|
if bucket.key in CODE_CACHE:
|
|
bucket.bytecode_from_string(CODE_CACHE[bucket.key])
|
|
|
|
def dump_bytecode(self, bucket):
|
|
"""Write compiled code into cache."""
|
|
CODE_CACHE[bucket.key] = bucket.bytecode_to_string()
|
|
|
|
|
|
def do_prepend(value, param='/'):
|
|
"""Prepend a string if the passed in string exists.
|
|
|
|
Example:
|
|
The template '{{ root|prepend('/')}}/path';
|
|
Called with root undefined renders:
|
|
/path
|
|
Called with root defined as 'root' renders:
|
|
/root/path
|
|
"""
|
|
if value:
|
|
return '%s%s' % (param, value)
|
|
else:
|
|
return ''
|
|
|
|
|
|
def preserve_linefeeds(value):
|
|
"""Escape linefeeds.
|
|
|
|
To make templates work with both YAML and JSON, escape linefeeds instead of
|
|
allowing Jinja to render them.
|
|
"""
|
|
return value.replace("\n", "\\n").replace("\r", "")
|
|
|
|
|
|
def get_jinja_environment(template, extra_globals=None):
|
|
"""Return a sandboxed jinja environment."""
|
|
template_map = {'template': template}
|
|
env = sandbox.ImmutableSandboxedEnvironment(
|
|
loader=jinja2.DictLoader(template_map),
|
|
bytecode_cache=CompilerCache())
|
|
env.filters['prepend'] = do_prepend
|
|
env.filters['preserve'] = preserve_linefeeds
|
|
env.globals['json'] = json
|
|
if extra_globals:
|
|
env.globals.update(extra_globals)
|
|
return env
|
|
|
|
|
|
def parse(template, extra_globals=None, **kwargs):
|
|
"""Parse template.
|
|
|
|
:param template: the template contents as a string
|
|
:param extra_globals: additional globals to include
|
|
:param kwargs: extra arguments are passed to the renderer
|
|
"""
|
|
env = get_jinja_environment(template, extra_globals=extra_globals)
|
|
|
|
minimum_kwargs = {
|
|
'data': {},
|
|
}
|
|
minimum_kwargs.update(kwargs)
|
|
|
|
try:
|
|
template = env.get_template('template')
|
|
except jinja2.TemplateSyntaxError as exc:
|
|
LOG.error(exc, exc_info=True)
|
|
error_message = "Template had a syntax error: %s" % exc
|
|
raise TemplateException(error_message)
|
|
|
|
try:
|
|
result = template.render(**minimum_kwargs)
|
|
#TODO(zns): exceptions in Jinja template sometimes missing traceback
|
|
except jinja2.TemplateError as exc:
|
|
LOG.error(exc, exc_info=True)
|
|
error_message = "Template had an error: %s" % exc
|
|
raise TemplateException(error_message)
|
|
except StandardError as exc:
|
|
LOG.error(exc, exc_info=True)
|
|
error_message = "Template rendering failed: %s" % exc
|
|
raise TemplateException(error_message)
|
|
return result
|