Notxor tiene un blog

Defenestrando la vida

Probando de nuevo org-roam

Notxor
2023-10-23

El otro día hablando con Jordi (de hispa-emacs) me emplazaba para un nuevo encuentro virtual y proponía como tema que explicara cómo utilizo org-roam. Había leído en el blog un artículo que hablaba sobre ello. El problema es que dicho artículo versa sobre org-roam, en su primera versión, y en la actualidad, la v2 es ligeramente distinta y no la he probado. Hace años que no utilizo org-roam, pero dado que parece ser el chismático de notas que utiliza la mayor parte de los usuarios de Emacs me decidí a volver a probarlo y ver si ha mejorado con respecto a lo que recordaba.

El asunto pasa por volver a instalarlo, crear nuevas notas, probar cómo funciona y (poder) contarlo. Vamos a ello.

Instalación

Lo primero es lo primero y hay que empezar por la instalación y, por tanto, hay que tener en cuenta algunas dependencias. La mayoría seguramente ya las tienes instaladas, si haces un uso habitual de Emacs, como es mi caso. Pero por si acaso, la dependencia más importante es a sqlite. Algo que no termina de gustarme, pero que parece que al resto de la gente le da igual.

(use-package emacsql
  :ensure t)
(use-package emacsql-sqlite
  :ensure t
  :after 'emacsql)

Después, hay que instalar el paquete org-roam y configurarlo:

(use-package org-roam
:ensure t
:custom
(org-roam-directory (file-truename "~/Notas"))
:bind (("C-c n l" . org-roam-buffer-toggle)
       ("C-c n f" . org-roam-node-find)
       ("C-c n g" . org-roam-graph)
       ("C-c n i" . org-roam-node-insert)
       ("C-c n c" . org-roam-capture)
       :map org-mode-map
       ("C-M-i" . completion-at-point)
       ;; Dailies
       ("C-c n j" . org-roam-dailies-capture-today))
:config
;; If you're using a vertical completion framework, you might want a more informative completion interface
(setq org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
(org-roam-db-autosync-mode)
;; If using org-roam-protocol
(require 'org-roam-protocol))

Posiblemente, y si tu objetivo es utilizar con asiduidad este paquete, necesitarás cambiar la variable org-roam-node-display-template para adecuarla más a tus necesidades. Yo de momento me limito a hacer pruebas y después decidiré si se queda o si vuelvo a como me estaba apañando hasta ahora.

Uno de los fuertes de org-roam no es en sí el mismo chismático, sino el adicional UI: una interface web que permite visualizar las notas en una nube de nodos y relaciones que hay entre ellas.

Tanto en su versión plana:

Captura_org-roam-ui-2d.png

Como en su versión tridimensional:

Captura_org-roam-ui-3d.png

Puesto que es un paquete que hará una representación gráfica de las relaciones de nuestras notas basándose en web, necesitará, como dependencia del mismo a websocket. Además teniendo cuidado de cargarlo después de org-roam, pero antes de org-roam-ui.

(use-package websocket
  :after org-roam)

(use-package org-roam-ui
  :after org-roam
  :config
  (setq org-roam-ui-sync-theme t
        org-roam-ui-follow t
        org-roam-ui-update-on-save t
        org-roam-ui-open-on-start t))

La configuración es sencilla estableciendo algunas variables para sincronizar los cambios entre el editor y el UI y... iba a decir «viceversa», pero en este caso es un UI en el que no se pueden modificar las notas. Pero sí es cierto que se situará en aquella nota que tengamos abierta en primer plano de Emacs o podremos abrir en el editor aquella que abramos en la interface.

¿Qué diferencias hay con la versión anterior?

Las notas siguen siendo org-mode, los cambios se producen en las cabeceras, de manera que:

  • #+roam_tags: pasa a ser #+filetags:
  • #+roam_key: pasa a ser una propiedad :ID:

Si no has utilizado con anterioridad org-roam, te darán igual estos cambios. Si estás utilizando la v1, algo que ya debería estar superado tras varios años de estar en uso y actualizada la v2, también puedes migrar notas con org-roam-migrate-wizard, que hará todo el proceso por ti. Yo lo he hecho para convertir algunas notas viejas.

Zettelkasten, pruebas y primeras impresiones

Evidentemente no son mis primeras impresiones sobre org-roam, que ya probé su primera versión. Sin embargo, no terminó de gustarme la v2, quizá por estar acostumbrado a la versión anterior, cambiaron las combinaciones de teclas y me obligaron a hacer una migración, que en su día me fallaba, obligándome a elegir si empezar de cero o cambiar a otro procedimiento de tomar notas.

Toda esta historia viene de implementar el método Zettelkasten, que es una forma de tomar notas y relacionarlas entre sí. Por ahí podrás encontrar una descripción más clara y más detallada de en qué consiste el método e incluso aplicaciones que lo soportan y te permiten utilizarlo. Pero, en resumen, para mí la potencia de este método consiste en los enlaces entre los nodos de información, donde no hay jerarquías y donde termina emergiendo una estructura propia.

Consejos:

  1. Las notas deben ser todo lo modulares que se pueda. Si se puede resumir todo a una sola idea mejor.
  2. Todas las notas deben estar relacionadas con otras. Las notas sueltas no aportan nada al sistema y son difíciles de encontrar.
  3. Escribe los conceptos con tus propias palabras, son tus notas personales, no un manual de perfección.
  4. Cuantas más notas tomas más aprendes y mejor funciona el sistema, así que si eres constante terminarás teniendo un segundo cerebro en tus notas.

El creador del método Zettelkasten distinguía entre distintos tipos de notas. Veamos:

  • Notas rápidas: Son notas que se toman rápidamente en el momento. Se hacen sin precisión y sin encajarlas en ningún sitio. Con posterioridad se estudiarán para enlazarlas con otras o para generar notas permanentes.
  • Notas permanentes: Son las anotaciones normales que están relacionadas con otras notas. Estas son las notas más importantes.
  • Notas bibliográficas: Son notas destinadas a recoger la bibliografía y las fuentes externas.
  • Notas literatura: Son notas donde se cita literalmente alguna fuente. El creador del sistema solía utilizar el reverso de una nota bibliográfica para introducir las citas.
  • Notas índice: Son notas que, con respecto a una palabra clave o concepto, contienen una lista de referencias a otros nodos relacionados.

Si quieres seguir el método, tendrás que crear este tipo de nodos en org-roam, porque el sistema no contempla mucha diferencia entre unas y otras. Quizá las notas rápidas estén cubiertas con las notas a través de dailies, que es una forma de capturar notas por días. Estas notas se guardan en otro directorio, dentro del de notas generales. Cada día escribe en un archivo cuyo nombre es la fecha correspondiente, aunque también se puede escribir en los correspondientes a otros días.

Para realizar las pruebas del pichorro lo que he hecho ha sido rescatar de una vieja copia de seguridad algunas notas que tenía de la versión 1 y convertirlas a la versión 2. He de decir que, por contra a cuando intenté hacerlo en su día, en esta ocasión no me he encontrado con ningún tipo de problema.

Como eran pocas, las notas rescatadas, decidí también incorporar a la base de datos de notas, los artículos de este blog. Que ya sé que incumplen la mayoría de los principios del sistema, es decir: no son ideas modulares y la poca relación que tienen entre sí es a través de las etiquetas e, incluso de ese modo, hay algunas de ellas que están relacionadas por los pelos. Pero en fin, por ser algo más didáctico, mostraré cómo he hecho la conversión de artículo del blog a nota de org-roam. El resultado es que pululan bastantes notas sueltas por ahí, pero para hacer pruebas me sirve.

Conversión a org-roam

La historia era pasar los artículos, tal como están almacenados en su repositorio, pero cumpliendo con la estructura esperada de una nota de org-roam.

Para no romper nada, lo primero que hice fue copiar todos los artículos, en formato org-mode, a otro directorio, por aquello de no estropear nada. Aunque están en un repositorio git, tampoco es cuestión de andar rompiendo cosas. Además, el objetivo era generar notas en archivos nuevos que no machaquen los antiguos, por si acaso.

(defvar dir-origen "~/proyectos/pasar-blog-roam/origen")
(defvar dir-destino "~/proyectos/pasar-blog-roam/resultado")

Por eso, creé dos directorios nuevos y guardé el nombre en sendas variables: dir-origen, que guarda los artículos en su versión original y dir-destino, que será donde se guarden las notas antes de pasarlas al directorio de notas (también por si acaso).

Las dos características fundamentales de una nota que hacen que org-roam las reconozca como propias, son:

  • El ID como propiedad del archivo: El id se puede crear con el comando uuidgen.
  • El nombre del archivo. Normalmente comienza con una cadena de año, mes, día, hora, minuto y segundo en el que se creó, seguido por el título, separando las palabras con símbolos _ en lugar de los espacios.

La cabecera con el ID lo genera la siguiente función:

(defun generar-cabecera-id ()
  "Genera un ID y devuelve una cabecera para `org-roam'."
  (concat ":PROPERTIES:\n"
          ":ID:       " (shell-command-to-string "uuidgen")
          ":END:\n"))

El nombre del archivo se obtiene mediante la función:

(defun dame-nombre (nombre-archivo fecha-post)
  "Dado el NOMBRE-ARCHIVO y la FECHA-POST genera uno compatible con org-roam."
  (concat (format-time-string "%Y%m%d%H%M%S-" fecha-post)
          (string-join (split-string nombre-archivo "-") "_")))

Esa función toma el nombre del archivo que contiene un artículo, que tiene los espacios convertidos en -, y una fecha, para generar el nombre de la nota. Para procesar la creación de una nueva nota sólo necesitamos el nombre del archivo que contiene el artículo, se busca la fecha de publicación en la cabecera del mismo y se pide el nombre de la nota.

(defun procesar-archivo (nombre-archivo)
  "Dado un NOMBRE-ARCHIVO genera uno nuevo válido para org-roam."
  (interactive)
  (let ((case-fold-search t)
        (nombre-destino "")
        (fecha-post (time-since 0)))
    (with-temp-buffer                         ; Abro un buffer temporal
      (insert (generar-cabecera-id))          ; Inserto cabecera `org-roam'
      (insert-file-contents nombre-archivo)   ; Inserto el contenido del artículo
      (progn
        (goto-char 0)
        (search-forward "<:t")                ; caracteres finales de la cabecera de un artículo
        (insert (enlace-root-blog)))          ; Inserto un enlace al nodo `blog'
      (setq fecha-post                        ; Obtengo la fecha del artículo
            (progn
              (goto-char 0)                   ; desde el inicio busco la fecha
              (if (search-forward-regexp "^\\#\\+date:[ ]*<\\([^]>]+\\)>$" nil t)
                  (date-to-time (match-string 1))
                (time-since 0))))
      (setq nombre-destino (concat dir-destino "/"
                                   (dame-nombre nombre-archivo fecha-post)))
      (write-file nombre-destino))))

Se crea un buffer temporal donde se insertan, por orden:

  • La cabecera con el ID del nuevo artículo
  • Un aviso de que es un artículo del blog y no una nota real.
  • El contenido, tal cual, del artículo.

Todo eso se guarda en un nuevo archivo que se puede utilizar como nota. El asunto, también, es que lo hiciera automágicamente para los sienes y sienes de artículos. Para ello primero tenemos que obtener la lista de artículos:

(defun lista-archivos ()
  "Devuelve la lista de archivos a convertir."
  (directory-files-recursively dir-origen ".*\\.org$"))

Este código devuelve una lista con el nombre de los archivos. Ahora la podemos llamar desde un bucle y que procese todos los nombres que haya en ella:

(defun pasa-los-archivos()
  "Procesa todos los archivos `.org' que estén en el `dir-origen'."
  (interactive "P")
  (let ((nombre-archivo ""))
    (dolist (archivo (lista-archivos))
      (setq nombre-archivo (file-name-nondirectory archivo))
      (procesar-archivo nombre-archivo))))

Conclusiones (provisionales)

De momento, en esta ocasión está comportándose mejor el lío con la base de datos. Sigue siendo un poco incordio cuando cambio de dispositivo y actualizo el repositorio. Al actualizar, tarda un rato en regenerar la base de datos sqlite. También hay que decir que luego la búsqueda se agiliza, mientras en Zetteldeft las búsquedas pueden ser un poco más lentas, pero no se retarda el cargar las notas.

Por otro lado, la manera de navegar entre enlaces en Zetteldeft es rapidísimo, algo de lo que carece org-roam y navegar hasta una determinada nota pude ser tedioso. Además los enlaces de org-roam es idéntico a cualquier enlace de org-mode, por lo que se visualiza como cualquier otro. Sin embargo, el destino que se enlaza se refiere al UUID y dichos enlaces sólo funcionan dentro de org-roam.

Sobre el UI, cuando cuentas con cientos de entradas, como es mi caso, el verlas de un pantallazo no ayuda especialmente a nada. Eso sí, cuando tienes mirones, quedan muy vistosas las bolitas danzando por la ventana. O quizá es que no le he encontrado aún un uso más interesante.

Como apunte para el futuro, me tengo que poner un poco con el tema de cómo poder separar notas que no quiero que se mezclen con el noterío principal. Por ejemplo, para las que realizo con Zetteldeft tengo un par de funciones y teclas que cambian el directorio de almacenado. Si solvento de algún modo este pequeño problema, es posible que se quede org-roam en mi caja de herramientas, si no... pues seguiré como hasta ahora.

Categoría: emacs org-mode

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en esta cuenta de Mastodon, también en esta otra cuenta de Mastodon y en Diaspora con el nick de Notxor.

Si usas habitualmente XMPP (si no, te recomiendo que lo hagas), puedes encontrar también un pequeño grupo en el siguiente enlace: notxor-tiene-un-blog@salas.suchat.org

Disculpen las molestias.