Blogging con Emacs y org-mode

Si el Hola Mundo! de la programación web es armar un blog, el Hola Mundo! de los posts es aquel que describe cómo se implementó ese blog. En este caso no hay mucho para decir sobre la implementación —todo el trabajo pesado lo hacen Jekyll y Github Pages—, pero sí tuve que invertir algo de tiempo preparando mi editor para el proceso de escritura y publicación.

Una de las razones por las que armé un nuevo blog es que trabajar con WordPress me daba escalofríos. Quería control de versiones para mis posts, tenerlos en GitHub en algún formato independiente de la plataforma, y, lo más importante, poder escribir en Emacs, el mismo editor que uso cotidianamente para programar. En particular, me interesaba usar org-mode como lenguaje de markup. El objetivo es alcanzar un ciclo virtuoso (?) en el que escribo más seguido, aprendo más sobre org-mode y mejoro mi configuración de Emacs en el camino.

Setup

Ya decidido a usar Emacs y org-mode, me pareció razonable buscar cómo integrarlos con Jekyll y Github Pages, para ahorrarme el trabajo y el costo de hosting y configuración del blog. Encontré este post, que sigue un razonamiento muy parecido al mío, y lo usé como guía. Adicionalmente, la documentación de org-mode tiene un tutorial dedicado específicamente a este escenario.

Siguiendo el ejemplo de aquel post, hice un fork de BeautifulJekyll, un template pensado para GitHub Pages que resuelve la mayoría de las necesidades básicas de un blog (layout, links, comentarios, analytics, etc.).

Para poder producir posts, necesitaba generar HTML que sea “Jekyll-friendly”1. org-mode incluye funcionalidad de exportación (convertir documentos org a otros formatos como LaTeX, markdown y html) y publishing (exportar jerarquías de documentos relacionados o “proyectos”). Lo único que hace falta en este caso es declarar un proyecto que indique de dónde se leen los archivos .org y en dónde hay que guardar los .html exportados:

(setq org-publish-project-alist
      '(("blog"
         ;; Path to org files.
         :base-directory "~/dev/facundoolano/facundoolano.github.io/org"
         :base-extension "org"

         ;; Path to Jekyll Posts
         :publishing-directory "~/dev/facundoolano/facundoolano.github.io"
         :recursive t
         :publishing-function org-html-publish-to-html
         :headline-levels 4
         :html-extension "html"
         :body-only t)))

Si bien teóricamente uno no necesita nada más que pushear cambios al repositorio para publicar en GitHub Pages, resulta más práctico tener Jekyll instalado localmente para previsualizar los posts antes de publicarlos. Instalé todo lo necesario así:

$ asdf install ruby 2.7.1
$ asdf local ruby 2.7.1
$ gem install bundler jekyll
$ bundle install

Workflow

Para convertir un archivo de markdown, o en mi caso, de org-mode, en un post de Jekyll, hacen falta dos cosas: 1. prefijar el nombre del archivo con un timestamp (e.g. 2020-09-02-blogging-con-emacs-y-org-mode) y 2. agregarle un encabezado con metadata:

---
layout: post
title: "Blogging con Emacs y org-mode"
date: 2020-09-04
tags: [blog, emacs]
---

Para automatizar esta tarea frecuente, escribí este comando (partiendo de lo que encontré acá):

(defun org-blog-new-post (title)
  "Create a new org-file for a blog post as expected by Jekyll with TITLE."
  (interactive "MPost title: ")
  (let ((slug (sluggify title))
        (date (current-time)))
    (find-file (concat (projectile-project-root) "org/_posts/"
                       (format-time-string "%Y-%m-%d") "-" slug
                       ".org"))
    (insert "#+BEGIN_EXPORT html\n")
    (insert "---\n")
    (insert "layout: post\n")
    (insert "title: \"") (insert title) (insert "\"\n")
    (insert "date: ") (insert (format-time-string "%Y-%m-%d %H:%M:%S")) (insert "\n")
    (insert "tags: []\n")
    (insert "---\n")
    (insert "#+END_EXPORT\n\n")))

También agregué un comando para actualizar la fecha del post en el nombre y el encabezado del archivo (más para practicar emacs-lisp que por necesidad, si vamos a ser honestos):

(defun org-blog-reset-date ()
  "Prompt for a new blog post date and set it in the filename and the Jekyll \
header."
  (interactive)
  (if (not (s-contains? "org/_posts/" (pwd)))
      (error "Not visiting a blog buffer")
    (let* ((date (read-from-minibuffer "Post date: " (format-time-string "%Y-%m-%d")))
           (filename (buffer-name))
           (new-name (concat date (substring filename 10))))
      (rename-file filename new-name t)
      (set-visited-file-name new-name t t)
      (save-excursion
        (goto-char (point-min))
        (re-search-forward "^date: .*$" nil t)
        (replace-match (concat "date: " date))))))

Para exportar el html uso org-publish-current-project y para ver el post localmente corro bundle exec jekyll serve.


Notas

1 Alternativamente podría exportar de org a markdown, formato que Jekyll también soporta, pero es algo que todavía no probé.



04/09/2020 #blog
source | comments

Facundo Olano

Si el Hola Mundo! de la programación web es armar un blog, el Hola Mundo! de los posts es aquel que describe cómo se implementó ese blog. En este caso no hay mucho para decir sobre la implementación —todo el trabajo pesado lo hacen Jekyll y Github Pages—, pero sí tuve que invertir algo de tiempo preparando mi editor para el proceso de escritura y publicación.