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

add: Provide a very basic template system

parent a069ecbc
...@@ -58,15 +58,72 @@ $ tree ...@@ -58,15 +58,72 @@ $ tree
``` ```
Boop! basically just copied `content` files under a `output` directory. But it 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:
```console
$ 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:
```console ```console
$ echo '# Welcome!' > content/markdown-index.md $ echo '# Welcome!' > content/markdown-index.md
$ boop.py $ boop.py
$ echo output/markdown-index.html $ echo output/markdown-index.html
<h1>Welcome!</h1> <html><body><h1>Welcome!</h1></body></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:
```markdown
---
title: Welcome!
author: Marien
---
This is my article.
```
And the HTML template:
```html
<html>
<head>
<title>{{ ARTICLE_TITLE }}</title>
</head>
<body>
<h1>{{ ARTICLE_TITLE }}</h1>
<p>By {{ ARTICLE_AUTHOR }}</p>
{{ ARTICLE_CONTENT }}
</body>
</html>
```
It will produce the following file:
```html
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome!</h1>
<p>By Marien</p>
<p>This is my article.</p>
</body>
</html>
```
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 ## Tests
There are some tests (i.e. doctests) that can be run to check that everything There are some tests (i.e. doctests) that can be run to check that everything
......
...@@ -3,9 +3,15 @@ ...@@ -3,9 +3,15 @@
import os import os
import sys import sys
import shutil import shutil
import locale
import markdown import markdown
import boopsy
locale.setlocale(locale.LC_ALL, "fr_FR.utf8")
class ProgramError(Exception): class ProgramError(Exception):
"""Exception raised during the program execution. """Exception raised during the program execution.
...@@ -41,6 +47,20 @@ def mkdirs_for_file(filepath): ...@@ -41,6 +47,20 @@ def mkdirs_for_file(filepath):
os.makedirs(path, exist_ok=True) 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(): def main():
# Check that content directory exists on the filesystem # Check that content directory exists on the filesystem
content_path = os.path.join(os.curdir, "content") content_path = os.path.join(os.curdir, "content")
...@@ -56,7 +76,7 @@ def main(): ...@@ -56,7 +76,7 @@ def main():
# ... but it also can be a simple file if user created it manually! # ... but it also can be a simple file if user created it manually!
os.remove(output_path) os.remove(output_path)
md = markdown.Markdown() md = markdown.Markdown(extensions=["meta"])
# And copy the files from ./content to ./output # And copy the files from ./content to ./output
for filepath in dir_tree(content_path): for filepath in dir_tree(content_path):
...@@ -74,9 +94,18 @@ def main(): ...@@ -74,9 +94,18 @@ def main():
with open(content_filepath, "r") as content_file: with open(content_filepath, "r") as content_file:
html = md.convert(content_file.read()) html = md.convert(content_file.read())
# 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: with open(output_filepath, "w") as output_file:
output_file.write(html) output_file.write(template.render(article_vars))
else: else:
# ... else, we simply copy the file to the output directory # ... else, we simply copy the file to the output directory
shutil.copyfile(content_filepath, output_filepath) shutil.copyfile(content_filepath, output_filepath)
......
import re
from datetime import datetime
class BoopsySyntaxError(Exception):
"""Exception raised if template file is invalid.
"""
pass
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 = f.read()
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
)
rendering.append(evaluated_expression)
elif token:
# default tokens must be added to the rendering array
rendering.append(token)
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