|
| 1 | +# Registering macros/variables/filters in MkDocs-Macros |
| 2 | + |
| 3 | +!!! Info "Important note" |
| 4 | + This is technical documentation for writers of MkDocs Plugins. |
| 5 | + |
| 6 | + |
| 7 | +## Introduction |
| 8 | + |
| 9 | +This is description of how other MkDocs [plugins](https://www.mkdocs.org/dev-guide/plugins/) (see [a list]([existing plugins](https://github.com/mkdocs/catalog))) |
| 10 | +can register their macros and variables with MkDocs-Macros. |
| 11 | + |
| 12 | +There can exist two motivations: |
| 13 | + |
| 14 | +1. Provide additional functionality to the plugin, by providing |
| 15 | + macros, variables and filters, accessible through MkDocs-Macros. |
| 16 | +2. Resolve syntax incompatibility issues, if the plugin uses a syntax |
| 17 | + similar to Jinja2 (typically expressions between `{{` and `}}`). |
| 18 | + |
| 19 | +## Syntax Incompatibility between plugins |
| 20 | + |
| 21 | +### Description of the issue |
| 22 | + |
| 23 | +MkDocs-Macros was written so that it does not interact with other plugins; |
| 24 | +it interacts only with MkDocs' events. |
| 25 | + |
| 26 | +However, there might be a number of reasons why incompatibilities |
| 27 | +could occur. |
| 28 | + |
| 29 | +!!! Note |
| 30 | + The most common one derives from the fact that |
| 31 | + MkDocs-Macros uses the Jinja2 syntax |
| 32 | + (typically expressions between `{{` and `}}`). |
| 33 | + |
| 34 | + For example a plugin might define `foo(x)` as a function, |
| 35 | + which might need a call as `{{ foo(x) }}` in a Markdown page. |
| 36 | + |
| 37 | + The plugin might fail |
| 38 | + (if declared before MkDocs-Macros in the [config file's list of plugins](https://www.mkdocs.org/user-guide/configuration/#plugins), |
| 39 | + because it now sees many calls with the same syntax |
| 40 | + that it can't interpret), |
| 41 | + or might make MkDocs-Macros fail (if declared later), since |
| 42 | + it is very strict and won't accept a non existent macro. |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | +!!! Tip |
| 47 | + Workarounds exist to change the way MkDocs-Macros handles syntax. |
| 48 | + |
| 49 | + They are described in the page on |
| 50 | + [controlling macros rendering](rendering.md). |
| 51 | + |
| 52 | + They can be useful if MkDocs-Macros is used as a secondary plugin. |
| 53 | + They might be inadequate if MkDocs-Macros is considered "core". |
| 54 | + |
| 55 | + |
| 56 | +### Solutions without plugin |
| 57 | + |
| 58 | +- A [macros module](macros.md) is the simplest a fastest solution, |
| 59 | + for solving a specific need that requires a simple function. |
| 60 | +- For a solution across several documentation projects, |
| 61 | + [pluglets](pluglets.md) were introduced so that a developer |
| 62 | + could quickly develop a solution from scratch, that does not |
| 63 | + involve a plugin. Pluglets are macros module easily distributable |
| 64 | + through Pypi. |
| 65 | + |
| 66 | + |
| 67 | +!!! Warning "What about rewriting existing a Plugin as an MkDocs-Macros pluglet?" |
| 68 | + |
| 69 | + This could be a solution. |
| 70 | + |
| 71 | + **However, it might not be convenient or desirable for the author of a |
| 72 | + plugin to rewrite it as pluglet**. |
| 73 | + |
| 74 | + **A solution had to be found for that case.** |
| 75 | + |
| 76 | + |
| 77 | +## How to adapt a plugin to register macros, filters and variables |
| 78 | + |
| 79 | + |
| 80 | +### Theory |
| 81 | + |
| 82 | +Existing MkDocs plugins might find advantage in using MkDocs-Macros's |
| 83 | +framework as a support for their own "macros", if they use the same syntax. |
| 84 | + |
| 85 | +This is done extremely easily, with the use of three methods |
| 86 | +exported by the `MacrosPlugin` class (itself based on `BasePlugin`). |
| 87 | + |
| 88 | +- `register_macros()`, which takes a dictionary of Python functions |
| 89 | + (Callables) as argument. Those functions must return an `str` result, |
| 90 | + or some object that can be converted to that type. |
| 91 | +- `register_variables()`, which takes a dictionary of Python variables |
| 92 | + as argument. |
| 93 | +- `register_filters()`, which takes a dictionary of Jinja2 filters |
| 94 | + as an argument (see [definition in the official documentation](https://jinja.palletsprojects.com/en/3.0.x/templates/#filters)). |
| 95 | + For our purposes,filters are Python callables where the first argument becomes |
| 96 | + implicit (it is considered as the input value before the `|` symbol). |
| 97 | + |
| 98 | + |
| 99 | +!!! Tip "Independence from the declaration order" |
| 100 | + These macros are designed to work independently from the order |
| 101 | + in which MkDocs plugins are declared. |
| 102 | + |
| 103 | + - If the plugin is declared **before** Mkdocs-Macros, then the |
| 104 | + macros/variables/filters will be "kept aside" and registered last, |
| 105 | + at the [`on_config`](https://www.mkdocs.org/dev-guide/plugins/#on_config) event. |
| 106 | + - If the plugin is declared **after** Mkdocs-Macros, then |
| 107 | + the items will be registered immediately. |
| 108 | + |
| 109 | + In both cases, a conflict with a pre-existing macro/variable/filter |
| 110 | + name will raise a `KeyError` exception. |
| 111 | + |
| 112 | + |
| 113 | +### Practice |
| 114 | + |
| 115 | +You want to register those macros/filters/variables |
| 116 | +at the `on_config()` method of your plugin, providing |
| 117 | +the MkDocs-Macros plugin is declared. |
| 118 | + |
| 119 | +Its argument `config` allows you to access the Mkdocs-Macros plugin (`macros`), |
| 120 | +and its three registration methods. |
| 121 | + |
| 122 | +```python |
| 123 | + |
| 124 | +def foo(x:int, y:str): |
| 125 | + "First macro" |
| 126 | + return f"{x} and {y}" |
| 127 | + |
| 128 | +def bar(x:int, y:int): |
| 129 | + "Second macro" |
| 130 | + return x + y |
| 131 | + |
| 132 | +def scramble(s:str, length:int=None): |
| 133 | + """ |
| 134 | + Dummy filter to reverse the string and swap the case of each character. |
| 135 | +
|
| 136 | + Usage in Markdown page: |
| 137 | +
|
| 138 | + {{ "Hello world" | scramble }} -> DLROw OLLEh |
| 139 | + {{ "Hello world" | scramble(6) }} -> DLROw |
| 140 | + """ |
| 141 | + r = s[::-1].swapcase() |
| 142 | + if length is not None: |
| 143 | + r = r[:length] |
| 144 | + return r |
| 145 | + |
| 146 | + |
| 147 | +MY_FUNCTIONS = {"foo": foo, "bar": bar} |
| 148 | +MY_VARIABLES = {"x1": 5, "x2": 'hello world'} |
| 149 | +MY_FILTERS = {"scramble": scramble} |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | +class MyPlugin(BasePlugin): |
| 154 | + "Your existing MkDocs plugin" |
| 155 | + |
| 156 | + ... |
| 157 | + |
| 158 | + def on_config(self, config, **kwargs): |
| 159 | + |
| 160 | + # get MkdocsMacros plugin, but only if present |
| 161 | + macros_plugin = config.plugins.get("macros") |
| 162 | + if macros_plugin: |
| 163 | + macros_plugin.register_macros(MY_FUNCTIONS) |
| 164 | + macros_plugin.register_variables(MY_VARIABLES) |
| 165 | + macros_plugin.register_filters(MY_VARIABLES) |
| 166 | +``` |
| 167 | + |
| 168 | + |
| 169 | + |
0 commit comments