Commit fc5c65b0 authored by Marien Fressinaud's avatar Marien Fressinaud

Extract a Code class from Template

This class will help us to manage indentation later and hide complexity
of the code execution.
parent 313754f8
......@@ -9,6 +9,48 @@ class BoopsySyntaxError(Exception):
pass
class Code:
"""Code is an utility class aimed to help us to manage code generation.
Example:
>>> code = Code()
>>> code.write("foo = 0")
>>> code.write("if True:")
>>> code.indent()
>>> code.write("foo = bar")
>>> context = code.run({"bar": 42})
>>> context["foo"]
42
"""
INDENT_STEP = 4
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.
......@@ -25,8 +67,8 @@ class Template:
# 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 = []")
code = Code()
code.write("rendering = []")
# The template is splitted in tokens:
# - {{ a_python_expression }}
......@@ -40,21 +82,19 @@ class Template:
# 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})")
code.write(f"rendering.append({expression})")
elif token:
# default tokens must be added to the rendering array
literal = repr(token)
code.append(f"rendering.append({literal})")
code.write(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)
# `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 = code.run(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"])
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