Blog de Raúl Salinas

De Markdown a WordPress sense dolor: així funciona el meu sistema de blog en Python

Durant molt de temps vaig escriure articles en Markdown i, quan tocava publicar-los a WordPress, apareixia sempre la mateixa fricció: els diagrames (sobretot Mermaid) no entraven bé, les imatges calia preparar-les a mà i el flux acabava sent una barreja de passos repetitius i fàcils de trencar.

La motivació inicial va ser molt senzilla: volia una ferramenta que, donat un document Markdown amb diagrames, em generara les imatges i em preparara un contingut llest per a incrustar a WordPress. Sense copiar-pegar eterns, sense scripts solts i sense dependre de recordar tots els passos.

Amb el temps, aquell script inicial va evolucionar cap a quelcom més ambiciós: un sistema complet per a gestionar diversos blogs des de terminal, cadascun amb els seus articles versionats en diversos idiomes, publicació controlada en un o més servidors WordPress, traducció assistida, comprovacions d'estat i còpies de seguretat.

El resultat és una CLI en Python que es diu blog.py.

Quin problema resol

En el meu cas, el coll de botella no era escriure, era operar:

  • convertir diagrames Mermaid a imatges,
  • generar una versió compatible amb WordPress,
  • mantindre sincronitzades distintes versions d'idioma,
  • publicar o deixar en esborrany amb seguretat,
  • saber si WordPress ha canviat des del meu darrer publish,
  • publicar el mateix blog en diversos servidors (mirrors),
  • i poder recuperar-ho tot amb backups.

blog.py unifica tot això sota una única ordre.

La idea central: separar programa i dades

La peça que més ha fet créixer el sistema és una distinció senzilla però potent: el programa va per una banda i les dades per una altra.

  • El programa (els .py i el Makefile) viu a l'arrel del repositori.
  • Les dades viuen per site: cada blog de contingut independent penja de sites/<site>/.
templates/post.md          # plantilla compartida per a 'new'
siteconfig.py              # carrega sites/<site>/site.toml + .env
sites/
    raulsa8/               # site personal (raulsa8.blogs.uv.es)
        site.toml          # idiomes, [[servers]], Telegram
        .env               # secrets (fora de git): contrasenyes, token
        posts/<post>/
            <base>_ca.md   # versió en valencià
            <base>_es.md   # versió en castellà …
            <base>_<lang>.<servidor>.state.json
        build/  backup/    # generats (gitignored)
    veg/                   # un altre site, un altre blog

Això separa amb claredat dos tipus de configuració:

  • El que depèn del blog (site): el contingut, el frontmatter, els diagrames i els idiomes admesos. Es defineix a site.toml.
  • El que depèn del servidor: la URL de WordPress, l'usuari, la contrasenya i tot l'estat (post_id, mèdia, enllaç). Per això l'estat es guarda per servidor.

Multi-site i multi-servidor

Un mateix programa gestiona diversos blogs, i cada blog pot publicar-se en diversos servidors WordPress alhora (mirrors):

./blog.py -s raulsa8 publish el-meu-post ca     # site explícit
./blog.py publish el-meu-post ca                  # site per defecte, a TOTS els seus servidors
./blog.py -S mirror publish el-meu-post ca        # a un servidor concret
  • -s/--site tria el blog. Si no l'indiques, s'usa el que tinga default = true, o $BLOG_SITE, o l'únic disponible.
  • -S/--server tria el servidor. En lectures (diff, status, tg-check) s'usa el primari; en escriptures (publish, unpublish) es va a tots els servidors llevat que n'indiques un.

L'estat per servidor (<base>_<lang>.<servidor>.state.json) guarda el post_id, els mèdia pujats (amb el seu sha256) i l'enllaç en eixe servidor. Si el fitxer no existeix, eixe post no s'ha publicat mai allí.

Filosofia tècnica: Python orquestra, Make executa

La idea és esta: Python com a capa d'orquestració amigable, Make com a motor d'execució.

La majoria de subordres deleguen en make, així no es perd funcionalitat prèvia ni es refan peces que ja funcionaven bé. Però la lògica més enrevessada (traducció, diff, status, tg-check, where, edit) viu directament en Python per a simplificar-ne el manteniment.

En altres paraules:

  • make continua sent la base tècnica,
  • blog.py aporta ergonomia, autocompletat i una interfície coherent.

Idioma sempre explícit

Una decisió de disseny deliberada: no hi ha idioma per defecte. Cada ordre exigeix l'idioma de manera explícita (o --all-lang per a totes les versions). Així mai es publica una versió equivocada per descuit.

Flux de treball habitual

Un flux real típic podria ser:

./blog.py new el-meu-post ca
./blog.py edit el-meu-post ca
./blog.py render el-meu-post ca
./blog.py publish-draft el-meu-post ca

I quan vols escalar-lo a diversos idiomes:

./blog.py translate el-meu-post en --from ca
./blog.py translate el-meu-post es --from ca
./blog.py publish-all

L'avantatge és que no has de recordar ordres disperses ni rutes internes: la CLI concentra el model mental.

Autocompletat amb TAB

Un dels detalls que més productivitat dóna en el dia a dia és l'autocompletat de sites, servidors, noms de post i idiomes.

Com que blog.py es basa en argparse, l'autocompletat l'aporta argcomplete: en este sistema el seu hook global ja està instal·lat, així que n'hi ha prou amb obrir una shell nova i usar:

./blog.py <TAB>

