Kabosu - Creando cosas

Logo de la página. Gato esférico con colores verdoso.

Coloreado de sintaxis para el blog

Publicado: 2024-06-08 (actualizado 2024-08-02)

Etiquetas: Blog, Proyectos, Python


En los artículos de este blog suele haber bastantes trozos de código. Aunque tengo el estilo configurado para que los bloques de texto formateado <pre></pre> tengan un tamaño un poco más grande y márgenes más anchos para facilitar la lectura no me terminaba de gustar del todo verlos como texto plano.

Estuve investigando cómo añadir coloreado de sintaxis al código de los artículos con alguna librería Python o JavaScript y encontré varias que lo hacían. De ellas parecía que la más usada y versátil era Pygments por lo que hace unos días me puse a mirar cómo se usaba. Este módulo recibe un trozo de código como cadena de texto y un lenguaje y devuelve un HTML con estilo para que se muestre coloreado.

Incluyendo trozos código fuente en markdown

La forma típica de presentar código en markdown es hacerlo dentro de un bloque de texto formateado (con ```). Tras las comillas se puede indicar en qué lenguaje está. Por ejemplo, un código en Python se escribiría en un bloque que empezaría así:

 ```python3
 print('¡hola mundo!')

Aunque indiquemos el lenguaje del bloque de texto la librería de markdown que usaba (commonmark) no colorea la sintaxis pero sí añade una clase HTML con el nombre language-XXX. Si inspeccionas el HTML del bloque anterior verás que tiene la etiqueta <code class="language-markdown"> indicando que yo he definido ese bloque como lenguaje markdown.

Aprovechando esas clases HTML, mi plan inicial era:

  1. A partir del texto markdown del artículo generar el código HTML con la librería commonmark.
  2. Buscar en el HTML etiquetas con una clase que empiece con language- y extraer el texto que hay dentro.
  3. Generar el HTML coloreado utilizando Pygments.
  4. Reemplazar el bloque de texto del paso 2 por el generado en el paso 3.

Aunque no es una tarea excesivamente difícil sí que iba a requerir escribir bastante código así que me puse a investigar si alguien ya había creado alguna forma de integrar las dos librerías que necesito. Al echar un vistazo al repositorio de commonmark vi que el proyecto dejó de mantenerse hace algo más de 2 años y yo empecé a usarlo hace apenas 3 meses. La descripción del proyecto en GitHub lo dejaba claro pero yo por alguna razón no lo leí: "DEPRECATED: Python CommonMark parser".

Me puse a buscar un módulo con el que sustituir a commonmark y al parecer la gente suele inclinarse por markdown-it-py. Además este generador permite integrar Pygments por lo que el cambio está más que justificado.

Migrando a markdown-it-py

Empezar a usar el nuevo módulo fue muy sencillo. Simplemente tuve que cambiar la línea commonmark.commonmark(content) por una llamada al render de MarkdownIt. Todo el código que cambié está aquí:

from markdown_it import MarkdownIt

MD = MarkdownIt('commonmark')

def convertContent(content):
   return MD.render(content)

Al crear el objeto MarkdownIt hay que indicarle qué variante de markdown queremos usar. A mí me gusta la especificación CommonMark así que es la que elegí. No confundir con el módulo de Python commonmark que es el software que usaba para generar las páginas del blog.

Tras comprobar que todos los artículos anteriores del blog se generaban bien actualicé el texto un par de ellos que mencionaban el uso del módulo commonmark y añadí una nota para sugerir markdown-it-py como reemplazo.

Coloreando sintaxis

Para que MarkdownIt genere código HTML "coloreado" en vez de bloques de texto sin formato, hay que definir una función que se llamará automáticamente para cada bloque que haya en el fichero.

import pygments
import pygments.formatters
import pygments.lexers


def synxtaxHighlighter(content, lang, attrs):
    if not lang:
        return content

    lexer = pygments.lexers.get_lexer_by_name(lang)
    formatter = pygments.formatters.HtmlFormatter()
    return pygments.highlight(content, lexer, formatter)

Esta función recibe el trozo de código, el lenguaje que hemos indicado para el bloque de texto y unos atributos que no tengo muy claro qué son y no utilizo. Lo único que hay que hacer es cargar el "lexer" para el lenguaje que queremos colorear (python3, markdown, javascript, ... Pygments soporta cientos de ellos). Crear un formateador a HTML y luego utilizar la función highlight de Pygments.

Al instanciar MarkdownIt (en mi caso con el nombre MD) tenemos que indicarle qué función tiene que llamar cuando encuentre un trozo de código.

MD = MarkdownIt('commonmark', { 'highlight': synxtaxHighlighter })

Tras esto ejecuté el script para regenerar todo el blog y... el código no salía coloreado.

El HTML generado por Pygments es bastante críptico pero básicamente está utilizando etiquetas <span> para añadir clases que asignan un estilo a cada palabra. Mi código no salía coloreado porque no había incluido el CSS en mi página. ¿Dónde estaba ese fichero CSS que faltaba?

<code class="language-python3"><div class="highlight"><pre>
<span></span><span class="n">MD</span> <span class="o">=</span> <span class="n">MarkdownIt</span>
<span class="p">(</span><span class="s1">&#39;commonmark&#39;</span><span class="p">,</span> 
<span class="p">{</span> <span class="s1">&#39;highlight&#39;</span><span class="p">:</span> 
<span class="n">synxtaxHighlighter</span> <span class="p">})</span>
</pre></div>
</code></pre>

Pues al parecer no existe fichero como tal sino que se obtienen las reglas de estilo mediante la función get_style_defs. Esas reglas se pueden pegar tal cual en un fichero .css. Aquí dejo el código con el que genero el CSS y luego lo escribo en un fichero. En mi caso lo guardé en www/assets/syntax.css.

def getCss():
    return pygments.formatters.HtmlFormatter().get_style_defs('.highlight')


with open(OUTPUT_LOCATION / 'assets' / 'syntax.css', 'w') as fout:
	print(getCss(), file=fout)

Tras esto solo tuve que cargar la hoja de estilo en los artículos.

	<link rel="stylesheet" href="/assets/syntax.css" />

Por si tienes curiosidad, estas son algunas de las lineas del CSS que usa Pygment. Así de paso puedo ver cómo colorea la sintaxis de CSS.

.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #008000; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */

Y esto es todo. Si has visto coloreados los distintos trozos de código de este texto quiere decir que todo ha ido bien. He editado todos los artículos anteriores del blog para añadir información sobre el lenguaje de cada bloque de texto.


Artículo siguiente: Bot de traducción para Telegram
Artículo anterior: El Arca de Rogulé - parte 2