Écrire un blog avec emacs

Dans ce tout premier billet, je vous propose de faire rapidement le tour du code qui me permet de générer ce site via Emacs et org-mode avec sa fonctionnalité publish. La configuration a été plus ou moins inspiré des blogs suivants :

L'implémentation

Le code complet peut être trouvé à cette adresse. Ici je détail quelques points du code pour aider à comprendre son fonctionnement et plus généralement le fonctionnement du dispacher ox-publish d'org-mode.

Détails d'implémentation

D'abord je définis quelques symboles qui seront réutilisés plus bas. Ici, du code HTML qui sera ajouté dans la balise <head>. On ajoute le fichier css principal du site ainsi qu'un fichier css généré par Google pour les polices de caractères utilisés dans ce site.

(setq mp/head-extra
      (concat
       "<link href=\"https://fonts.googleapis.com/css?family=M+PLUS+Rounded+1c\" rel=\"stylesheet\">"
       "<link rel=\"stylesheet\" type=\"text/css\" href=\"/res/style.css\"/>"
       "<link rel=\"stylesheet\" type=\"text/css\" href=\"/res/code.css\"/>"
       "<link rel=\"icon\" type=\"image/x-icon\" href=\"/res/favicon.ico\"/>"))

(setq mp/project-path
      (file-name-directory
       (or (buffer-file-name) load-file-name)))

(setq mp/header-file "res/header.html")

Ensuite, nous avons une fonction qui retourne le header de la page (qui contient mon nom et le menu de navigation du site) dans un fichier séparé qui sera inclu dans toutes les pages générées.

(defun mp/header (arg)
  (with-temp-buffer
    (insert-file-contents (concat mp/project-path mp/header-file))
    (buffer-string)))

Après l'entête, le pied de page avec les informations de droit d'auteur et de licence.

(setq mp/footer
      (concat
       "<a rel=\"license\""
       "href=\"http://creativecommons.org/licenses/by-sa/4.0/\"><img "
       "alt=\"Licence Creative Commons\" style=\"border-width:0\" "
       "src=\"https://i.creativecommons.org/l/by-sa/4.0/88x31.png\""
       "/></a><br />"
       "© Matthias Paulmier -- "
       "Vous pouvez copier, modifier et/ou distribuer ce document "
       "sous les termes de la <a rel=\"license\" "
       "href=\"http://creativecommons.org/licenses/by-sa/4.0/\">Licence "
       "CC BY-SA</a>."))

Le fichier indexant les billets déjà postés sur le site s'appel la sitemap au sein d'org-mode. La fonction suivante permet de définir l'aspect général du site (titre capitalisé suivi de la liste des billets formattés).

