2019-11-25 02:48:12 +01:00
|
|
|
import json
|
|
|
|
|
|
|
|
import jinja2
|
|
|
|
|
|
|
|
|
|
|
|
# The Jinja2 environment
|
2019-11-25 04:35:50 +01:00
|
|
|
__ENV = None
|
2019-11-25 02:48:12 +01:00
|
|
|
|
|
|
|
|
2019-11-25 04:35:50 +01:00
|
|
|
def _env_init(force: bool = False):
|
2019-11-25 03:14:14 +01:00
|
|
|
"""
|
2019-11-25 02:48:12 +01:00
|
|
|
Initialize the Jinja2 environment.
|
2019-11-25 04:10:20 +01:00
|
|
|
:param force: If true, force reload the environment.
|
2019-11-25 03:14:14 +01:00
|
|
|
"""
|
2019-11-25 04:35:50 +01:00
|
|
|
global __ENV
|
|
|
|
if __ENV is None or force:
|
2019-11-25 02:48:12 +01:00
|
|
|
# Use json.dumps as finalizer in order to preserve complex data structures
|
2019-11-25 04:35:50 +01:00
|
|
|
__ENV = jinja2.Environment(finalize=json.dumps)
|
|
|
|
return __ENV
|
2019-11-25 02:48:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
def render(template: str):
|
2019-11-25 03:14:14 +01:00
|
|
|
"""
|
2019-11-25 02:48:12 +01:00
|
|
|
Render the given string as a Jinja2 template.
|
2019-11-25 04:10:20 +01:00
|
|
|
:param template: The template string to render.
|
2019-11-25 03:14:14 +01:00
|
|
|
"""
|
2019-11-25 04:35:50 +01:00
|
|
|
# Make sure the Jinja2 environment is initialized
|
|
|
|
env = _env_init()
|
2019-11-25 02:48:12 +01:00
|
|
|
# Create a Jinja2 template from the input string
|
|
|
|
t = env.from_string(template)
|
2019-11-26 14:24:53 +01:00
|
|
|
decoder = json.JSONDecoder()
|
2019-11-25 02:48:12 +01:00
|
|
|
# Render the template and turn the JSON dump back into complex data structures
|
2019-11-26 14:24:53 +01:00
|
|
|
# Only parse the first JSON object in the string, ignore the rest
|
|
|
|
obj, i = decoder.raw_decode(t.render())
|
|
|
|
return obj
|
2019-11-25 04:35:50 +01:00
|
|
|
|
|
|
|
|
|
|
|
def render_traverse(obj):
|
|
|
|
"""
|
|
|
|
Walk through a complex, JSON-serializable data structure, and pass
|
|
|
|
string objects through the Jinja2 templating engine.
|
|
|
|
:param obj: The object to traverse.
|
|
|
|
"""
|
|
|
|
if isinstance(obj, list):
|
|
|
|
# list -> recurse into each item
|
|
|
|
for i in range(len(obj)):
|
|
|
|
obj[i] = render_traverse(obj[i])
|
|
|
|
return obj
|
|
|
|
elif isinstance(obj, dict):
|
|
|
|
# dict -> recurse into the value of each (key, value)
|
|
|
|
for k, v in obj.items():
|
|
|
|
obj[k] = render_traverse(obj[k])
|
|
|
|
return obj
|
|
|
|
elif isinstance(obj, str):
|
|
|
|
# str -> template
|
|
|
|
return render(obj)
|
|
|
|
else:
|
|
|
|
# anything else -> return as-is
|
|
|
|
return obj
|