Commit 1d4ab7d1 authored by Marien Fressinaud's avatar Marien Fressinaud

add: Provide a very basic template system

parent a069ecbc
......@@ -58,15 +58,72 @@ $ tree
Boop! basically just copied `content` files under a `output` directory. But it
can convert Markdown files in HTML too:
can convert Markdown files in HTML too. For that, you'll need a template file
for articles:
$ mkdir theme
$ echo '<html><body>{{ ARTICLE_CONTENT }}</body></html' > theme/article.html
Then, you can create your Markdown file as you did with the HTML one:
$ echo '# Welcome!' > content/
$ echo output/markdown-index.html
You can define meta variables in the Markdown file which will be accessible
then in the template. Variables are uppercased and prepended by `ARTICLE_`. For
example, for the following Markdown file:
title: Welcome!
author: Marien
This is my article.
And the HTML template:
<title>{{ ARTICLE_TITLE }}</title>
<h1>{{ ARTICLE_TITLE }}</h1>
<p>By {{ ARTICLE_AUTHOR }}</p>
It will produce the following file:
<p>By Marien</p>
<p>This is my article.</p>
For now, you can use any valid Python expression between `{{ }}`, you have
access to the variables defined in the meta header, the Python builtin
functions and the `datetime` module.
## Tests
There are some tests (i.e. doctests) that can be run to check that everything
......@@ -3,9 +3,15 @@
import os
import sys
import shutil
import locale
import markdown
import boopsy
locale.setlocale(locale.LC_ALL, "fr_FR.utf8")
class ProgramError(Exception):
"""Exception raised during the program execution.
......@@ -41,6 +47,20 @@ def mkdirs_for_file(filepath):
os.makedirs(path, exist_ok=True)
def convert_meta_to_article_vars(meta):
"""Convert Markdown Meta to vars for article layout.
article_vars = {}
for key, values in meta.items():
article_key = f"ARTICLE_{key.upper()}"
# The Markdown's meta extension extract all the values in arrays, even
# if there is only one value. Because there is no iteration system in
# the template system, for the moment we just get the first value of
# the array.
article_vars[article_key] = values[0]
return article_vars
def main():
# Check that content directory exists on the filesystem
content_path = os.path.join(os.curdir, "content")
......@@ -56,7 +76,7 @@ def main():
# ... but it also can be a simple file if user created it manually!
md = markdown.Markdown()
md = markdown.Markdown(extensions=["meta"])
# And copy the files from ./content to ./output
for filepath in dir_tree(content_path):
......@@ -74,9 +94,18 @@ def main():
with open(content_filepath, "r") as content_file:
html = md.convert(
# And we write it as a HTML file
# We initialize the template
article_template_filepath = os.path.join("theme", "article.html")
template = boopsy.Template(article_template_filepath)
# We get the local variables from the Markdown file (metadata)
# which will be accessible in the article template
article_vars = convert_meta_to_article_vars(md.Meta)
article_vars["ARTICLE_CONTENT"] = html
# And we write the content in the output file
with open(output_filepath, "w") as output_file:
# ... else, we simply copy the file to the output directory
shutil.copyfile(content_filepath, output_filepath)
import re
from datetime import datetime
class BoopsySyntaxError(Exception):
"""Exception raised if template file is invalid.
class Template:
def __init__(self, template_name):
"""Initialize a Boopsy template from file content.
self.template_name = template_name
with open(self.template_name, "r") as f:
self.template =
def render(self, variables):
"""Return a string corresponding to template content with vars replaced.
rendering = []
# The template is splitted in tokens:
# - {{ a_python_expression }}
# - or all the other strings
tokens = re.split(r"(?s)({{.*?}})", self.template)
for token in tokens:
if token.startswith("{{"):
expression = token[2:-2].strip()
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
elif token:
# default tokens must be added to the rendering array
return "".join(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