MeryClaire: uno static blog generator scritto in Elixir in un weekend

Cosa succede quando un programmatore si trova senza far niente in una grigia giornata di pioggia? Semplice: scrive uno static blog generator in Elixir!

Cos’è uno static blog generator? Generalmente un blog per funzionare ha bisogno di un backend, di un database e di un’applicazione che permetta agli autori di scrivere i post e ai lettori di poterli leggere.

WordPress è l’esempio più conosciuto e diffuso tra i blogger: scritto in PHP con un database MySQL ha un’interfaccia di amministrazione molto sofisticata per pubblicare i contenuti e una parte pubblica per poterli leggere.

Ma ammettiamo che a noi piaccia la semplicità e l’immediatezza, ammettiamo di non avere a disposizione un server  su cui installare MySQL, PHP e tutto ciò che serve per eseguire WordPress. Ammettiamo di avere solo un web server in grado di erogare pagine statiche o di volerci appoggiare a Github per l’hosting.

Ci serve uno static blog generator.

Ne esistono a decine, e si potrebbero passare giorni a valutarli, provarli e una volta selezionato quello più adatto altri giorni a installarlo, configurarlo e finalmente a generare i contenuti.

Oppure, se sei un programmatore con un paio di giornate libere, magari un weekend piovoso, te ne puoi scrivere uno.

E’ quello che ho provato a fare, spinto più che altro dalla curiosità che dalla reale necessità. Siamo programmatori? Ai programmatori piace crearsi i propri tool.

Cosa fa esattamente uno static blog generator?

Nella sua accezione più semplice si tratta di uno script o di un’applicazione che configurata opportunamente prende un set di file con i testi e le immagini e genera l’HTML+CSS necessario alla consultazione. Essendo static le pagine del blog non saranno generate a runtime ma esisteranno dei veri e propri file HTML contenenti gli articoli.

E’ standard de facto scrivere i contenuti (gli articoli del blog) in markdown per la semplicità del formato e per il supporto su molti editor.

Gli static blog generator più diffusi sono Gatsby, Jekyll e Hugo, ma come scrivevo sopra ne esistono molti altri, tutti adottano un approccio simile.

Il progetto

Dovendo scegliere una prima funzionalità da cui partire ho scelto di provare a trasforamre un file .md in HTML, la prima idea è stata quella di scriversi un parser per Markdown e un generatore di HTML(siamo programmatori e programmatrici?), ma visti i tempi stretti ho pensato di cercare una libreria che già facesse questa operazione. Il caso vuole che Phoenix abbia al suo interno una libreria che si occupa proprio di trasformare il markdown in HTML (ho sempre detto che Phoenix è  un mega-framework…). La libreria in questione si chiama Earmark che abbinata a Eex è in grado di compiere questa prima parte di lavoro.

Apprese queste informazioni di base sono partito con questo script per testare la generazione:

"test_file.md"
  |> File.read!()
  |> Earmark.as_html!(%{gfm: true, breaks: true})
  |> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)

 

Queste poche righe di codice trasformano il file markdown test_file.md in una stringa con il contenuto in formato HTML. Non entro nei dettagli delle opzioni che potete trovare sulla doc di Earmark

Completato con successo il primo test, quello su cui avevo i maggiori dubbi, sono passato al progetto vero e proprio. Ma di prima di scrivere una riga di codice mi sono fermato a pensare…

Come funziona uno Static Blog Generator?

Gatsby e Jekyll sono progetti open source e quindi è interessante analizzare come funzionano internamente per farsi un’idea dell’approccio da utilizzare.

Di fatto sono dei compositori che prendono vari tipi di file e li mergiano secondo una logica dettata prevalentemente da convenzioni di progetto.

Generalmente un blog è composto da queste pagine:

  • Un indice che è anche la home page che mostra un elenco degli ultimi N post
  • Una pagina per ogni post con il testo, immagini, link, ecc…
  • Un pagina archivio con l’elenco di tutti post scritti
  • Una pagina about con informazioni sul blog e sull’autore

l tutto condito da file CSS che permettono di rendere personalizzato, bello e leggibile il blog. Oltre alla gestione di queste tipologie di pagine, generalmente mettono a disposizione un insieme di template per gestire “lo scheletro” del blog: header, footer, parti comuni. Queste sono anche le parti che posso essere personalizzate andando a modificare l’HTML di partenza e/o i CSS del tema.

L’idea è stata quindi quella di partire definendo due set di file: uno per le varie pagine (chiamati template) e un altro set per i contenuti. Il compito dell’engine di generazione è quello di prendere i file di contenuto, applicargli il template, compilarli/trasformarli e infine salvare in una cartella di destinazione con tutti gli asset generati.

