featured image

En 2013 comencé un cambio de dinámico a estático en la web comenzando con Octopress y llegando dos años más tarde hasta Lambdapad. No obstante, como digo en este issue, la situación se había vuelto insostenible, ¿qué hacer?

Como he anticipado antes, en 2013 cambié mi blog a modo estático. Me cansé de no poder escribir artículos sin conexión a Internet y probar cientos de formas de conexión XML-RPC para publicar desde aplicaciones de escritorio. Octopress me pareció la mejor solución en su momento, no obstante, en aquél entonces ya tenía un blog de tamaño considerable, cerca de 300 publicaciones. Octopress tardaba unos 20 minutos en generar todo el sitio web.

En 2015, venido como agua de mayo, llegó Lambdapad haciendo la promesa de poder escribir toda la generación de páginas en Erlang. En ese momento mi empresa Altenwald estaba en auge, llovían los contratos para programadores de Erlang y me pareció ideal poder escribir un blog centrado en esos momentos alrededor del mundo de Erlang (aunque no en exclusiva) ¡en Erlang!

No obstante, los blogs tienen diferentes elementos visibles en las páginas. Lambdapad era un recopilador de información y una ayuda inestimable para proporcionar esta información a una plantilla y generar una o varias páginas. Sin embargo, todo lo demás depende de uno mismo y la destreza con el código. Sumado a intentar mantener todo el código en un solo módulo (index).

En la sección de issues se pueden ver aún algunos de mis problemas encontrados en 2015 y finalmente, el problema. La lentitud de procesamiento. Tuve que descartar algunas partes de las páginas en aquel momento y aún así no conseguí recortar el tiempo de generación de 60 minutos, el cual terminó subiendo hasta 2020 llegar a 75 minutos. Me desmotivó y dejé de escribir.

Abordemos los problemas y veamos más tarde cómo fueron solucionados.

Actualización 2021-04-22 He subido un vídeo en Youtube, en el Canal de Altenwald donde se puede ver una explicación y demostración en vivo de cómo funciona Lambdapad:

Generación de Páginas

El problema principal fue la generación de las páginas. Lambdapad cargaba la meta-información de las páginas en memoria pero no el contenido. Si una plantilla solicitaba el renderizado del contenido, entonces se procedía a la lectura del fichero, la conversión y la inclusión del texto renderizado.

Además, el renderizado no se lleva a cabo dentro de lambdapad. Se delega a un comando externo: Multimarkdown. Esto requiere la generación de un proceso de sistema operativo de consola, la ejecución del comando, lectura del fichero original y escritura del fichero destino. Lambdapad después lee el fichero destino (originando otra lectura a disco) y lo incluye en la plantilla. Muy lento. Demasiado. Quizás con 10 publicaciones no se note, pero con un blog de 300 publicaciones, agregando paginación para índices, nube de etiquetas y categorías. Todo suma.

Daba igual si se escribía el código optimizado y muy bien. Esta dinámica era interna en Lambdapad e insalvable. Había que cambiarla.

¿Sólo HTML?

Este era otro problema. Yo quería mantener mi sistema de sindicación o RSS. En un sistema de sindicación normalmente se agrega un resumen de cada publicación (el primer párrafo o el texto marcado como extracto o excerpt). En mi primer fork de Lambdapad agregué el uso de otro comando del sistema para generar texto plano en lugar de HTML: pandoc. Esto contribuyó al salto de 60 a 75 minutos en la generación. Pero también a incrementar la complejidad de Lambdapad.

No solo debíamos configurar apropiadamente la localización del comando multimarkdown, sino ahora también la disponibilidad del comando pandoc.

Earmark y ExDoc

A principios de 2021 estuve revisando en detalle el proyecto earmark. Este proyecto tiene una ventaja sustancial. El código es 100% Elixir. Esta ventaja asegura no tener que levantar un proceso de sistema operativo y mantener una fuerte dependencia con un comando del sistema. En caso de poder incluir esta librería en Lambdapad aceleraría enormemente el desarrollo.

No terminaba de convencerme cambiar el compilador o parser de Markdown. Cada Markdown tiende a ser diferente. No obstante, Earmark había hecho sus deberes. Tenía soporte del Markdown original e incluso extensiones de Github. Esto lo hacía ideal. La mayoría de desarrolladores emplean este formato de Markdown e incluso las extensiones de Github.

