Aller au contenu

Macros personnalisées

Le plugin pyodide_macros hérite de la classe MacrosPlugin du plugin mkdocs-macros-plugin, et les différentes façons de déclarer les macros pour celui-ci restent donc toutes applicables avec le thème.

Il est donc toujours possible d'ajouter vos propres macros en plus de celles définies par le thème :

Contraintes sur les noms des macros personnalisées

Les macros personnalisées ne doivent pas avoir le même nom que l'une de celles définies par le thème (voir ici).

Fichier main.py#

C'est la méthode la plus simple :

  1. Ajouter un fichier main.py Ă  la racine de votre propjet (PAS dans le docs_dir !)
  2. Y déclarer une fonction define_env(env:PyodideMacrosPlugin), dans laquelle déclarer vos macros :
Exemple de fichier `main.py`
from pyodide_mkdocs_theme.pyodide_macros import PyodideMacrosPlugin

def define_env(env:PyodideMacrosPlugin):

    @env.macro
    def macro1(...) -> str :
        return ...

Module de macros#

Si vous avez beaucoup de code/macros personnalisées, il peut être intéressant d'utiliser un package python plutôt qu'un simple fichier main.py, pour obtenir une meilleure organisation du projet :

  1. Créer un dossier à la racine du projet.

  2. Dans le ficher mkdocs.yml, ajouter le nom du dossier de macros personnalisées dans la configuration du plugin :

    Utiliser un package pour les macros personnalisées
    plugins:
        - pyodide_macros:
            module_name: package_name
    


  3. Ajouter un fichier __init__.py dans ce dossier (ceci transforme le dossier en package python).

  4. Ajouter une fonction define_env(env:PyodideMacrosPlugin) Ă  ce fichier __init__.py.
    Toutes les macros doivent être définies depuis l'intérieur de cette fonction, comme pour le fichier main.py.



Les fonctions définissant les macros peuvent aussi être déclarées dans d'autres fichiers de la bibliothèque, puis importées pour être enregistrées en tant que macros.

Il y a de nombreuses approches possibles pour réaliser ceci. En voici une ci-dessous :

  • Import des fonctions depuis le fichier __init__.py
  • EnregistrĂ©es en tant que macros en executant env.macro(function_importĂ©e) depuis l'intĂ©rieur de la fonction define_env.
Exemple de fichier __init__.py
from pyodide_mkdocs_theme.pyodide_macros import PyodideMacrosPlugin
from . import my_file1, my_file2

def define_env(env:PyodideMacrosPlugin):

    env.macro(my_file1.macro1)      # my_file1 contient une fonction "macro1"
    env.macro(my_file1.macro2)
    ...

    # Ou créer les macros directement ici (mais le package n'a alors plus d'intérêt...)
    @env.macro
    def macroX(...):
        ...


Si une de ces macros nécessite d'accéder à la variable env, on peut ruser de différentes façons :

La façon de procéder la plus naturelle, en combinant des fichiers contenant une fonction define_env(env), comme un fichier main.py isolé le ferait :

└── `macros_perso`
    ├── __init__.py
    ├── module1.py
    └── module2.py

module1.py

def define_env(env):

    @env.macro
    def my_macro():
        ...
        return ...

__init__.py

from . import module1, module2

def define_env(env):

    for module in (module1, module2):
        module.define_env(env)

Une alternative, moins naturelle, utilisant des "functions factories". L'intérêt pourrait être de voir les noms de toutes les macros personnalisées depuis le fichier __init__.py.

La structure de fichiers est la mĂŞme:

└── `macros_perso`
    ├── __init__.py
    └── module1.py

module.py

def macro1(env):
    def macro1():
        # impérativement le même nom !
        return ... # avec env
    return macro1 # la fonction interne

__init__.py

from .module import macro1

def define_env(env):

    env.macro( macro1(env) )

Exemples#

Comme dit précédemment, il y a de nombreuses stratégies utilisables pour intégrer ses propres macros à un projet PMT.

L'exemple qui vient naturellement à l'esprit est le module de macros personnalisées du thème lui-même, pmt_macros, mais il a atteint un niveau de complexité telle, que je déconseille de commencer par là.


Voici l'évolution des codes déclarant des macros personnalisées dans la documentation de PMT ou sur CodEx, illustrant différentes stratégies.

