Writing modules
Introduction
Modules are libraries of macros, filters and variables, which can be used by your MkDocs project.
Every module MUST contain a
define_env()
function,
which contains the declarations.
Location of the modules
Local module
By default, the Python code must go into one main.py
file in the main
website's project directory (generally beside the mkdocs.yml
file).
If no main
module is available, this is ignored.
If you wish, you can change the name of that module by adding a
module_name
entry to the mkdocs.yml
file (no need to add the .py
suffix):
plugins:
...
- macros:
module_name: source_code
** If you specify a module name, it must be available, or this will raise an error.**
Note
If you wish, you can implement your module as a package (subdirectory) instead of a single file.
Preinstalled modules (pluglets)
If you wish to reuse modules across several documentation projects, you may want to pre-install them, turning them into pluglets.
The define_env()
function
Note
New, as of version 0.3.0
As a first step, you need declare a hook function called define_env
,
with one argument: env
(object).
This object contains the environment (variables, filters, etc.) of the
templating tool (Jinja2).
This is the information that will be used to generate the pure Markdown pages, which will then be translated into HTML (and displayed in a browser).
Registration of variables, macros and filters
The example should be self-explanatory:
"""
Basic example of a Mkdocs-macros module
"""
import math
def define_env(env):
"""
This is the hook for defining variables, macros and filters
- variables: the dictionary that contains the environment variables
- macro: a decorator function, to declare a macro.
- filter: a function with one of more arguments,
used to perform a transformation
"""
# add to the dictionary of variables available to markdown pages:
env.variables['baz'] = "John Doe"
# NOTE: you may also treat env.variables as a namespace,
# with the dot notation:
env.variables.baz = "John Doe"
@env.macro
def bar(x):
return (2.3 * x) + 7
# If you wish, you can declare a macro with a different name:
def f(x):
return x * x
env.macro(f, 'barbaz')
# or to export some predefined function
env.macro(math.floor) # will be exported as 'floor'
# create a jinja2 filter
@env.filter
def reverse(x):
"Reverse a string (and uppercase)"
return x.upper()[::-1]
env
object does all the 'magic').
Tip
You can export (as variables, macros or filters)
a wide range of objects, and their attributes
will remain accessible to the jinja2 template via the standard Python
convention, e.g. {{ foo.bar }}
(see more
information)
Definition of variables/macros/filters
- You register a variable for MkDocs-macros
by adding a key/value pair
to the
env.variables
dictionary (or namespace). Variables are loaded with each page being rendered. - You register a macro by decorating a function
with the expression
@env.macro
(or by adding it to theenv.macros
dictionary).
Macros are loaded in the global namespace of the Jinja2 environment.1. - You register a filter by decorating a function
with the expression
@env.filter
(or by adding it to theenv.filters
dictionary).
This must be done within that define_env()
function.
You may, however, place any imports or other declarations
outside of the function.
Priority of variables
Warning
In case of conflict, variables declared in the Python
module will override
those created by users in YAML files (extra
).
This is a safety feature, to ensure that the maintainers of that file
will not accidentally break the setup defined
by programmers in the module.
Conversely, keep that fact in mind,
if users start complaining that an extra
value has a different value than the one which they expected!
Content of the env object
Description
The env
object is used for introspection, i.e. is to get information
on the project or page.
Here is a list of commonly needed attributes (constants) or functions of that object:
Item | Type | Description |
---|---|---|
variables |
attribute | The namespace that contains the variables and macros that will be available in markdown pages with {{ ... }} notation. This dictionary is initialized with the values contained in the extra section of the configuration file (and optionally, with external yaml files). This object is also accessible with the dot notation; e.g. env.variables['foo'] is equivalent to env.variables.foo . |
macro |
function | A decorator function that you can use to declare a Python function as a Jinja2 callable ('macro' for MkDocs). |
filters |
attribute | A list list of jinja2 filters (default None) |
filter |
function | A decorator for declaring a Python function as a jinja2 custom filter |
project_dir |
attribute | The source directory of the MkDocs project (useful for finding or including other files) |
conf |
attribute | The content of the config file (mkdocs.yaml ). |
config |
attribute | This can be a useful object; it contains the global context for MkDocs]2. |
page |
attribute | The information on the page being served (such as the title, etc.). For more information on its content, see MkDoc's description of the page object. |
Technical Note
env
is essentially an instance of a subclass of the
MkDocs' BasePlugin
class, with some additional properties.
Whatever you find in the BasePlugin
class, you will find in the
the env
object.
Accessing the whole config file (mkdocs.yaml
)
Sometimes, you might need information from the whole config file
(mkdocs.yaml
), e.g. site_description
, theme
, copyright
, etc.
The property conf
of the env
object contains that information.
For example you could define such a function:
@env.macro
def site_info():
"Return general info on the website (name, description and theme)"
info = (env.conf['site_name'], env.conf['site_description'],
env.conf['theme'].name)
return "%s/%s (theme: %s)" % info
Beware the change of name
Beware that the what is usually called config
is allied env.conf
in the module. That is is because there is already env.config
property as part of the BasePlugin
class.
Indeed, you will also find the same object under env.variables.config
;
in other words, it will be thus be accessible as {{ config }}
within the markdown pages.
Tip
In order obtain the documents directory (docs
), you can
use, within the Python module, the value: env.conf['docs_dir']
.
Manipulating the MkDocs configuration information
env.config
is the object containing the global context for mkdocs,
i.e. the data structures that are being manipulated to create the final
HTML web site.
You would have to explore it
(using the MkDocs documentation on the global context),
but it contains the navigation (env.conf['nav']
), as well
as all objects that could be manipulated.
Note
env.config
is thus a superset of the env.conf
object
(which is env.config['config']
).
Caution
This object is not accessible as a variable from the markdown pages. Exposing it might encourage black magic.
Validating environment variables in Python code
By design, the call to define_env()
is the last stage of the config
process, to create the jinja2 environment that will interpret the jinja2
directives inserted in the markdown code.
It means in particular, that you can test the variables dictionary to validate its key/values, and to take appropriate action.
For example, to check that root branches are present in the variables tree:
MINIMAL_BRANCHES = ('foo', 'bar', 'baz')
def define_env(env):
"""
This is the hook for defining variables, macros and filters
...
"""
# initial checks
for branch in MINIMAL_BRANCHES:
if branch not in env.variables:
raise KeyError("Branch '%s' is not in environment variables! ")
Tip
This is a place where you could check that you code will not conflict with variables defined in the configuration files.
You may also verify other aspects of the configuration file
(env.conf
). Note that the attributes of the pluging->macro
branch
are automatically checked by mkdocs (type and default value).
List of hook functions within a module
define_env()
is not the only possible hook within a module.
There are other functions available. Each is triggered by a MkDocs event.
Function | Description | Typical Use | Triggered by MkDoc's event |
---|---|---|---|
define_env(env) |
Main function | Create macros, filters, etc. | on_config |
on_pre_page_macros(env) |
Executed just before the Jinja2 directives (markdown page) have been rendered | Directly modify a markdown page | on_page |
on_post_page_macros(env) |
Executed just after the Jinja2 code (markdown page) have been rendered | Directly modify a markdown page | on_page |
on_post_build(env) |
Executed after the html pages have been produced | Add files to the website | on_post_build |
declare_variables(variables, macro) |
Main function | Deprecated (< version 0.3.0) | on_config |
Implementing the module as a package (module subdirectory)
Instead of implementing the Python module as a file (typically main.py
),
you can create a package (i.e. a main
subdirectory).
The Python rules for defining a package apply: particularly
the dot (.
) prefix for relative imports inside the package.
The define_env()
function should be accessible
through the __init__.py
file.
A typical directory file could look like this:
main
├── __init__.py
├── util.py
├── ...
└── ...
The __init__.py
file could look like this:
from .util import foo, bar
def define_env(env):
"""
This is the hook for defining variables, macros and filters
"""
@env.macro
def make_foo():
.......
return foo(s)
@env.macro
def get_bar(s):
s = bar(s, ...)
return s
...
Notes on Modules
A caution about security
Warning
It is true that you are generating static pages.
Nevertheless, think about potential side effects of macros (in case of error or abuse) or about the risks of exposing sensitive information, if the writers of markdown pages are different persons than the maintainers of the webserver.
Depending on your use case, you may want to give access to the shell (e.g. for a development team). Or else, may you want to "sandbox" your web pages (for business applications).
What you can and can't do with define_env()
The fact is that you cannot actually access page information
in the define_env()
function, since
it operates at the configuration stage of the page building process
(during the on_config()
event of MkDocs).
At that point, you don't have access to specific pages
Vital Note on mkdocs-macros
Of course, you can declare macros, which appear to act on pages.
But realize that these are only declarations and that their
execution is deferred.
The macros will actually be run ** later**
(by MkDocs' on_page_markdown()
event),
just before the markdown is rendered. The framework is so organized
that, in macros, you are actually "talking" about objects that don't exist yet.
So you cannot influence the rendering process other than by
adding macros, variables and filters to mkdocs_macros
.
Do not modify system entities in env.variables
Also, the system information in env.variables
is for reading purposes.
You could modify it in your Python code, of course (at your own peril).
But by design, it may have no effect on the mechanics
of mkdocs
(these are shallow copies).
Whatever you do in that way, is likely to be branded black magic.
Hook scripts (standard) versus MkDocs-Macros Modules
Similarities
Hook scripts (offered as standard by MkDocs) and modules provided by MkDocs-Macros are python programs that operate on the same principle : they use hooks, which are special predefined functions that will be called at specific point of the code, so that you can customize the behavior of the software according to your needs.
Those scripts take a special object as an argument:
config
for MkDocs hook scriptsenv
for Mkdocs-Macros modules
Differences
Their purpose is, however, very different. To explain this in a simple way:
-
You can think of a standard hook script as built over a barebone plugin that doesn't do anything more than MkDocs, except providing those hooks.
-
An MkDocs-Macros module is exploiting the full power of the MkDocs-Macros plugin, which relies on its Jinja2 templating engine and the manipulation of macros, variables and filters.
A standard hook script is usually called hooks.py
, while
an Mkdocs-Macros module is called main.py
by default, though you can
change these names if you wish.
-
From version 0.5.10. Before that, macros were inserted in
env.variables
. ↩ -
env.config
versusenv.conf
: it is unhappy thatenv.config
represents MkDocs whole context, whereasenv.conf
represents only theconfig
(YAML) file (a subset). This ambiguity was born from the fact that MkDoc itself usedconfig
to represent the context, as a property of theBasePlugin
object. ↩