Hice algunas pruebas a renderizar algunos artículos e incluso descubrí un acceso para obtener el árbol AST de Markdown. Este hallazgo no solo ayudaría a facilitar la renderización de las páginas en modo HTML (ya hecho por defecto por earmark) sino también facilitar la renderización del contenido en texto plano. Sin formato.

¡Teníamos ganador! Pero, Earmark estaba escrito en Elixir y Lambdapad en Erlang. En mi experiencia en un proyecto Elixir podemos introducir sin problema dependencias en Erlang pero al contrario es muy complicado. ¿Qué hacemos?

Lambdapad en Elixir

Creo que me sentí como un traidor. En el proyecto original podemos leer textos al estilo de Garrett anunciándonos:

Static site generator using Erlang. Yes, Erlang.

Una forma de decir "sí, en Erlang se puede". Sin embargo llegué yo y lo reescribí todo a Elixir. Mi primer cambio fue este texto. Puse simplemente Elixir y eliminé la reafirmación. Me sentí un poco sucio. Pero quería llevar a cabo la prueba de concepto y para ello tendría que formar los pilares del proyecto:

  • Simple. Hay que escribir código, pero debe ser simple. Lambdapad debe ayudar y establecemos por convención todo lo que podamos.
  • Rápido. Este era mi punto crucial. No quería tardar una hora en tener mi blog.
  • Un comando. Generar todo en un solo script que pueda ser distribuido como rebar3.
  • Extensible. Aún en desarrollo pero permitir instalar extensiones para cumplir con la simpleza.
  • Elegante. Cuando entra por los ojos, gusta más. Una de las potencias de Elixir sobre Erlang considero que es la elegancia. Mostrar colores y una salida pulcra de cada comando, comprensible y más amigable.
  • Robusto. Trabajando aún en esto pero la idea es conseguir 100% de cobertura en tests.
  • Documentado. Al igual que Elixir, este debe ser otro pilar. Cuanta más documentación, mejor.

Bastante ambicioso. Mucho trabajo por delante pero incluso he vuelto a escribir en este blog. Me siento motivado. No obstante, no nos desviemos. Estábamos en la prueba de concepto.

Prueba de Concepto

Mi primera prueba fue muy muy simple. Con la idea de crear un sistema de configuración basado en meta-programación. Algo bastante simple de hacer con Elixir. Diseñé basado en la idea original, una forma de decir a Lambdapad qué hacer:

  • config: indicamos dónde encontrar el fichero de configuración y su formato. No me convencía hacerlo en formato Elixir ni Erlang Term, así que busqué y encontré TOML. Si no especificábamos este bloque automáticamente se buscaría un fichero config.toml.
  • assets: indicamos qué ficheros copiar y dónde. Pero basado en la simpleza. También permitir si no se define nada y existe el directorio assets, el contenido se copie tal cual dentro del directorio de destino.
  • pages: indicamos qué páginas procesar y cómo. Esto escaló muy rápido. Tenía una forma de procesar páginas independientes, páginas de índices, índices paginados e incluso páginas de índices a un nombre de fichero específico (caso de atom.xml).

Esta era la base. En el archivo de configuración podría agregar información sobre el blog. Por ejemplo, la url.

Fui convirtiendo mi sitio web a este nuevo formato. Corrigiendo cada parte y durante una semana la base de código fue creciendo. Hasta llegado el día. Una sola ejecución consiguió generar todo el sitio web exactamente igual en tan solo 2 minutos. ¡Conseguido!

No obstante, recordé los cambios para el antiguo Lambdapad. Había eliminado código de algunas páginas: las publicaciones recientes y la nube de etiquetas solo aparecían en la página principal. Este trozo de HTML es exactamente igual en todas las páginas. ¿Cómo podemos optimizarlo e incluirlo?

Transformadores

Los transformadores son funciones anónimas (o lambdas) para modificar la información antes de ser enviada a la plantilla. Este fue un avance interesante porque en el sistema original todo se procesaba en un único sitio y dependía de uno mismo. Cambiar todo el conjunto de páginas o la configuración depende de tu código.

Las transformaciones ayudan a identificar qué tipo de cambios se realizan y poder realizar composiciones. Son cambios deterministas y queda mucho más funcional. Mucho más Lambda.