(defun mp/sitemap (title list)
  "Create the sitemap.  TITLE is the title of the sitemap.  LIST
holds the list of files to include in the sitemap"
  (concat "#+TITLE: " (capitalize title) "\n"
          (string-join (mapcar #'car (cdr list)) "\n\n")))

Les fonctions suivantes permettent de formatter les entrées dans le fichier. De base, nous n'avons qu'une suite de titres menant vers chaque billets publié sur le site. Ici j'ajoute plusieurs choses. Tout d'abord, la date de parution du billet en français (sous forme de lien vers le billet), ainsi que les "tags" (ou catégories) du billet. Pour ce faire je me sers des file-tags qui peuvent être spécifiés avec l'option d'entête #+FILETAGS dans un fichiers sous org-mode. On ajoute également la date de parution du billet. Ensuite, avec la fonction mp/blog-post-get-preview, nous récupérons un aperçu du billet. Pour cela, on va chercher le texte se situant dans le block entouré par #+BEGIN_PREVIEW et #+END_PREVIEW dans le fichier source du billet. Enfin, un second lien vers l'article (en plus du titre) est ajouté à la fin de l'aperçu avec le texte Lire la suite →.

(defun mp/get-tags (filename)
  "FILENAME if the file from which we want to extract the tags
list. Returns the tags in the file."
  (with-temp-buffer
    (insert-file-contents (concat "blog/" filename))
    (org-mode)
    org-file-tags))

(defun mp/blog-post-get-preview (filename)
  "Helper function that returns the content of the preview for
the sitemap. FILENAME is the file of the article."
  (with-temp-buffer
    (insert-file-contents (concat "blog/" filename))
    (goto-char (point-min))
    (let ((beg (+ 1 (re-search-forward "^#\\+BEGIN_PREVIEW$")))
          (end (progn (re-search-forward "^#\\+END_PREVIEW$")
                      (match-beginning 0))))
      (buffer-substring beg end))))

(defun mp/sitemap-entry (entry style project)
  "Formatter function for a sitemap entry. ENTRY is the file
name. STYLE is the style of the sitemap (not used here but
mandatory for the function to work with the ox-publish). PROJECT
is the current project."
  (when (not (directory-name-p entry))
    (format (concat
             "-----\n"
             "* [[file:%s][%s]]  %s\n"
             "#+BEGIN_publidate\n"
             "Published on %s at %s\n"
             "#+END_publidate\n"
             "%s\n"
             "\n")
            entry
            (org-publish-find-title entry project)
            (concat (mapconcat #'(lambda (s) (format ":%s" s)) (mp/get-tags entry) "") ":")
            (capitalize (format-time-string "%FT" (org-publish-find-date entry project)))
            (capitalize (format-time-string "%T%z" (org-publish-find-date entry project)))
            ;; Build the actual preview of the article
            (let ((preview (mp/blog-post-get-preview entry)))
              (format
               (concat
                "%s\n\n"
                "#+BEGIN_readmorelink\n"
                "[[file:%s][Plus \\rarr]]\n"
                "#+END_readmorelink\n")
               preview entry)))))

Enfin, le dispacher ox-publish fonctionne avec une liste associative, org-publish-alist, dans laquelle on spécifie les paramètres qui vont déterminer la façon dont le site sera publié. Pour plus d'information sur cette liste et les options disponibles, la documentation est très bien fournie (la documentation pour org-mode et Emacs en général est très bien faite).

(setq org-publish-project-alist
      `(("blog"
         :components ("articles" "pages" "res"))
        ("articles"
         :base-directory ,(concat  mp/project-path "blog")
         :base-extension "org"
         :publishing-directory ,(concat mp/project-path "public/blog")
         :publishing-function org-html-publish-to-html
         :with-title t
         :with-author t
         :with-creator nil
         :with-date t
         :headline-level 4
         :with-toc nil
         :with-drawers t
         :with-sub-superscript nil
         :section-numbers nil
         :html-link-home "/"
         :html-head nil
         :html-head-extra ,mp/head-extra
         :head-include-scripts nil
         :html-viewport nil
         :html-doctype "html5"
         :html-link-up ""
         :html-link-home ""
         :html-preamble mp/header
         :html-postamble ,mp/footer
         :html-footnote-section "<div id='footnotes'><!--%s-->%s</div>"
         :html-format-drawer-function mp/org-export-format-drawer
         :auto-sitemap t
         :sitemap-function mp/sitemap
         :sitemap-format-entry mp/sitemap-entry
         :sitemap-filename "index.org"
         :sitemap-title "Le blog"
         :sitemap-sort-files anti-chronologically)
        ("pages"
         :base-directory ,mp/project-path
         :base-extension "org"
         :publishing-directory ,(concat mp/project-path "public")
         :publishing-function org-html-publish-to-html
         :exclude "README.org"
         :recursive t
         :with-title t
         :with-author nil
         :with-toc nil
         :with-date t
         :with-drawers t
         :with-sub-superscript nil
         :section-numbers nil
         :html-viewport nil
         :html-head nil
         :html-head-extra ,mp/head-extra
         :html-doctype "html5"
         :html-link-up ""
         :html-link-home ""
         :html-preamble mp/header
         :html-postamble ,mp/footer)
        ("res"
         :base-directory ,(concat mp/project-path "res")
         :base-extension ".*"
         :publishing-directory ,(concat mp/project-path "public/res")
         :publishing-function org-publish-attachment)))

Ce que je souhaite ajouter

J'ai déjà utilisé cette configuration pour un autre site auparavent (que j'utilisais uniquement pour organiser mes cours). Je sais donc qu'il y a quelques fonctionnalités que j'aimerai bien ajouter et qui feront peut-être l'objet d'un future billet.

Il y aura certainement d'autres choses que j'aurai besoins d'implémenter par la suite suivant mon utilisation. Si elles sont assez intéressante selon moi, elles feront sûrement l'objet d'autres billets.

Pagination de la sitemap

La sitemap peut devenir très grande suivant le nombre de billets. Un mécanisme de pagination automatique serait intéressant pour permettre de réduire sa taille. Une autre alternative serai d'archiver les billets afin qu'ils n'apparaissent plus sur cette page.

Un index des billets par catégorie

Avec le système que j'ai mis en place pour les catégories, j'aimerai avec une page d'index qui permettrai de lister tous les billets appartenant à chaque catégorie.

Licence Creative Commons
© Matthias Paulmier -- Vous pouvez copier, modifier et/ou distribuer ce document sous les termes de la Licence CC BY-SA.