Org-mode: Index dynamique de documents Org-mode
Dans un précédent billet, on a vue comment utiliser les blocs dynamiques d'Org-mode et comment créer un nouveau type de bloc.
Maintenant, je vais montrer comment utiliser tout ça pour créer un
nouveau type de bloc dynamique: index
. Ce type de bloc génère un
indexe de documents Org-mode.
Avant de commencer
Dans ce billet de blog, je vais analyser du code. Si tu n'es pas intéressé par cette analyse, mais que tu souhaite seulement utiliser ce nouveau type de bloc pour créer des indexes: Tu peux sauter à la section TL;DR.
Pour comprendre l'analyse, il est important:
- D'avoir lu le précédent billet sur les blocs dynamiques
- D'avoir quelques notions de programmation
- Connaitre un minimum le
Emacs-Lisp
(Elisp
), notamment comment est déclaré une fonction et comment elle peut retourner une valeur
Bonne lecture.
Objectif
Imagine: Tu as un dossier dans lequel se trouve des documents Org-mode et tu voudrais avoir, dans un autre document, un index de ce dossier.
Pour y arriver, on va créer un nouveau type de bloc:
- Nommé
index
- Il va généré une liste, au format Org-mode, de tout les fichiers Org-mode présents dans un dossier
- Le dossier parcouru pourra être indiqué avec le paramètre
:directory
, sinon le dossier courant sera utilisé - Le formatage de chaque élément de la liste pourra être personnalisé
avec le paramètre
:format
- Optionnellement, les fichiers trouvés pourront être triés selon leur
mot-clé
DATE
, du plus récent au plus ancien, avec le paramètre:sort-recent
Formatage des éléments de la liste
Pour le formatage des éléments de la liste, on pourra indiquer une
chaine de caractère avec le paramètre :format
. Cette chaine sera
utilisée pour chacun des éléments.
Dans cette chaine de caractère, on aura accès à des gabarits, comme le titre, la description ou le chemin vers le fichier. Certains de ces gabarits reprennent la valeur des mots-clés présents dans les documents Org-mode trouvés.
Les gabarits suivants seront utilisable:
gabarit | Mot-clé Org-mode | description |
---|---|---|
%t |
TITLE | Titre |
%d |
DATE | Date |
%a |
AUTHOR | Auteur ou autrice |
%c |
CREATOR | Créateur ou créatrice |
%D |
DESCRIPTION | Description |
%p |
Chemin vers le fichier | |
%P |
Chemin absolue vers le fichier | |
%l |
LANGUAGE | Langue |
%o |
OPTIONS | Options |
Exemple de chaine de formatage:
"%d: [[%p][%t]]"
Et voici ce que donnerait cette exemple une fois formaté:
2023.07.05: Org-mode: Dynamic block
Exemple d'utilisation d'un bloc index
Voici un exemple d'utilisation d'un bloc de type index
:
#+BEGIN index :directory "posts/" #+END
Et voici un exemple de ce que le bloc va générer:
#+BEGIN: index :directory "posts/" - 2023.07.05 : Org-mode: Dynamic block - 2023.07.03 : Org-mode: Dupliquer une note - 2023.06.06 : Fedora: OSTree Native Container - 2023.05.09 : Org-noter - 2023.04.30 : Ouverture #+end
TL;DR
Pour créer le nouveau type de bloc dynamique index
, il faut copier
ce code dans ~/.emacs.d/init.el
:
(defun my/org-index-extract-infos (file-path) "Extract, from one file, all the info needed for building an index" (with-temp-buffer (insert-file-contents file-path) (append (org-element-map (org-element-parse-buffer) 'keyword (lambda (keyword) (list (org-element-property :key keyword) (org-element-property :value keyword)))) (list (list "FILE-PATH" file-path) (list "FILE-FULL-PATH" (expand-file-name file-path)))))) (defun my/org-index-list-files-path (directory) "Get relative path of all Org-mode document of a directory" (mapcar (lambda (file-name) (concat directory file-name)) (directory-files directory nil ".org$"))) (defun my/org-index-sort-documents (documents-list) "Sort an Org-documents list by its date" (sort documents-list (lambda (file-a file-b) (string> (car (cdr (assoc '"DATE" file-a))) (car (cdr (assoc '"DATE" file-b))))))) (defun my/org-index-format-element (document-infos fmt) "Generate a string with document infos" (format-spec fmt (list (cons ?t (car (cdr (assoc '"TITLE" document-infos)))) (cons ?d (car (cdr (assoc '"DATE" document-infos)))) (cons ?a (car (cdr (assoc '"AUTHOR" document-infos)))) (cons ?c (car (cdr (assoc '"CREATOR" document-infos)))) (cons ?D (car (cdr (assoc '"DESCRIPTION" document-infos)))) (cons ?p (car (cdr (assoc '"FILE-PATH" document-infos)))) (cons ?P (car (cdr (assoc '"FILE-FULL-PATH" document-infos)))) (cons ?l (car (cdr (assoc '"LANGUAGE" document-infos)))) (cons ?o (car (cdr (assoc '"OPTIONS" document-infos))))))) (defun org-dblock-write:index (params) "Generate an index of a director, as a list" (let ((directory (or (plist-get params :directory ) (file-name-directory (buffer-file-name)))) (sort-recent (plist-get params :sort-recent)) (fmt (or (plist-get params :format) "%d: [[file:%p][%t]]")) (documents-infos nil)) (if sort-recent (setq documents-infos (my/org-index-sort-documents (mapcar 'my/org-index-extract-infos (my/org-index-list-files-path directory)))) (setq documents-infos (mapcar 'my/org-index-extract-infos (my/org-index-list-files-path directory)))) (mapcar (lambda (one-document-infos) (insert "- " (my/org-index-format-element one-document-infos fmt) "\n")) documents-infos))) (defun my/org-index-insert-dblock (directory) "Insert a new dynamic bloc of type index" (interactive (list (string-replace (file-name-directory (buffer-file-name)) "" (expand-file-name (read-directory-name "Dirictory to build index on: "))))) (org-create-dblock (list :name "index" :directory directory))) (org-dynamic-block-define "Index" 'my/org-index-insert-dblock)
Fonctionnement interne du bloc
Dans la section "Objectifs", on a passé en revue le fonctionnement du nouveau type de bloc du point de vue extérieur. On a définit quel texte il devait générer, quels paramètres il devait accepter et quels effets auront ces paramètres.
Maintenant, on doit définir son fonctionnement interne. Et pour commencer: De quels informations, en plus des paramètres, notre nouveau type de bloc aura besoin.
Stockage des informations de chaque document Org-mode trouvé
Pour chaque fichiers trouvés dans le dossier parcouru, il faudra récupérer les mots-clés ainsi que le chemin vers le fichier. On doit donc générer, pour chaque document Org-mode, une liste d'informations.
Ici, j'ai choisi le format associated list
, ou alist
. Il s'agit
d'une liste, où chaque élément est lui-même une liste dans laquelle on
trouve 2 éléments. Le premier élément sert de clé, le second de valeur
associée. D'où le nom associated list
.
Voici un exemple de alist
:
'(("TITLE" "Titre de mon document") ("DATE" "2023.07.08"))
Je vais donc créer une première fonction Emacs-Lisp
, dont le but
sera d'extraire les informations d'un seul fichier. Cette fonction
nous retournera ces informations sous la forme d'une alist
. Sa
signature sera: (my/org-index-extract-infos file-path)
. Son seul
paramètre d'entrée, file-path
, est une chaine de caractère
représentant le chemin vers le fichier.
- Fonction:
(my/org-index-extract-infos file-path)
- Paramètres:
file-path
: Chemin vers le fichier à analyser
- Retour:
- Une
alist
de toutes les informations extraites du document analysé
- Une
Lister les documents Org-mode trouvés
Pour la deuxième fonction, je vais en créer une qui retournera la
liste des fichiers Org-mode trouvés dans un dossier. Sa signature est:
(my/org-index-get-file-paths-list directory)
. Son seul paramètre,
directory
, est le chemin vers le dossier à parcourir. Sous forme de
chaine de caractère.
- Fonction:
(my/org-index-get-file-paths-list directory)
- Paramètres:
directory
: Chemin vers le dossier à parcourir
- Retour:
- Une liste de chemins, sous la forme d'une chaine de caractère
Cette fonction ne donnera que le chemin vers chaque document Org-mode.
Mais je pourrais utiliser la fonction (mapcar)
, pour exécuter la
fonction d'extraction sur chaque chemin retourné par la fonction de
parcours.
Trie selon la date
Il me faudra aussi une fonction capable de trier la liste de plusieurs
alist
, selon le critère de trie définit dans la section "Objectif"
de ce billet.
- Fonction:
(my/org-index-sort-documents documents-list)
- Paramètres:
documents-list
: La liste des documents Org-mode, sous la forme d'une liste de plusieursalist
- Retour:
- La liste des documents Org-mode, triés selon la date
Formatage du contenu du bloc
Pour rappelle, le bloc dynamique devra générer une liste, au format Org-mode, de tout les documents Org-mode trouvés.
Mais chaque éléments de cette liste devra être formaté en utilisant une chaine de caractère. (Voir section Formatage des éléments de la liste).
Pour ça, je dois créer une fonction nommée
(my/org-index-format-element document-alist format-str)
qui
s'occupera de formater une chaine de caractère. Le premier argument
est une alist
de toutes les informations sur un seul document. Le
second argument est la chaine de caractère utilisée pour le formatage.
- Fonction:
(my/org-index-format-element document-infos fmt)
- Paramètres:
document-infos
: Les information d'un document Org-mode trouvé, au formatalist
fmt
: La chaine de caractère utilisée pour le formatage
- Retour:
- Une chaine de caractère formatée, à insérer dans le bloc dynamique
Le bloc dynamique
Ensuite, je vais créer la fonction (org-dblock-write:index params)
,
qui va insérer le contenu du bloc dynamique. Elle utilisera les
fonctions précédemment écrites, pour récupérer toutes les informations
nécessaires sur chacun des fichiers Org-mode trouvés dans le dossier
parcouru. Elle utilisera ensuite les fonctions (mapcar)
, (insert)
et (format-spec)
pour générer le contenue du block, à partir des
informations extraites et de la chaine de formatage.
En enfin, j'intégrerai le nouveau type de bloc avec la fonction
org-dynamic-block-insert-dblock
.
Préparations
Pour cette article, je t'invite à créer un dossier de travail afin de pouvoir y tester ton code.
Crée simplement un dossier, nommé par exemple index-test/
. Dans ce
dossier, créer un fichier index-test.el
ainsi qu'un dossier
posts/
. Le code du nouveau type de bloc sera écrit dans le fichier
.el
. Et dans le dossier posts/
, on va placer 2 fichiers Org-mode.
Dans le dossier posts/
, crée 2 fichiers Org-mode auxquels tu aura
définit quelques mots-clés. Définit au moins les mots clés suivants:
TITLE
, DATE
et AUTHOR
. Par exemple comme ceci:
#+TITLE: Article de test 1
#+AUTHOR: Moi
#+DATE: 2023.06.06
Pour la date, je conseil d'utiliser le format AAAA.MM.DD
, où AAAA
est l'année, MM
le mois sur 2 nombres et DD
le jour sur 2 nombres
aussi.
Tu peux maintenant ouvrir le fichier index-test.el
avec Emacs et
commencer l'écriture du code.
Écriture du nouveau type de bloc
Dans cette section, je vais suivre le format suivant: D’abord j'écris
le code qu'il faut recopier dans index-test.el
, puis je explique son
fonctionnement partie par partie.
Et là première ligne de code qu'on va ajouter au fichier index-test.el
est la suivante:
(require 'org-element)
La fonction (require)
va simplement charger une fonctionnalité si
elle n'est pas déjà chargée. Ici on charge org-element
, qui nous
fourni une API pour manipuler des fichiers Org-mode.
Récupération des mots-clés d'un fichier
Voici la fonction, (my/org-index-extract-infos)
, qui va récupérer
toutes les informations utiles d'un seul fichier Org-mode:
(defun my/org-index-extract-infos (file-path) "Extract, from one file, all the info needed for building an index" (with-temp-buffer (insert-file-contents file-path) (append (org-element-map (org-element-parse-buffer) 'keyword (lambda (keyword) (list (org-element-property :key keyword) (org-element-property :value keyword)))) (list (list "FILE-PATH" file-path) (list "FILE-FULL-PATH" (expand-file-name file-path))))))
Le paramètre d'entrée est file-path
, le chemin vers le fichier à
traiter. Le retour est une alist
représentant toutes les
informations extraites du fichier.
Il faut l'écrire dans le fichier index-test.el
.
Maintenant, une explication du code.
Exécution dans un buffer temporaire
Pour commencer, notre fonction va appeler la fonction
(with-temp-buffer)
. Cette dernière va simplement créer un buffer
Emacs temporaire. Toutes les instructions Elisp
donnés en paramètre à
(with-temp-buffer)
seront exécutées depuis ce buffer.
Son utilisation est:
(with-temp-buffer
(call_1)
(call_2)
…)
Il faut remplacer call_1
et call_2
par des fonction qu'on souhaite
appeler.
Remplissage du buffer temporaire
La première fonction exécutée dans ce buffer temporaire est
(insert-file-contents)
. Elle va remplir le buffer courant avec le
contenu du fichier indiqué par le premier paramètre. Ici, on lui donne
le chemin reçu par le paramètre file-path
. Après exécution, le
buffer temporaire contiendra tout ce qui se trouve dans le fichier
qu'on traite.
Concaténation de 2 listes
La deuxième fonction est (append)
, qui nous sert à concaténer 2
alist
. La première contient tous les mots-clés Org-mode extraits à
partir du contenu. La seconde contient le chemin relatif vers le
fichier et le chemin absolue.
Extraction des mots clés du document Org-mode
La liste des mots clés est obtenu avec ce code:
(org-element-map (org-element-parse-buffer) 'keyword (lambda (keyword) (list (org-element-property :key keyword) (org-element-property :value keyword))))
Dans ce bout de code, on utilise la fonction
(org-element-parse-buffer)
. Cette fonction analyse le buffer courant
et retourne un arbre de tout les éléments trouvés.
On utilise également la fonction (org-element-map)
. Cette fonction
sert à exécuté une fonction sur chaque élément d'un certain type
présent dans un arbre d'analyse. On lui passe 3 arguments:
- Un arbre d'analyse de notre document Org-mode, obtenu par l'appel à
(org-element-parse-buffer)
- Le type d'élément pour lequel on souhaite effectuer un traitement,
ici
'keyword
- Une fonction anonyme (
lambda
), qui sera exécutée pour chaque élément trouvé
La fonction anonyme (lambda
) reçoit un seul paramètre. Il s'agit
d'une property-list
(plist
) dans laquelle se trouvent les
informations sur l’élément actuellement traité.
Si dans le documents Org-mode se trouvent 4 mots-clés (keywords
),
alors notre fonction anonyme sera appelée 4 fois.
Voici un exemple de plist
que la fonction reçois:
(:key "Keyword_name" :value "Keyword_value")
Notre fonction anonyme va simplement récupérer les 2 valeurs de la plist
et
reconstruire une cellule de alist
avec.
Par exemple, si elle reçoit ceci:
(:key "AUTHOR" :value "Moi")
Elle va retourner ceci:
("AUTHOR" "Moi")
Tous les retours des appelles à la fonction anonymes seront ajoutés à
une liste, qui sera retournée par la fonction (org-element-map)
après exécution.
Appel à cette fonction
Pour tester cette nouvelle fonction, tu peux l'exécuter comme ceci:
(my/org-index-extract-infos "posts/article1.org")
Il faut remplacer "posts/article1.org"
par le chemin relatif à un de
tes document Org-mode présent dans le dossier posts/
.
Si tout fonctionne, elle devrait te retourner une alist
avec tout
les mots clés présent dans ton document de test, ainsi que le chemin
relatif et absolue vers document.
Si tu veux tester la fonction, le chemin que tu indique est relatif au dossier de travail d'Emacs. Et le dossier de travail d'Emacs dépends du buffer sur lequel tu travail.
Récupération de la liste des fichiers org-mode d'un dossier
Voici la fonction, (my/org-index-list-files-path)
, qui va trouver
tous les documents Org-mode d'un dossier:
(defun my/org-index-list-files-path (directory) "Get relative path of all Org-mode document of a directory" (mapcar (lambda (file-name) (concat directory file-name)) (directory-files directory nil ".org$")))
Cette fonction reçoit comme paramètre le chemin vers le dossier à parcourir. Elle retourne une liste de chemins, un vers chaque document Org-mode trouvé.
Il faut l'écrire dans le fichier index-test.el
.
Cette fonction va appeler (directory-files)
en lui indiquant:
- Le dossier à parcourir (premier paramètre)
- De ne pas retourner des chemins absolues (deuxième paramètre)
- La regex à utiliser pour trouver les fichiers qui nous intéressent (troisième paramètre)
La fonction (directiory-files)
retourne les noms des fichiers
trouvés, sous la forme d'une liste de chaines de caractères. Mais on
souhaite avoir une liste de chemins vers chaque documents, pas
uniquement les noms des fichiers.
C'est pour cela qu'on utilise (mapcar)
: Pour chaque nom de fichier
trouvés par (directory-files)
, on exécute la fonction anonyme
suivante:
(lambda (file-name)
(concat directory file-name))
Cette fonction va simplement concaténer le chemin vers le dossier parcourus et le nom du fichier trouvé.
Le résultat de chaque exécution de la fonction anonyme est ajouté à
une liste retournée par (mapcar)
. Et c'est cette liste que notre
fonction retourne.
Trie en fonction de la date
Voici la fonction de trie, (my/org-index-sort-documents documents-list)
:
(defun my/org-index-sort-documents (documents-list) "Sort an Org-documents list by its date" (sort documents-list (lambda (file-a file-b) (string> (car (cdr (assoc '"DATE" file-a))) (car (cdr (assoc '"DATE" file-b)))))))
Elle accepte une liste de alist
et retourne la liste triée. Elle
utilise la fonction (sort)
, à laquelle on a passé 2 paramètres: La
liste à trier et une fonction utilisée pour faire la comparaison
entre 2 éléments à trier.
Il faut l'écrire dans le fichier index-test.el
.
La fonction de trie est une fonction anonyme et ressemble à ceci:
(lambda (file-a file-b) (string> (car (cdr (assoc '"DATE" file-a))) (car (cdr (assoc '"DATE" file-b)))))
Elle accepte comme paramètres les 2 éléments à comparer et utilise la
fonction (string>)
pour comparer les dates. Ici, pour des raisons de
simplification, on va traiter les dates comme de simples chaines de
caractères.
Pour extraire la date de file-a
et file-b
, qui sont des alist
,
on utilise la fonction (assoc)
. En indiquant la clé '"DATE"
,
(assoc)
nous renvoie la cellule dont le premier élément est "DATE".
Ce qui nous donne, par exemple: ("DATE" "2023.07.08")
. On utilise
ensuite (cdr)
pour extraire le deuxième élément de la cellule.
(cdr)
retourne toujours la liste qu'on lui donne, sans le premier
élément. Dans le cas d'une liste de 2 éléments, il nous retourne donc
une liste d'un seul élément. C'est pour ça qu'on utilise (car)
, pour
extraire ce seul élément de la liste.
Formatage d'un élément de la liste
Voici le code de la fonction (my/org-index-format-element
document-infos fmt)
:
(defun my/org-index-format-element (document-infos fmt) "Generate a string with document infos" (format-spec fmt (list (cons ?t (car (cdr (assoc '"TITLE" document-infos)))) (cons ?d (car (cdr (assoc '"DATE" document-infos)))) (cons ?a (car (cdr (assoc '"AUTHOR" document-infos)))) (cons ?c (car (cdr (assoc '"CREATOR" document-infos)))) (cons ?D (car (cdr (assoc '"DESCRIPTION" document-infos)))) (cons ?p (car (cdr (assoc '"FILE-PATH" document-infos)))) (cons ?P (car (cdr (assoc '"FILE-FULL-PATH" document-infos)))) (cons ?l (car (cdr (assoc '"LANGUAGE" document-infos)))) (cons ?o (car (cdr (assoc '"OPTIONS" document-infos)))))))
Pour rappelle, cette fonction accepte 2 paramètres: Les informations
sur un document (document-infos
), au format alist
, et la chaine de
caractère servant au formatage. Elle retourne la chaine de caractère
formatée.
Il faut l'écrire dans le fichier index-test.el
.
Pour le formatage, on utilise la fonction (format-spec)
avec 2
paramètres: La chaine de caractère de formatage et la liste des
gabarits.
La liste de gabarits est une alist
, où les cellules utilisent un
caractère .
pour séparer la clé et la valeur.
Exemple de liste de gabarit :
'((?t . "Title of document") (?a . "Name of the author"))
On doit construire cette liste et associer chaque informations extraites du document avec une lettre de gabarit.
Construction de la liste des gabarits
Pour rappelle, la liste de gabarits doit ressembler à ceci:
'((?t . "Title of document") (?a . "Name of the author"))
On doit remplacer les chaines de caractère de cette exemple par les
informations extraites du document Org-mode. Ces informations se
trouvent dans la variable document-infos
.
Pour chaque gabarit, on construit une cellule de alist
en utilisant
la fonction (cons)
. Cette fonction accepte 2 paramètres: La clé et
la valeur de la cellule.
Exemple, si on exécute ceci:
(cons "Author" "Me")
On obtiens cette cellule en retour:
("Author" . "Me")
Dans notre liste de gabarit, on va donc associer une lettre avec une
valeur issue des informations sur le document. Pour extraire une
information de la variable document-infos
, on utilise (assoc)
,
(cdr)
et (car)
. Je te renvoie à la section Trie en fonction de la
date pour le détail de leur utilisation.
Chaque cellule construite est ajoutée à une liste en utilisant la
fonction (list)
.
Le code du bloc dynamique
On arrive presque à la fin. Accroche toi, c'est bientôt terminé.
Voici le code de la fonction (org-dblock-write:index)
, qui sert à
écrire le contenu d'un bloc de type index
:
(defun org-dblock-write:index (params) "Generate an index of a director, as a list" (let ((directory (or (plist-get params :directory ) (file-name-directory (buffer-file-name)))) (sort-recent (plist-get params :sort-recent)) (fmt (or (plist-get params :format) "%d: [[file:%p][%t]]")) (documents-infos nil)) (if sort-recent (setq documents-infos (my/org-index-sort-documents (mapcar 'my/org-index-extract-infos (my/org-index-list-files-path directory)))) (setq documents-infos (mapcar 'my/org-index-extract-infos (my/org-index-list-files-path directory)))) (mapcar (lambda (one-document-infos) (insert "- " (my/org-index-format-element one-document-infos fmt) "\n")) documents-infos)))
Cette fonction sera appelée directement par Org-mode, quand on
demandera à générer le contenu d'un bloc de type index
. Elle recevra
tout les paramètres du bloc avec le paramètre params
et elle va
simplement insérer du texte: La liste, au format Org-mode,
représentant tous les documents Org-mode trouvés.
Il faut l'écrire dans le fichier index-test.el
.
Cette fonction est séparée en 3 parties: Définition des variables locales, récupération des informations sur chaque documents Org-mode et génération de la liste au format Org-mode.
Variables locales
Pour définir des variables locales, on utilise la fonction (let)
.
Son première argument est une alist
de toutes les variables locales.
Tous les autres arguments sont du code qui aura accès à ces variables
locales.
La alist
des variables locale sera transformée en liste
automatiquement. Donc, si pour la valeur d'une variable on écrit du
code, ce code sera exécuté et son résultat sera assigné à la valeur.
Exemple simple d'utilisation de (let)
:
(let ((var_1 11)
(var_2 22))
(message var_1)
(+ var_1 var_2))
Dans cet exemple, on définit 2 variables: var_1
et var_2
. On va
ensuite appeler les fonctions (message)
et (+)
. Ces 2 appelles
doivent êtres passé en paramètres à (let)
pour qu'elles aient accès
aux variables locales.
Pour revenir à notre code, on définit 3 variables locales:
directory
, le dossier à parcourirfmt
, la chaine de caractère de formatagesort-recent
, indique si on doit trier les documents selon leur mot clésDATE
documents-infos
, la liste des informations de tout les documents trouvés, pour le moment vide
La variable directory
reprends le paramètre de bloc :directory
. Si
ce paramètre n'est pas définit, on utilise le dossier courant obtenu
avec (file-name-directory (buffer-file-name))
.
La variable fmt
reprends le paramètre de bloc :format
. Si
ce paramètre n'est pas définit, on utilise "%d: [[file:%p][%t]]"
.
La variable sort-recent
reprends le paramètre de bloc
:sort-recent
. Si ce paramètre n'est pas définit, on utilise nil
qui est retourné par (plist-get params :sort-recent)
.
Récupération des informations
Pour cette partie là, on utilise la fonction (mapcar)
pour exécuter
(my/org-index-extract-infos)
sur chaque chemins trouvés par
(my/org-index-list-files-path)
.
Le résultat est stocké dans la variable documents-infos
. Si, pour le
bloc dynamique, le paramètre :sort-recent
a été définit à t
, alors
la liste des documents sera triée avec la fonction
(my/org-index-sort-documents)
.
(if sort-recent (setq documents-infos (my/org-index-sort-documents (mapcar 'my/org-index-extract-infos (my/org-index-list-files-path directory)))) (setq documents-infos (mapcar 'my/org-index-extract-infos (my/org-index-list-files-path directory))))
Génération de la liste au format Org-mode
Cette dernière partie va utiliser la fonction (mapcar)
, pour traiter
chaque documents présents dans la variable documents-infos
:
(mapcar (lambda (one-document-infos) (insert "- " (my/org-index-format-element one-document-infos fmt) "\n")) documents-infos)
La fonction anonyme exécutée par chaque document va faire appelle à la
fonction (insert)
en lui passant 3 chaines de caractères:
- Le "- ", qui indique le début d'une élément de liste Org-mode
- La chaine de caractère formatée par l'appelle à
(my/org-index-format-element)
- Un retour à la ligne
Création automatique d'un bloc de type index
Avec le code décrit jusque là, on a tout ce qu'il faut pour avoir un
bloc de type index
fonctionnel. Mais on aimerait aussi pouvoir
demander à Emacs de créer un bloc de type index
à notre place, après
avoir demandé le chemin vers le dossier à parcourir.
Pour ça, ajoutez ce code au fichier index-test.el
:
(defun my/org-index-insert-dblock (directory) "Insert a new dynamic bloc of type index" (interactive (list (string-replace (file-name-directory (buffer-file-name)) "" (expand-file-name (read-directory-name "Dirictory to build index on: "))))) (org-create-dblock (list :name "index" :directory directory))) (org-dynamic-block-define "Index" 'my/org-index-insert-dblock)
Ici, on déclare une nouvelle fonction interactive, nommée
(my/org-index-insert-dblock)
. Elle accepte un paramètre:
directory
. Ce paramètre sera répété, comme paramètre du bloc écrit
par cette fonction.
On peut distinguer 2 parties dans cette fonction:
- La partie que demande à l'utilisateur/utilisatrice quel dossier parcourir
- La partie qui crée le bloc dynamique
Interactivité de la fonction
Pour rappelle, une fonction interactive est une fonction elisp
qui
peut être appelée directement par un utilisateur ou une utilisatrice.
Une fonction devient interactive si l'instruction (interactive)
est
indiquée juste après la documentation. Cette instruction sert
également à définir comment Emacs va récupérer les paramètres de la
fonction. Cette indication peut avoir 2 formes: Une chaine de
caractère ou une liste. Ici, j'ai choisi la liste, où chaque élément
correspond à un des paramètres.
Pour récupérer le chemin relatif vers le dossier à parcourir, j'utilise ceci:
(string-replace (file-name-directory (buffer-file-name)) "" (expand-file-name (read-directory-name "Dirictory to build index on: ")))
J'appelle (read-directory-name)
pour demander à
l'utilisateur/utilisatrice quel dossier parcourir. Cette fonction
retourne un chemin absolue. (expand-file-name)
va ensuite remplacer
le ~/
par le chemin complet vers le dossier de
l'utilisateur/utilisatrice. Enfin, (string-replace)
va enlever le
dossier courant du chemin pour qu'il ne reste que le chemin relatif.
(file-name-directory (buffer-file-name))
nous servent à obtenir le
dossier courant.
Création du bloc dynamique
Pour insérer un nouveau bloc de type index
, ma fonction va appeler
(org-create-dblock)
, en indiquant le type de bloc et le paramètre
:directory
à utiliser.
Enregistrement de la nouvelle fonction
L'appelle à (org-dynamic-block-define)
va enregistrer ma fonction de
création de bloc à la liste des fonctions disponibles.
Il est maintenant possible d'appeler, depuis un document Org-mode, la
fonction interactive (org-dynamic-block-insert-dblock)
. Quand Emacs
demande quel type de bloc dynamique insérer, on peut choisir Index
puis indiquer le dossier à parcourir.
Utilisation
Si on évalue tout le code qu'on a écrit dans index-test.el
, nous
pouvons maintenant insérer des blocs dynamiques de type index
.
Pour tester, on va créer un fichier index.org
à la racine de notre
dossier de test.
Insérer un bloc de type index
Pour insérer un bloc de type index dans un document Org-mode, on doit:
- Ouvrir le document
- Placer le curseur d'écriture là où on souhaite insérer le bloc
- Appeler
org-dynamic-block-insert-dblock
(raccourcisC-c C-x x
) - Choisir
Index
- Indiquer le dossier à parcourir
Emacs va ensuite insérer un nouveau bloc:
#+BEGIN: index :directory "posts/" #+END:
On peut également écrire sois-même le bloc, ou appeler la fonction
interactive (my/org-index-insert-dblock)
.
Générer le contenu
Pour générer le contenu, on place le curseur d'écriture sur la ligne
#+BEGIN:
et on appelle le raccourcis clavier C-c C-c
.
Voici le résultat de notre exemple:
Si on ajoute le paramètre :sort-recent t
au bloc et qu'on génère à
nouveau le contenu:
Installation permanent du type de bloc dans Emacs
Pour pouvoir utiliser notre nouveau type de bloc, index
, après un
redémarrage d'Emacs, il faut copier dans ~/.emacs.d/init.el
tout le
code qu'on a écrit dans index-test.el
.
Conclusion
On a put voir comment fonctionnait un type de bloc dynamique qui construit l'index d'un dossier. Pour des questions de simplicité, j'ai séparé son fonctionnement en plusieurs fonctions. Mais il aurait été possible de tout condenser en une seule.
Un index généré automatiquement peut servir à organiser ses notes, mais également à créer la page d'index d'un blog.
Si tu utilise la fonction de publication d'Org-mode, pour publier un site web, il est possible de demande à Org-mode de:
- Générer automatiquement un fichier index.org
- D'y écrire un index des autres documents Org-mode convertit
- De convertir également index.org vers un fichier
index.html
Mais avec cette solution, le fichier index.org
est ré-écrit à chaque
exportation. Ce qui empêche de construire un fichier index
personnalisé.
Là, avec un type de bloc dynamique index
, on peut créer sois-même le
document index.org
, le personnaliser et garder une construction
automatique de la liste des articles. On peut également avoir
plusieurs indexes dans le même document.
Pour plus d'information, je t'invite à lire la documentation sur les
blocs l'API org-element
, ainsi que la documentation des différentes
fonctions qu'on a utilisé.