Widgets

Esta es una de las características que más me gustan de Lambdapad. Los Widgets. Trozos de código obtenidos de las páginas generando un único trozo HTML y almacenado para ser empleado como etiqueta en cualquier plantilla.

De esta forma, la nube de etiquetas y la cuenta de publicaciones por categoría podía renderizarse una sola vez. Los widgets permiten realizar un proceso una sola vez y utilizarlo en las miles de páginas a generar de forma rápida.

El tiempo no se había visto afectado. Habían vuelto algunas de las antiguas funcionalidades y aún tardaba 2 minutos en generarse todo. Cuando descubrí algo interesante.

Leer una sola vez

Las páginas en Markdown se leían del disco para cada bloque de widgets o páginas. Si especificaba leer todos los ficheros de Markdown. Los 300 ficheros en cada bloque. Estos se leían en cada bloque. Cada vez. Se procesaban de la misma forma y se empleaban como datos de entrada. ¿Y si creábamos una caché?

La configuración de mi blog tiene:

  • Widget para la barra lateral (aside).
  • Página principal y subsiguientes paginadas.
  • Páginas de publicaciones individuales.
  • Página de archivo.
  • Páginas de cada una de las etiquetas.
  • Páginas de cada una de las categorías.

Si hay 300 ficheros se procesarían como 1800 ficheros.

Para esta tarea emplee ETS. En una tabla en memoria almacenaría el resultado del procesamiento. Se identificaría por el wildcard empleado para buscar los ficheros. Si el sistema quería volver a obtener esos ficheros por segunda vez, obtendría la información de la tabla ETS.

Al ejecutar de nuevo el proyecto fue cuando publiqué el tuit en la cuenta de Altenwald:

El sistema tardó 16 segundos en procesar todo el blog y generar el sitio web completo.

Pero como había dicho me sentí un traidor. Trabajo mucho con Erlang, me encantó el entusiasmo inicial del proyecto Lambdapad y todo el movimiento alrededor de él cuando se desarrolló en Erlang y, después de todo Elixir se ejecuta sobre Erlang. ¿Podría quizás rescatar el formato antiguo también?

La vuelta de Erlang

Quise quitarme la espina de haber eliminado el código de Erlang. En principio la configuración, las plantillas y otras partes del código mantienen una opción de formato. En la configuración fue muy sencillo volver a agregar Erlang Term (eterm) como opción para escribir la configuración.

No obstante, recuperar el uso de index.erl fue más costoso. El formato original de index.erl se basa en dos funciones:

  • data/1 donde se pasan los argumentos pasados a script de Lambdapad y debe retornar información de los ficheros a cargar. Este fue tomado casi igual en Elixir. Su conversión no fue problemática. Hasta recordar que aquí también se precargaban los ficheros de Markdown.
  • site/1 se obtiene la configuración y dentro de la configuración se esperan los ficheros de Markdown y toda la información a ser enviada o procesada para las plantillas. Esta función proporciona los datos para el generador de páginas pero con toda la información dentro.

Después de un día completo intentando rescatar el formato original caí en la cuenta de porqué lo cambié. Caí en la cuenta de quizás no haber entendido completamente el proceso original. No obstante, ya estaba hecho.

Pensé en volver a cambiarlo todo. No se me ocurría una buena forma de hacerlo y la ya existente en Elixir tenía sentido para mi. Así que probé a cambiarlo también en Erlang. En lugar de seguir estrictamente lo definido, desarrollé la nueva forma. Cambié los nombres de las funciones y asigné la misma dinámica.

Al final, conseguí rescatar un ejemplo de blog. Lo reescribí al nuevo formato y esta vez 100% en Erlang. Me sentí orgulloso de poder devolver Erlang al proyecto. Así que agregué la información al README del proyecto y lo anuncié en Twitter.

Conclusiones

Aún queda mucho recorrido en el proyecto. Recientemente estuve hablando con Garrett y parece que le gustó cómo se ve ahora. Hay un cambio pendiente en el logo, muchas más características comentándose en la sección de issues y mucho más por venir.

¿Te animas a probarlo? ¿Qué te parece el proyecto? ¿Usarías el formato de Erlang o Elixir? ¡Déjanos un comentario!