Projet Stratégie/intérêt
PMT 1.5.0 Fichier main.py unique.
Simple d'utilisation.
PMT 2.0.0 Module pmt_macros.
Correspond à la "méthode 2" décrite ci-dessus.
CodEx (ancien) Module codex_macros.
Correspond à la "méthode 2" décrite ci-dessus.
CodEx (actuel) Module codex_macros.
Utilise les deux techniques décrites ci-dessus pour enregistrer les macros, avec le fichier __init__.py appelant la fonction define_env d'un sous-module (méthode 1). Cette version est sans doute plus naturelle, venant d'un fichier main.py unique.
pmt_macros Module pmt_macros.
Utilise une logistique entièrement différente, afin de déclarer certaines macros globalement pour une "session" mkdocs serve entière (ceci permet de mettre en cache certains résultats). Pour programmeurs avertis seulement, car il est très facile de se retrouver avec des problèmes de données invalides en cache.

Configuration#

Toutes les options du plugin original des macros sont disponibles directement sur le plugin pyodide_macros.
Pour plus de détails, voir :

Indentation et macros multilignes #

Pour les macros qui doivent insérer du contenu multilignes, il est important de pouvoir savoir à quel niveau d'indentation l'appel de la macro en cours a été fait.

Par exemple, pour une macro admo(lst:List[str]) qui générerait une liste de puces dans une admonition, on souhaite obtenir le résultat suivant :

Rendu markdown souhaité

!!! tip ""
    * chat
    * chien
    * chaud

??? tip "indenté"
    !!! tip ""
        * chat
        * chien
        * chaud

Fichier markdown source

{{ admo(["chat", "chien", "chaud"]) }} 

??? tip "indenté"
    {{ admo(["chat", "chien", "chaud"]) }} 

 
 
 
 
 

Pour réaliser cela, il faut que la macro sache combien d'espaces elle doit ajouter au début de chaque ligne.


Pyodide-mkdocs-theme propose une fonctionnalité permettant d'indenter automatiquement le contenu généré par votre macro avec le bon niveau d'indentation : env.indent_macro(contenu).

Cette fonctionnalité peut-être étendue aux macros personnalisées, en procédant de la façon suivante :

  1. Ajouter le nom de la macro dans la configuration du plugin du thème, dans build.macros_with_indents :

    plugins:
        - pyodide_macros:
            build:
                macros_with_indents:
                    - admo
    


  2. Implanter la macro dans votre fichier main.py ou votre module de macros personnalisées (voir en haut de cette page), en omettant la logique d'indentation dans un premier temps :

    from typing import List
    from pyodide_mkdocs_theme.pyodide_macros import PyodideMacrosPlugin
    
    def define_env(env:PyodideMacrosPlugin):
    
        @env.macro
        def admo(lst:List[str]):
            list_items = '!!! tip ""' + ''.join( f'\n    * { item }' for item in lst )
            return list_items
    


  3. Intégrer au code de la macro l'appel à la méthode env.indent_macro(content:str), qui prend en argument le contenu markdown non indenté, et renvoie le contenu indenté de manière appropriée. :

        @env.macro
        def admo(lst:List[str]):
            list_items = '!!! tip ""' + ''.join( f'\n    * { item }' for item in lst )
            indented   = env.indent_macro(list_items)
            return indented
    


Les appels de macro dans l'exemple ci-dessus générerait en l'occurrence les chaînes suivantes :

  • Appel 1 :

    '!!! tip ""\n    * chat\n    * chien\n    * chaud'
    

  • Appel 2 :

    '!!! tip ""\n        * chat\n        * chien\n        * chaud'
    

Notez que dans la sortie indentée, la première ligne ne doit contenir aucune indentation, puisque l'appel de macro lui-même est déjà indenté dans le fichier markdown source.

Utilisation des macros référencées dans build.macros_with_indents

  • Il ne doit pas y avoir d'autres caractères que des espaces Ă  gauche de ces appels de macros, dans les fichiers markdown.

  • PyodideMacrosTabulationError est levĂ©e si des caractères de tabulation sont trouvĂ©s dans les indentations Ă  gauche de ces appels de macros.

    Il est possible de contourner cette erreur en utilisant l'option build.tab_to_spaces du plugin, pour remplacer automatiquement les caractères de tabulation.
    Garder en tête que la validité des indentations obtenues n'est alors plus garantie.

ContrĂ´le "plus fin" (?) de l'ajout des indentations

S'il s'avérait nécessaire d'exercer un contrôle plus poussé sur l'insertion des indentations, ou de stocker l'information pour une utilisation ultérieure lors du processus de construction du site, la méthode env.get_macro_indent() renvoie la chaîne de caractères avec le bon nombre d'espaces à utiliser au début de chaque ligne, sauf la première, pour indenter les contenus multilignes.

La macro ci-dessus pourrait alors être écrite de la façon suivante :

    def admo(lst:List[str]):
        list_items = '!!! tip ""' + ''.join( f'\n    * { item }' for item in lst )
        indent     = env.get_macro_indent()
        return list_items.replace('\n', '\n'+indent)