Per i file template ho deciso di utilizzare l’engine di eex  integrato in Phoenix per avere a disposizione un linguaggio completo per customizzare i layout delle pagine.

Sono quindi partito dal definire una struttura di cartelle:

  • _assets contiene immagini, icone e altri file che devono essere copiati as-is nella cartella di destinazione.
  • _posts contiene i file markdown che rappresentano i post da pubblicare
  • _scss contiene i file sass (css) per stilare il sito
  • _templates contiene i template html per le pagine index, post, archive e about.

A livello di architettura non è necessaria una vera applicazione, di fatto è sufficiente uno script che quando viene lanciato esegue il flusso di task  necessari alla generazione delle pagine.

Elixir per questo genere di progetti mette a disposizione il modulo Mix.Task, una sorta di script eseguito dal terminale tramite il comando mix nome_task.

Il primo task realizzato è quello addetto alla generazione. A parte la gestione di eventuali argomenti passa semplicemente la chiamata al modulo Generator

defmodule Mix.Tasks.Gen do
  use Mix.Task

  @shortdoc "build the blog"
  def run(args) do
    env =
      case args do
        [] -> nil
        [env] -> env
        _ -> nil
      end

    MeryClaire.Generator.run(env)
  end
end

Il generatore vero e proprio è nel modulo Generator

def run(_env) do
  generate_posts()
  generate_index()
  generate_about()
  generate_archive()
  generate_css()
  copy_assets()
end

Il codice è abbastanza semplice (e procedurale…) e non richiede ulteriori commenti (si può ottimizzare? si, ma vi interessa risparmiare 42 millisecondi durante la generazione?)

Proviamo ad analizzare gli step più interessanti:

 defp generate_posts() do
    Path.wildcard("#{Settings.posts()}/*.md")
    |> Enum.sort_by(& &1, :desc)
    |> Enum.map(&get_header/1)
    |> Enum.map(&generate_post/1)
  end

Il codice sopra prende tutti i file con estensione md presenti nella cartella dedicata ai post (_posts), li ordina per nome (per convenzione i nomi dei post iniziano con la data) estrae l’header del post e genera l’HTML.

I post hanno un header contenente meta informazioni utili alla personalizzazione dei contenuti: 

---
title: Mery Claire - A minimal but complete blog static generator
tag: static_site_generator 
date: 2023-04-09
abstract: What is Mery Claire and how does it works...
---

There are lots of site and blog static generators. Most of them are full of tons of features and configuration options that needs pages of documentation and tutorias to get started.

L’header (compreso tra i due marker ---) contiene il titolo, uno o più tag, una data e un abstract. Tecnicamente potete aggiungere quello che volete, ogni chiave: valore aggiunto all’header è una chiave/valore disponibile nella generazione del post.

defp generate_post(header) do
  # ...

  html = MdParser.compile(body, out_file)
  page = compose_html("post.html.heex")
  result = EEx.eval_string(page, [content: html] ++ header ++ globals)

  File.write("#{destination}/#{out_file}", result)
end

Infine la generate post si avvale delle due librerie descritte prima per leggere il contenuto in markdown e generare l’html.

A EEx viene passato il contenuto in HTML più tutto ciò che è presente nell’header sotto forma di keywordlist.

EEx è lo stesso engine utilizzato da Phoenix per generare l’HTML delle pagine, per cui nei template abbiamo a disposizione tutto quello che serve per fare cicli, if, partial file, ecc… il livello di personalizzazione che possiamo raggiungere è illimitato.

Una volta generato il file HTML finale viene salvato nella cartella di destinazione.

Le altre funzioni (generate_index, generate_about, generate_archive, generate_css ) sono delle varianti meno complicate di quella che genera i post per cui lascio ai lettori l’onere di guardare nel repository come sono state implementate (https://github.com/emadb/meryclaire)

A livello di infrastruttura l’applicazione contiene anche un dev server che pubblica il blog via http per lo sviluppo e test locale. E’ presente anche un filewatcher che a fronte di una modifica dei file sorgente rigenera tutto il sito senza dover fermare  il server e riavviare l’intera procedura di compilazione.

Sviluppare il progetto è stato divertente e interessante, confesso che temevo fosse più complesso realizzare uno static blog generator invece si è rivelata una cosa abbastanza semplice se quello che si vuole ottenere ha dei confini definiti e non è richiesta troppa generalizzazione. MeryClaire si può usare in un contesto reale? Credo proprio di si, di fatto genera dell’HTML che è possibile controllare e all’occorrenza correggere se qualcosa non va. Supporta l’hosting su Github e quindi può essere pubblicato facilmente senza costi.

Se vi va di provarlo fatemi sapere cosa ne pensate…