mkdocs
Following a quick rundown of how to configure the mkdocs project structure using macros, includes, a base page and external data files to better organize the data for our content.
Installation
We need the python packages mkdocs
/ mkdocs-material
and mkdocs-macros-plugin
.
The mkdocs cli features a command to create a new project:
Snippets
The mkdocs-macros plugin allows to add includes as *.md
or *.html
. Here snippets is next do the docs_dir
, which is
docs
by default.
Glossary
In tech, we love cryptic abbreviations, right ? So to add a glossary,
place a file
glossary.md
into the snippets
directory:
At the bottom of your page, you can include the glossary like in this page example:
Macros
Macros are a little different to includes. They are more like functions, but can not return data, only text.
To add a macros file, add a macros.md
to snippets
directory:
{%- macro linkify(text) -%}
{% set t = text | string %}
{%- if t.startswith('https://') or t.startswith('http://') -%}
[{{ t }}]({{ t }})
{%- else -%}
{{ t }}
{%- endif -%}
{%- endmacro -%}
In order to use the macros on a page, you have to include it in the page:
{% import 'macros.md' as macros with context %}
Linkified link: {{ macros.linkify('https://www.python.org/') }}
See also the docs of the mkdocs-macros-plugin, which you can use to add your custom plugins in order to build your custom workflows.
Adding a base page
In order to achieve consistent results and make macros on each page available, you can add a base page that contains the macros.
{% import 'macros.md' as macros with context %}
{% block content %}
{% endblock %}
{% include 'glossary.md' %}
Reuse the base_page.md
using the extends
directive:
Drive documentation with data files
Instead of adding variables to the extra
key in mkdocs.yml
, it might be useful to have dedicated data files that can
be included using the mkdocs-macros
plugin. For example, you might create a urls.yml
file with all
urls used in your project. It makes sense to have them in a central place and to reference them from there.
plugins:
- search:
lang: de
- macros:
include_dir: snippets
include_yaml:
- urls: data/urls.yml
You can then reference the url by a name: {{ urls.github }}
.
It is also possible to use relative paths like ../otherrepo/data/hosts.yml
to fetch data from outside the git repo.
Re-render when data changes
By default, mkdocs does not watch for changes in the data
directory. You can tell it to do so by using
mkdocs serve --watch data
.
Making drafts
I found that I like to add markdown pages as drafts and commit them. I made sure not to include them in the nav
in mkdcos.yml
.
In that case, the draft page can be found using the search because it will be rendered as html. So there are other possibilities depending
on your workflow
- Pushing only to
main
, making draft visible for others, but ignored for html rendering: Put a.
in front of the filename. - Pushing only to
main
, making draft visible for others, discoverable by search: Just exclude from nav. - Using feature branch: not really visible to others, normal workflow where you include the page in the nav where desired
Builtin filters
Filtering, ordering or modifying data using Jinja2 filters is often needed. You can include the code below on your page to help you find all available filters.
Result:abs
, attr
, batch
, capitalize
, center
, count
, d
, default
, dictsort
, e
, escape
, filesizeformat
, first
, float
, forceescape
, format
, groupby
, indent
, int
, join
, last
, length
, list
, lower
, items
, map
, min
, max
, pprint
, random
, reject
, rejectattr
, replace
, reverse
, round
, safe
, select
, selectattr
, slice
, sort
, string
, striptags
, sum
, title
, trim
, truncate
, unique
, upper
, urlencode
, urlize
, wordcount
, wordwrap
, xmlattr
, tojson
,
How this doc site is made
Following the up-to-date configurations that are used to build this site.
FROM python:3.9.10-bullseye as python-base
# Setting up proper permissions, running app not as root
RUN mkdir -p /app \
&& groupadd --gid 1000 -r web \
&& useradd -d /app --uid 1000 -r -g web web \
&& chown web:web -R /app \
&& pip install poetry
ARG BUILD_DIR=/app/site
ENV PATH="/app/.local/bin:$PATH"
WORKDIR /app
USER web
COPY ./poetry.lock ./pyproject.toml ./
RUN poetry install
FROM python-base as development
# Expose MkDocs development server port
EXPOSE 8000
COPY --chown=web:web . .
# ARG cannot be used in CMD, but env vars can
CMD ["poetry", "run", "mkdocs", "serve", "--dev-addr=0.0.0.0:8000"]
FROM development as builder
RUN poetry run python -m pytest examples/
RUN poetry run mkdocs build --strict
FROM nginx:latest as production
COPY --from=builder /app/site /usr/share/nginx/html
To automatically deploy the generated website using a Gitlab CICD, I use the jwilder/nginx-proxy
in combination with a docker-compose.yml
.
version: '3.8'
services:
rd_docs:
image: rd_docs:production
environment:
VIRTUAL_HOST: docs.real-digital.ch
LETSENCRYPT_HOST: docs.real-digital.ch
LETSENCRYPT_EMAIL: for-letsencrypt@pm.me
networks:
- default
- proxy-tier
networks:
default:
proxy-tier:
external:
name: proxy-tier
And the CICD configuration using Gitlab with a small helper script:
#!/usr/bin/env bash
target=${1:-development}
docker build --tag rd_docs:"$target" --target "$target" .
stages:
- build
- deploy
build-job:
stage: build
script:
- ./build.sh production
deploy-job:
stage: deploy
script:
- docker-compose up --force-recreate -d
shutdown-job:
stage: deploy
when: manual
script:
- docker-compose down
Examples
Open external links in new tabs using javascript
Add this javascript code to docs/js/custom.js
and add it to mkdocs.yml
:
function is_external_link(link) {
if (link.href.startsWith('#')) {
return false
}
if (link.href.includes('javascript:')) {
return false
}
if (link.href.includes('mailto:')) {
return false
}
if (link.href.includes('tel:')) {
return false
}
return link.hostname !== window.location.hostname;
}
function init_external_links() {
var links = document.links;
for (var i = 0, linksLength = links.length; i < linksLength; i++) {
if (is_external_link(links[i])) {
links[i].target = '_blank';
}
}
}
init_external_links()
Table Macro
If you want to generate a tables from data (list of lists), you can use the following macro, that also uses the
macro linkify
mentioned above:
{%- macro table_header(headers) %}
| {{ headers | join (' | ')}} |
| {% for i in headers %} - |{% endfor -%}
{%- endmacro -%}
{%- macro table_row(row_data, row_macro=None) -%}
| {%- for item in row_data %} {{ linkify(item) }} | {% endfor %}
{%- endmacro %}
{%- macro table(headers, data) %}
| {{ headers | join (' | ')}} |
| {% for i in headers %} - |{% endfor %}
{% for line in data -%}
| {%- for row in line %} {{ linkify(row) }} | {% endfor %}
{% endfor %}
{%- endmacro %}
Note: The line and whitespace control using -
in Jinja2 directives is important here to get the right formatting.
Erstellt: August 7, 2022