Commit 313754f8 authored by Marien Fressinaud's avatar Marien Fressinaud

Change implementation of Boopsy

Instead of creating a `rendering` variable directly in our `render`
method, we create a mini Python program which is evaluated by an `exec`
function.

It will be helpful to handle the `if` and `for` statements because we
will be able to rely on the Python interpretor behaviour to manage the
variable scopes.

It is probably not the most elegant nor the most secure way to do it,
but it's the cheapest one and it's fine in our situation.
parent 217226ea
......@@ -20,7 +20,13 @@ class Template:
def render(self, variables):
"""Return a string corresponding to template content with vars replaced.
"""
rendering = []
# `code` will store a valid Python mini-program. It will basically
# create a `rendering` array, storing the strings to render at the end.
# It will be helpful to handle the `if` and `for` statements because we
# will not have to manage variable scopes and logic by ourselves: we'll
# simply rely on the Python behaviour.
code = []
code.append("rendering = []")
# The template is splitted in tokens:
# - {{ a_python_expression }}
......@@ -32,15 +38,23 @@ class Template:
if len(expression) == 0:
raise BoopsySyntaxError("an expression is expected between {{ }}")
# We evaluate a Python expression which can access the
# variables passed as an argument of `render` and the
# `datetime` module.
evaluated_expression = eval(
expression, {"datetime": datetime}, variables
)
rendering.append(evaluated_expression)
# Expression token is just valid Python code so we add it to
# the rendering array. It will be evaluated later.
code.append(f"rendering.append({expression})")
elif token:
# default tokens must be added to the rendering array
rendering.append(token)
return "".join(rendering)
literal = repr(token)
code.append(f"rendering.append({literal})")
# `global_context` gives access to external variables and modules.
# `local_context` will store the `rendering` variable so we can get the
# finale result and return it to the caller.
global_context = variables.copy()
global_context["datetime"] = datetime
local_context = {}
exec("\n".join(code), global_context, local_context)
# `local_context["rendering"]` is now an array containing our
# interprated template. `item` might be None and so `join` can fail,
# `str` makes sure we are manipulating only strings.
return "".join(str(item) for item in local_context["rendering"])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment