Source code for woom.render
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Jinja text rendering
"""
import os
import shlex
from jinja2 import (
BaseLoader,
Environment,
FileSystemLoader,
PackageLoader,
StrictUndefined,
TemplateNotFound,
Undefined,
)
from . import util as wutil
#: :class:`jinja2.Environment` used to render woom commandline templates
JINJA_ENV = Environment(
loader=PackageLoader("woom"), undefined=StrictUndefined, trim_blocks=True, extensions=['jinja2.ext.do']
)
[docs]
def setup_template_loader(workflow_dir):
"""Setup Jinja loader to support user template extensions
This allows users to extend base templates using Jinja inheritance.
User templates are searched in: :file:`{workflow_dir}/templates/`
.. warning:: To inherit from the default template,
prefix the template name with a "!".
Parameters
----------
workflow_dir: str
Path to the workflow directory
Example
-------
User can create :file:`{workflow_dir}/templates/job.sh` with:
.. code-block:: jinja
{% extends "!job.sh" %}
{% block header %}
{{ super() }}
# Custom header additions
echo "Starting custom workflow"
{% endblock %}
"""
user_template_dir = os.path.join(workflow_dir, "templates")
JINJA_ENV.loader = WoomLoader(workflow_dir)
return user_template_dir
[docs]
class WoomLoader(BaseLoader):
"""Jinja loader that searches for user templates
Base template is loadable with a "!" prefix.
Parameters
----------
workflow_dir : str
Workflow directory path
"""
[docs]
def __init__(self, workflow_dir):
user_template_dir = os.path.join(workflow_dir, "templates")
if os.path.exists(user_template_dir):
self._woom_user_loader = FileSystemLoader(user_template_dir)
else:
self._woom_user_loader = None
self._woom_package_loader = PackageLoader("woom")
[docs]
def get_source(self, environment, template):
# Handle absolute paths
if os.path.isabs(template):
if os.path.exists(template):
dirname = os.path.dirname(template)
basename = os.path.basename(template)
loader = FileSystemLoader(dirname)
return loader.get_source(environment, basename)
else:
raise TemplateNotFound(f"Template file not found: {template}")
# Handle base template prefix and user templates
loaders = [self._woom_package_loader]
if not template.startswith('!') and self._woom_user_loader:
loaders.insert(0, self._woom_user_loader)
template = template.lstrip("!")
for loader in loaders:
try:
return loader.get_source(environment, template)
except TemplateNotFound:
pass
# Fall back to path
if os.path.exists(template):
loader = FileSystemLoader(".")
return loader.get_source(environment, template)
raise TemplateNotFound(f"Templates {template} not found")
[docs]
def render(template, context, strict=True, nested=True):
"""Render this text with Jinja
Note
----
Rendering is performed through a recursive process until the final
string does not change. This allows passing parameters that contains jinja patterns.
Parameters
----------
text: str, jinja2.Template
Input template
context: dict
Objects used for filling
Return
------
str
"""
if not strict:
JINJA_ENV.undefined = Undefined
prev = template
while True:
if isinstance(prev, str):
tpl = JINJA_ENV.from_string(prev)
else:
tpl = prev
curr = tpl.render(context)
if nested and (not isinstance(prev, str) or curr != prev):
prev = curr
else:
break
if not strict:
JINJA_ENV.undefined = StrictUndefined
return curr
[docs]
def filter_replicate_option(values, opt_name, format="{opt_name}={value}"):
"""Replicate a command line option with values
Parameters
----------
values: list
Values to distribute
opt_name: str
Option name, typically startwith with a dash '-'
format: str
How to format the option
Return
------
str
Example
-------
>>> filter_replicate_option(['uo', 'vo'], '--var')
'--var=uo --var=vo'
"""
calls = []
if not isinstance(values, list):
values = [values]
for value in values:
value = shlex.quote(value)
calls.append(format.format(**locals()))
return " ".join(calls)
[docs]
def filter_strftime(date, format):
"""Create a :class:`~woom.util.WoomDate` and format it
Parameters
----------
date: str, pandas.Timestamp, woom.util.WoomDate
Date to initialize a :class:`~woom.util.WoomDate`
format: str
Iso format
Return
------
str
Example
-------
>>> filter_strftime("2025-02-01T02:12", "%Y-%m")
"2025-02"
See also
--------
woom.util.WoomDate
"""
return wutil.WoomDate(date).strftime(format)
[docs]
def filter_as_env_str(value):
"""Convert to environment variable string
Parameters
----------
value:
Vaule to convert as string
Return
------
str
Example
-------
>>> filter_as_env_str(2.5)
'2.5'
>>> filter_as_env_str(["uo", "vo"])
'uo:vo'
"""
if isinstance(value, (list, tuple, set)):
return os.pathsep.join([str(v) for v in value])
return str(value)
[docs]
def get_jinja_filter(name):
"""Simply return a filter function using its name"""
return JINJA_ENV.filters[name]
#: Default woom jinja filters
JINJA_FILTERS = dict(
replicate_option=filter_replicate_option, strftime=filter_strftime, as_str_env=filter_as_env_str
)
JINJA_ENV.filters.update(JINJA_FILTERS)