Cambiando el blog a org-static-blog
Pues estoy de cambios en el blog y ya tengo algo visible que subir a la red para que se pueda ver. Me resistía al cambio, pues los sistemas son distintos y me arriesgaba a dejar por el camino muchos enlaces rotos, no sólo externos de otros blogs o redes sociales, sino también internos.
Después de pelearme con mis reticencias me puse al tajo e, incluso, me he dedicado a migrar también los diferentes artículos al formato de cabecera nuevo. De esta manera, se podrá consultar el contenido en el nuevo formato o en el antiguo... si al subir el contenido resulta todo como espero.
¿Por qué el cambio? Básicamente tengo estas razones:
org-page
es un sistema complejo y por tanto también es más difícil hackearlo para ajustarlo a tus necesidades.- Por contra,
org-static-blog
es mucho más sencillo y parece pensado para que te lo adaptes a tus necesidades con poco esfuerzo. - Estaba cansado ya del tema oscuro que había hecho y aprovechando que estoy de cambios también le he puesto un poco más de cariño al CSS. Algo que aún no ha terminado del todo y no descarto ponerle un poco más.
- Cuando terminas un artículo y lanzas el comando de publicación de
org-static-blog
, sólo genera ese HTML y actualiza los índices y RSS. - Puedo tener borradores en proceso sin andarme por las ramas (de
git
, se entiende). - Y por supuesto, el utilizar un control de versiones o no, es
problema mío, no me obliga a tener una rama de edición, otra con el
sitio generado, ni a tener que acordarme de generar el sitio cada
vez que hago algún cambio, porque si se pierde en la rama de
git
no llega a materializarse. Este sistema detecta si el HTML es más antiguo que elorg
y si es así lo genera de nuevo.
¿Todo son ventajas? Pues no. Hay inconvenientes que exigen atención, como los enlaces rotos.
Enlaces rotos
A estas alturas, después de varios años escribiendo en el blog sobre mis cosas, resulta que hay quien lo lee y todo. No sólo eso, sino también que hay gente que lo enlaza y lo sigue por RSS.
El miedo al cambio viene, principalmente, de la cantidad de enlaces rotos que quedan siempre sueltos por el mundo. Incluso dentro del mismo blog o página. Por ello voy a intentar que esto no suceda o al menos que sea en el menor grado posible.
Podría haber optado por encogerme de hombros y decir: «sistema nuevo, adaptarse o morir». Pero no me ha parecido conveniente. Para pegar el cerrojazo siempre hay tiempo, me parece.
De momento, como he dicho antes, he convertido todos los artículos del
sistema anterior, hecho con org-page
, al nuevo sistema
org-static-blog
. Ha sido un trabajo relativamente sencillo pues sólo
tenía que convertir algunos detalles de las cabeceras del fichero
org
: en lugar de la etiqueta tags
de org-page
, utiliza
filetags
, y también me encontré con alguna fecha que no cogía bien y
me dediqué a añadir a mano los paréntesis angulares <..-..-..>
y con
eso está todo convertido... pero falta aún algo de trabajo.
Además, estoy escribiendo esto sin tener la certeza de que efectivamente no romperé ningún enlace... tengo confianza en que así sea, porque va a haber artículos duplicados para lo mismo. En las pruebas que he hecho en local, todo ha parecido funcionar correctamente... pero hasta que no lo pruebe subiéndolo todo al servidor, no puedo estar seguro. De todas formas me guardo una copia de seguridad del sitio viejo, por si tengo que replantear el tema.
El paquete lo puedes descargar por el procedimiento habitual, está en melpa y si tienes configurado ese repositorio de paquetes basta con el comando
M-x package-install <RET> org-static-blog <RET>
Yo soy algo más curioso y no lo he instalado: me he bajado el código
del repositorio porque lo estoy cambiando un poco. De hecho no lo
tengo donde esté accesible a Emacs, cuando lo uso tengo que cargarlo
con load-file
, pero aún no estoy en fase de darle cariño.
El código que he cambiado
El propio creador del sistema habla de que está pensado para que sea fácil de leer, comprender y modificar.
Above all, I tried to make org-static-blog as simple as possible. There are no magic tricks, and all of the source code is meant to be easy to read, understand and modify.
Después de haberlo mirado un poco, he de coincidir con el autor: ha sido fácil ajustar la funcionalidad a lo que quería conseguir sin complicarme mucho.
Una de las cosas que es ajustable es «ignorar» variables como
org-export-with-toc
o org-export-with-section-numbers
y en su
lugar, he incluido una línea #+options:
. La ventaja es que puedo
determinar si quiero números o índice en cada artículo, en lugar de
limitarlo en todos. La función de crear un nuevo artículo me ha
quedado así:
;;;###autoload (defun org-static-blog-create-new-post (&optional draft) "Creates a new blog post. Prompts for a title and proposes a file name. The file name is only a suggestion; You can choose any other file name if you so choose." (interactive) (let ((title (read-string (org-static-blog-gettext 'title)))) (find-file (concat (if draft org-static-blog-drafts-directory org-static-blog-posts-directory) (read-string (org-static-blog-gettext 'filename) (concat (format-time-string "%Y-%m-%d-" (current-time)) (replace-regexp-in-string "\s" "-" (downcase title)) ".org")))) (insert "#+title: " title "\n" "#+date: " (format-time-string "<%Y-%m-%d %H:%M>") "\n" "#+filetags: " "\n" "#+options: H:3 num:nil toc:nil \\n:nil ::t |:t ^:nil -:nil f:t *:t <:t")))
Estructuras fijas de los ficheros
El código fundamental que falta en el org-static-blog
son las
cabeceras y pies de página. El sistema org-page
lleva soporte para
plantillas mustache que facilitan la vida en este sentido. Sin
embargo, en este sistema va en el mismo código. Hay que cargar ese
código en sus correspondientes variables:
Para la cabecera del
HTML
:(defcustom org-static-blog-page-header "<meta name=\"author\" content=\"Notxor\"> <meta name=\"referrer\" content=\"no-referrer\"> <link href= \"/medios/css/estilos.css\" rel=\"stylesheet\" type=\"text/css\" /> <link rel=\"icon\" href=\"/medios/img/favicon.png\">" "HTML to put in the <head> of each page." :type '(string) :safe t)
Básicamente establece el autor, la dirección del fichero de estilos CSS y el
favicon
de la página. En mi caso, el autor lo pone fijo, pero no costaría mucho establecer una variable que cargue el contenido desde la cabecera, por ejemplo.Para el encabezamiento de cada página:
(defcustom org-static-blog-page-preamble "<div> <header class=\"masthead\"> <h1 class=\"masthead-title\"><a href=\"/\">Notxor tiene un blog</a></h1> <h2 class=\"masthead-subtitle\">Defenestrando la vida</h2> <img class=\"avatar\" src=\"/medios/img/abatar.png\" width=\"100px\"></img> <ul> <li><a href=\"/\">Blog</a></li> <li><a href=\"/tags/esperanto\">Esperanto</a></li> <li><a href=\"/tags/radio\">Radio</a></li> <li><a href=\"http://blog-antiguo.nueva-actitud.org\" target=\"_blank\">Blog antiguo</a></li> <li><a href=\"/etiquetas.html\">Etiquetas</a></li> <li><a href=\"/about/\">Acerca de...</a></li> <li><a href=\"/rss.xml\">RSS</a></li> </ul> <form method=\"get\" id=\"searchform\" action=\"//www.duckduckgo.com/\"> <input type=\"text\" class=\"field\" name=\"q\" id=\"s\" placeholder=\"Buscar en DuckDuckGo\"> <input type=\"hidden\" name=\"as_sites\" value=\"https://notxor.nueva-actitud.org\"> </form> </header> </div>" "HTML to put before the content of each page." :type '(string) :safe t)
Como se puede comprobar, la definición del «título» y el «subtítulo», el habitual menú de secciones con sus correspondientes enlaces y el formulario de buscar.
Para el pie de página:
(defcustom org-static-blog-page-postamble "<div> <script src=\"/medios/js/jquery-latest.min.js\"></script> <script src=\"https://polyfill.io/v3/polyfill.min.js?features=es6\"></script> <script id=\"MathJax-script\" async src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\"></script> <script src=\"/medios/js/main.js\"></script> <div class=\"footer\"> <p>Generado con <code>org-static-blog</code> en <i>Emacs</i></p> <p> <img style=\"display: inline-block;\" src=\"/medios/img/cc-by-nc-sa.png\" align=\"middle\" /> <br />2012 - <span id=\"footerYear\"></span> <a href=\"mailto:notxor@nueva-actitud.org\">Notxor</a> - Powered by <a href=\"https://github.com/bastibe/org-static-blog\" target=\"_blank\">org-static-blog</a> <script type=\"text/javascript\">document.getElementById(\"footerYear\").innerHTML = (new Date()).getFullYear();</script> </p> <table> <tr> <td> <b>Donar por Paypal</b> </td> <td> <b>Donar por Liberapay</b> </td> </tr> <tr> <td> <!-- Inserción de botón pypal --> <form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\" target=\"_top\"> <input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\" /> <input type=\"hidden\" name=\"hosted_button_id\" value=\"29VXCW8SRNJ78\" /> <input type=\"image\" src=\"https://www.paypalobjects.com/es_ES/ES/i/btn/btn_donateCC_LG.gif\" border=\"0\" name=\"submit\" title=\"PayPal - Donar online de forma segura.\" alt=\"Botón Donar con PayPal\" /> <img alt=\"\" border=\"0\" src=\"https://www.paypal.com/es_ES/i/scr/pixel.gif\" width=\"1\" height=\"1\" /> </form> </td> <td> <!-- Inserción de botón liberapay --> <script src=\"https://liberapay.com/Notxor/widgets/button.js\"></script> <noscript> <a href=\"https://liberapay.com/Notxor/donate\"> <img alt=\"Donate using Liberapay\" src=\"https://liberapay.com/assets/widgets/donate.svg\"></a> </noscript> </td> </tr> </table> </div> </div>" "HTML to put after the content of each page." :type '(string) :safe t)
Básicamente carga el poco javascript que utiliza el blog: coloreado de sintaxis, visualización de fórmulas matemáticas y el necesario para los botones de donación1. También está la información sobre la licencia y un enlace al código fuente que lo anima.
Los path
Una de las cosas que me encontré fue la manía que tiene el sistema de
utilizar las URL de forma absoluta, comenzando por la dirección
pública de la página. Eso coarta en gran medida las pruebas de
visualización del sitio en local. Una costumbre que he adquirido tras
escribir un artículo... monto un servidor local, bien con M-x
httpd-start
o desde una consola con python3 -m http.server 8080
luego lo abro con un navegador y lo pruebo. Veo si está todo correcto
en su sitio, si las imágenes las he puesto bien y aparecen todas. Lo
leo despacio por si encuentro algún error2.
Por todo ello, en muchos sitios he sustituido las llamadas a
org-static-blog-get-absolute-url
por una llamada a
org-static-blog-get-local-url
. Una función que la metí yo con un
calzador para la tarea.
(defun org-static-blog-get-local-url (relative-url) "Esta es mía...Returns local URL based on the RELATIVE-URL passed to the function." (concat "/" (org-static-blog-get-relative-path relative-url)))
¿Qué consigo con esto? Pues básicamente que el blog completo sea navegable en local y no intente salir a Internet cada vez que pinches un enlace.
Además, para poner un poco de coherencia en los path del sitio, me
empeñé en que las URL de los artículos se distribuyan por directorios
con el formato año/mes/día
y el título del mismo.
Para esto he tenido que hacer un par de cambios más. Uno de ellos viene recomendado en el mismo código:
(defun org-static-blog-generate-post-path (post-filename post-datetime) "Returns post public path based on POST-FILENAME and POST-DATETIME. By default, this function returns post filepath unmodified, so script will replicate file and directory structure of posts and drafts directories. Override this function if you want to generate custom post URLs different from how they are stored in posts and drafts directories. For example, there is a post in posts directory with the file path `hobby/charity-coding.org` and dated `<2019-08-20 Tue>`. In this case, the function will receive following argument values: - post-filename: 'hobby/charity-coding' - post-datetime: datetime of <2019-08-20 Tue> and by default will return 'hobby/charity-coding', so that the path to HTML file in publish directory will be 'hobby/charity-coding.html'. If this function is overriden with something like this: (defun org-static-blog-generate-post-path (post-filename post-datetime) (concat (format-time-string \"%Y/%m/%d\" post-datetime) \"/\" (file-name-nondirectory post-filename))) Then the output will be '2019/08/20/charity-coding' and this will be the path to the HTML file in publish directory and the url for the post. Por defecto es: post-filename)" (concat (format-time-string "%Y/%m/%d" post-datetime) "/" (file-name-nondirectory post-filename)))
Meter ese código ahí tal cual funciona como se espera, se genera la
URL tal como dije antes y se crea el fichero html
en un directorio
dentro de su día, que está dentro de su mes y que a la vez está dentro
de su año.
Lo que no avisa la documentación de la función es que luego tienes que sustituir, allí donde se busque el contenido de un artículo que no lo busque en el sitio por defecto con el formato por defecto:
/YYYY-mm-dd-nombre-del-artículo.html
En algunas funciones tenemos que sustituir también las formas que generan url's de ese estilo, para que busquen en el sitio correcto. Básicamente es sustituir la forma que añade la fecha al nombre del archivo por algo así:
(concat "/" (format-time-string "%Y/%m/%d" post-date)
No estoy seguro de haberlo hecho en todos los sitios oportunos, el
código está disperso por distintas funciones que generan código html
y es un poco enredoso encontrar los sitios adecuados. Lo he repasado
varias veces y he visto que todos los enlaces que he probado
funcionan. Sin embargo, hay tantos pichorros donde pinchar que no
estoy seguro de haberlos probado todos. Si alguien encuentra algún
enlace raro que no lleva a ningún lado le agradecería que me avisara.
Estructura de directorios
El sistema funciona de una manera bastante sencilla de entender: hay
un directorio configurable donde escribir los artículos y otro donde
escribir los borradores. Cuando generas el sitio, coge todo lo que
haya en el directorio de referencia y genera los ficheros html
. No
los genera todos: sólo creará aquellos cuyo fichero html
, si existe,
sea más antiguo que su fichero org
. O dicho de otro modo: sólo
genera los modificados. En producción supongo que será mucho más
sencillo rápido y efectivo... org-page
empezaba a hacerse pesado la
generación del sitio.
Otro aspecto sobre los directorios es que todo va al directorio raíz, por defecto, ya he contado que hay que modificar algo el código para conseguir que siga el formato de crear directorios (para fechas) y para otras cosas.
Además se pueden generar listados de artículos según las etiquetas,
pero por defecto también las mete en el directorio raíz con el formato
tag-etiqueta.html
y contribuye al caos del raíz de forma
considerable, sobre todo si te gustan la etiquetas como a mí. El caso
es que me pareció muchos más manejable la forma de hacerlo de
org-page
que genera en un directorio tags/
una estructura de
directorios, con el nombre de las etiquetas y dentro de ellos su
correspondiente fichero index.html
con el contenido. Para ello,
modifiqué la función que genera esos ficheros de la siguiente manera:
(defun org-static-blog-assemble-tags () "Render the tag archive and tag pages." (org-static-blog-assemble-tags-archive) (dolist (tag (org-static-blog-get-tag-tree)) (org-static-blog-assemble-multipost-page (concat org-static-blog-publish-directory "/tags/" (downcase (car tag)) "/index.html") (cdr tag) (concat "<h1 class=\"title\">" (org-static-blog-gettext 'posts-tagged) " \"" (car tag) "\":</h1>"))))
Evidentemente, luego, al generar los enlaces que los visiten hay que modificar el código para que las encuentre con el mismo formato con código tal que:
(concat "/tags/" (downcase tag) "/index.html")
Además, hay que tener en cuenta los archivos rss.xml
que quiero que
se generen por etiqueta y hay que meterlos en algún sitio donde no se
pisen unos a otros... pero lo vemos más despacio a continuación.
Fichero rss.xml
por etiqueta
Otro problema que me planteaba con el sistema nuevo y que me resultaba una limitación es la generación de archivos RSS para cada etiqueta. De la gente que tengo constancia que sigue el blog, no accediendo al sitio, sino a través de lectores RSS, me constan, al menos tres de esos ficheros: el de la radio, el de Esperanto y el de Emacs. También sé que hay quien sigue el RSS general, no entiendo muy bien por qué.
El caso es que necesitaba generar los RSS por etiquetas y eso no era
algo que contemplara el sistema, así que... ¿por qué no hacerlo yo?
Pues manos a la obra: me puse a mirar el código y encontré una función
que genera el RSS general (org-static-blog-assemble-rss
). También
encontré otra que genera un ítem para cada artículo. que se llama
org-static-blog-get-rss-item
y recibe como parámetro el
=post-filename).
Partí del código de la primera pero añadiendo el parámetro tag
:
(defun org-static-blog-rss-for-tag (tag) "Assemble the tag RSS feed. The RSS-feed is an XML file that contains every blog post in a machine-readable format." (let ((system-time-locale "en_US.utf-8") ; force dates to render as per RSS spec (rss-filename (concat org-static-blog-publish-directory "/tags/" (car tag) "/" org-static-blog-rss-file)) (rss-items nil)) (dolist (post-filename (cdr tag)) (let ((rss-date (org-static-blog-get-date post-filename)) (rss-text (org-static-blog-get-rss-item post-filename))) (add-to-list 'rss-items (cons rss-date rss-text))) (setq rss-items (sort rss-items (lambda (x y) (time-less-p (car y) (car x))))) (org-static-blog-with-find-file rss-filename (concat "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" "<rss version=\"2.0\">\n" "<channel>\n" "<title><![CDATA[" org-static-blog-publish-title "]]></title>\n" "<description><![CDATA[" org-static-blog-publish-title "]]></description>\n" "<link>" org-static-blog-publish-url "</link>\n" "<lastBuildDate>" (format-time-string "%a, %d %b %Y %H:%M:%S %z" (current-time)) "</lastBuildDate>\n" org-static-blog-rss-extra (apply 'concat (mapcar 'cdr rss-items)) "</channel>\n" "</rss>\n")))))
El parámetro tag
llega como una lista de cadenas donde la primera es
el nombre de la etiqueta y las siguientes son los ficheros que la
llevan. Por tanto, monto el rss.xml
en el mismo directorio que su
listado de ficheros, para no sobreescribir un fichero con otro y el
resto es idéntico a como se genera el RSS del blog.
Por un momento dudé si hacer la función interactiva o llamarla sólo para cada etiqueta. Al final, me decidí por hacerlo de una manera alternativa. Como no todas la etiquetas son relevantes, porque no todas tienen seguimiento, generar siempre los ficheros es un poco pérdida de tiempo. Así que lo solucioné de manera manual:
(defun org-static-blog-assemble-rss-by-tag () "Assemble the RSS for tags." (interactive) (mapcar 'org-static-blog-rss-for-tag (org-static-blog-get-tag-tree)))
Es decir, cuando llamo a la función
org-static-blog-assemble-rss-by-tag
se generarán todos los ficheros
rss.xml
─lo que lleva unos segundos─. ¿Por qué no generar sólo una
etiqueta o unas pocas? Pues porque hacer unas etiquetas y no otras
puede ser un poco confuso para los posibles lectores que lleguen al
blog cuando encuentren que tienen referencias de un tema determinado
y de otros no.
Como se ve, no es complicado adaptar un sistema tan sencillo: se pueden hacer muchas otras cosas y con muy pocas líneas de código.
El fichero CSS
Por otro lado, también ha habido un cambio de aspecto. He cambiado tipos de letras, colores y alguna cosilla más con la esperanza de hacerlo un poco más atractivo. Sin embargo, como podéis ver, tampoco es que mis capacidades de diseño sean sobresalientes. No ha quedado mal del todo.
Me había cansado del tema oscuro y aprovechando que iba a hacer cambios, me puse también manos a la obra con el aspecto. Aún me quedan algunas cosas que pulir.
El código completo
Sería muy largo poner aquí todo el código implicado. Se sale del tamaño razonable de un artículo del blog. Si alguien quiere acceder a él puede mirarlo en el siguiente repositorio:
Conclusiones
Ha habido más cambios, por ejemplo en el código también he añadido la traducción al Esperanto de las cadenas del sistema. Al principio de comenzar a manejarlo pensé que debía utilizar dos blogs para cada una de las lenguas. Después he visto que me podía ajustar de otros modos.
Por supuesto el código que he subido al repositorio se puede utilizar como mejor veáis. Si hago cambios en el sistema para ajustarlo, para corregir algún bug o para dotarlo de alguna característica nueva, lo subiré al repositorio, pero tampoco tengo el ánimo de convertirlo en un proyecto que mantener... no me da de sí el tiempo como para comprometerme.
Al final, el sistema es tan sencillo que me parece hermoso, se entiende cómo funciona sin demasiado esfuerzo, es altamente personalizable y hasta un psicólogo como yo lo puede hacer sin ayuda.
Si estáis pensando en hacer un blog, queréis un sistema estático y sois usuarios de Emacs, es la alternativa estáis buscando... incluso le podéis automatizar la vista previa de forma muy sencilla:
(httpd-serve-directory org-static-blog-publish-directory) (httpd-start)
Así no necesitas ni salir de Emacs para hacer las pruebas.
Comentarios