Tutorial¶
This tutorial covers Mondir’s basic functionality. For reference-style documentation on all features, see API reference and Template reference instead.
Example setup and data¶
As an unrealistic but simple usage example, let’s build an “employee directory” in which there is one text file with some data for each employee. We also want another file containing an index listing all employees.
We can imagine that we get the list of employees from some data source but will just hardcode it in our example scripts. This is what it might look like:
employees = [
{"name": "John Doe", "dept": "Sales"},
{"name": "Jane Smith", "dept": "Accounting"},
{"name": "Andy Nobody", "dept": "Marketing"},
{"name": "Audrey Whatchamacallit", "dept": "Administration"},
]
Rendering setup¶
To render a template directory with this data, we create an instance of
DirTemplate, passing the template directory (which we have yet to set
up) as a constructor argument, then call its render()
method, providing the desired output directory as a positional argument and any
template parameters as keyword arguments:
from mondir import DirTemplate
dir_template = DirTemplate(template_dir)
dir_template.render(output_path, employees=employees)
Here, employees is our template parameter. Its value will be accessible
within all templates contained in template_dir.
Template directory setup¶
Now let’s set up the template directory itself.
For the individual employee files, inside whatever directory template_dir
points to, we create a file {{ employee.name }}.txt with these contents:
{% dirlevel %}
{% for employee in employees %}
{% thisfile %}
{% endfor %}
{% enddirlevel -%}
Name: {{ employee.name }}
Department: {{ employee.dept }}
The first thing you might notice is that both the filename and the file’s
contents may contain Jinja template syntax, in this case expression
substitution via {{ ... }}.
Next we have those non-standard Jinja tags at the beginning of the file, which as you might have guessed are Mondir instructions.
dirlevel tags signal that everything inside of them is not
part of the file’s (template) contents but should be considered
“directory-level” instructions (hence the name), usually involving the file
itself. Inside those, we can use Jinja tags for control flow etc. as normal,
which is what we did here by iterating over our employees template
parameter with a for loop.
thisfile means simply to render and output the current file in
the current context. In this case, this is done for each iteration, with each
iteration’s employee value accessible in the rendering context.
So in our output directory we’ll get 3 files John Doe.txt, Jane
Smith.txt and Alan Nobody.txt, each with contents like
Name: John Doe
Department: Sales
For the employee index, we add another file index.txt with these contents:
{% for employee in employees %}
- {{ employee.name }}
{% endfor %}
This will be rendered as a simple Jinja template and placed as index.txt in
the output directory, which goes to show that the absence of dirlevel
instructions means to simply render the file once and put the result in the
output directory under its original name (and path).
Shorthands¶
Because iterating over sequences, rendering the current file for each is such a common occurrence, Mondir comes with several shorthands that make this case easier.
Loop shorthand¶
The first is a shorter form of the for loop, inspired by Python’s list
comprehensions. Instead of the Jinja for loop with thisfile inside
like above, we can write:
{% dirlevel %}
{% thisfile for employee in employees %}
{% enddirlevel -%}
...
Implicit dirlevel¶
Now that we no longer have any Jinja instructions inside our dirlevel tags,
we can just leave them out:
{% thisfile for employee in employees -%}
...
Mondir will treat such tags as being surrounded by implicit dirlevel tags.
Note that, as mentioned above, this only works if the statement isn’t
nested inside other tags. For example, getting rid of dirlevel without
getting rid of the Jinja for loop first wouldn’t work:
{% for employee in employees %}
{% thisfile %}
{% endfor -%}
...
There is no proper error handling for this yet, so for now it leads to undefined behavior, probably resulting in incorrect and hard to debug output or strange error messages.
Asterisk loop variable¶
This one might not be a pure improvement in this specific situation and has
drawbacks, but is worth mentioning anyway: Instead of always referring to
variables employee.name, employee.dept etc., we could use * (“star”
/ asterisk) as our loop variable instead of employee, which automatically
merges the contents of the loop variable (if it’s a dictionary) into the
evaluation context, allowing us to use them directly. So after renaming the
file to just {{ name }}.txt, we could change its contents to:
{% thisfile for * in employees -%}
Name: {{ name }}
Department: {{ dept }}
If overused, this can make things confusing and so it should be used with caution.
Overriding filenames¶
Let’s imagine that, for some reason, we want the administrative staff to have
their employee files in a separate folder called admin.
To do this, we need to set different output file paths depending on each
employee’s department (dept). While we could put the necessary expression
in our filename, this would not only make it quite unwieldy but also wouldn’t
really work, because we need to set not only the filename itself but also the
parent directory name and / is not a valid filename character.
Luckily, there is a way to override output filenames and paths relative to the output directory from within the template file itself:
{% thisfile for employee in employees with %}
{% if dept == "Administration" %}
{% filename %}admin/{{ employee.name }}.txt{% endfilename %}
{% else %}
{% filename %}{{ employee.name }}.txt{% endfilename %}
{% endif %}
{% endthisfile -%}
...
As the example shows, thisfile [...] with tags (which, unlike regular
thisfile tags, must be closed!) can be used to set certain file properties.
In this case, we set the template for the output filename (and path relative to
the output directory) using filename tags and do so within
Jinja conditional (if) tags.
Note once again that the nesting order here is important: We can nest if
inside thisfile, but nesting thisfile in if without surrounding
dirlevel tags would lead to undefined behavior as mentioned in
Implicit dirlevel.