Si en la teua sessió no està actiu, pots habilitar-lo així:

eval "$(register-python-argcomplete ./blog.py)"

Sembla un detall menut, però redueix errors tontos i accelera moltíssim l'operació diària.

Subordres principals (i per a què serveixen)

blog.py exposa un conjunt d'ordres que cobreixen tot el cicle de vida del contingut:

  • list: llista articles i idiomes disponibles.
  • new: crea un post buit des de la plantilla.
  • render: genera diagrames i construeix wordpress.md.
  • extract: extrau Mermaid i genera wordpress.md.
  • diagrams: renderitza únicament els diagrames Mermaid a imatge.
  • publish: crea o actualitza i publica en viu.
  • publish-draft: crea o actualitza com a esborrany.
  • publish-all: publica totes les versions de tots els articles.
  • unpublish: torna un contingut a estat esborrany.
  • translate: tradueix amb Claude i puja com a esborrany.
  • master: mostra o fixa l'idioma mestre d'un article (origen de les traduccions).
  • diff: mostra diferències entre local i WordPress.
  • status: comprova (en paral·lel) si WordPress ha sigut modificat des del nostre darrer publish.
  • pull: mostra (sense integrar) els canvis fets a mà a WordPress.
  • bad-slugs: detecta slugs col·lisionats (sufix -2/-3) per a enllaçar traduccions.
  • lang-check: verifica l'idioma real dels posts publicats.
  • tg-check: verifica que el token del bot de Telegram funciona.
  • backup: descarrega tot el blog a backup/.
  • export-static: genera un blog HTML estàtic a static/.
  • clean / distclean: netegen el build/ d'una versió o sencer.
  • where: imprimeix la ruta exacta del fitxer (per exemple, per a obrir-lo amb vim).
  • edit: obri el fitxer al teu $EDITOR.

No totes les ordres s'usen cada dia, però tindre-les unificades evita solucions ad hoc.

Multiidioma i versionat: la part que més valor aporta

Conforme creix un blog tècnic, mantindre versions en diversos idiomes es torna difícil si no hi ha estructura.

Este sistema ho resol recolzant-se en dos pilars:

  • fitxers d'articles gestionats de manera explícita per idioma,
  • versionat amb Git per a conservar traçabilitat, revisió i rollback.

Això permet treballar de manera incremental: escrius en un idioma base, tradueixes quan convé, revises diferències i publiques cada versió amb control.

Com que use Polylang gratuït, l'assignació d'idioma i l'enllaç entre traduccions es fa una sola vegada a mà al wp-admin. A partir d'ací, publish actualitza el contingut sense perdre eixe enllaç.

Publicar amb xarxa de seguretat

En publishing, la pregunta no és si hi haurà un ensurt, sinó quan. El sistema ho té en compte de dues maneres.

Primer, publish avorta si WordPress ha sigut modificat des de l'últim publicat, per a no trepitjar canvis manuals (pots forçar amb --force si saps el que fas). L'ordre status t'avisa d'eixes modificacions abans de tocar res, i pull te'ls mostra com un diff (sense integrar-los). A més, publish és idempotent: guarda una firma del contingut i només puja el que ha canviat, així que dos publish-all seguits no refan faena. I publish-all publica els articles en paral·lel (per defecte de 4 en 4).

Segon, l'ordre backup no és accessori: descarregar l'estat complet del blog a una carpeta local (backup/) afig una xarxa de seguretat real per a migracions, auditories ràpides o recuperació després d'errors.

Exportar a HTML estàtic

A més de publicar a WordPress, el sistema pot generar una versió HTML estàtica completa del blog amb export-static.

Renderitza tots els articles i produeix un lloc estàtic a sites/<site>/static/: una pàgina per article i idioma, índexs per idioma, selector d'idioma i sense comentaris. La cerca es recolza en Google amb l'operador site:, on base_url és la URL pública on allotjaràs l'estàtic (configurable a site.toml).

És ideal per a tindre una còpia navegable i independent del blog, sense dependre de WordPress per a llegir-lo.

Integració amb Telegram

A més del flux de publicació, el sistema contempla la part operativa de Telegram: en publicar pot notificar en un canal, amb xat configurable per idioma.

La subordre tg-check valida que el token del bot funciona correctament, quelcom molt útil quan tens automatitzacions de notificació al voltant del procés editorial. A la pràctica, t'evita descobrir un problema de credencials massa tard: pots comprovar la integració abans de publicar i mantindre la cadena de ferramentes en estat saludable.

Per què em funciona este enfocament

No és un framework gegant ni una plataforma tancada. És un sistema menut, pràctic i compost per peces conegudes:

  • Markdown com a font de veritat,
  • Mermaid per als diagrames,
  • Python per a orquestrar,
  • Make per a executar,
  • WordPress (un o diversos) com a destí,
  • i HTML estàtic com a còpia navegable.

Eixa combinació té una virtut important: redueix fricció sense sacrificar control tècnic.

Tancament

La ferramenta va nàixer per a resoldre un problema puntual (passar Markdown amb diagrames a un format publicable a WordPress) i va acabar convertint-se en una cadena completa de producció editorial des de terminal: diversos blogs, diversos idiomes, diversos servidors i exportació estàtica, tot sota una única ordre.

Si t'agrada escriure en local, versionar-ho tot i publicar amb confiança, un enfocament així marca la diferència: menys treball mecànic, menys errors i més focus en allò que de veritat importa, que és el contingut.

Figura 1
Figura 1