Commit b7bdd945 authored by berumuron's avatar berumuron
Browse files

tec: Improve implementation of Boopsy

We now create a full mini Python program to manage all the template
rendering. It will help to manage `if` and `for` statements.

The `Code` class hides the indentation complexity and execution.
parents 217226ea fc5c65b0
......@@ -9,6 +9,48 @@ class BoopsySyntaxError(Exception):
class Code:
"""Code is an utility class aimed to help us to manage code generation.
>>> code = Code()
>>> code.write("foo = 0")
>>> code.write("if True:")
>>> code.indent()
>>> code.write("foo = bar")
>>> context ={"bar": 42})
>>> context["foo"]
def __init__(self):
self.code = []
self.indent_level = 0
def write(self, line):
"""Write a new line of code."""
indentation = " " * self.indent_level
self.code.append(indentation + line)
def indent(self):
"""Increase the code indentation by one step."""
self.indent_level += self.INDENT_STEP
def dedent(self):
"""Decrease the code indentation by one step."""
self.indent_level -= self.INDENT_STEP
def run(self, global_context={}):
"""Exec the written code and return a dict containing local context.
local_context = {}
exec("\n".join(self.code), global_context, local_context)
return local_context
class Template:
def __init__(self, template_name):
"""Initialize a Boopsy template from file content.
......@@ -20,7 +62,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()
code.write("rendering = []")
# The template is splitted in tokens:
# - {{ a_python_expression }}
......@@ -32,15 +80,21 @@ 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
# Expression token is just valid Python code so we add it to
# the rendering array. It will be evaluated later.
elif token:
# default tokens must be added to the rendering array
literal = repr(token)
# `result` will store the `rendering` variable so we can get the finale
# string to return to the caller.
context = variables.copy()
context["datetime"] = datetime
result =
return "".join(rendering)
# `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 result["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