Pelican est un outil Python pour générer des sites statiques. Dans cet article, nous verrons dans le détail comment l'installer et le configurer de façon à créer un blog personnel. URLs propres, flux RSS, thèmes, plugins indispensables... tous les points essentiels seront abordés. Vous pourrez alors constater que Pelican peut avantageusement remplacer un Wordpress.
Si les causes sont multiples, une chose est sûre : plus l'interface de publication est désagréable à utiliser, moins on l'utilisera.
Les nouvelles plate-formes à la mode type Medium.com essaient d'innover sur ce point. Mais pour un développeur, il n'y aura jamais meilleure interface que celle dont il connaît par cœur tous les raccourcis clavier : celle de son éditeur de code favori.
Cet article va vous montrer comment utiliser Pelican comme moteur de blog : « Libérez votre potentiel d'auteur en 30 minutes, satisfait ou remboursé ! ».
1. Pelican: un générateur de sites statiques
Pelican est un outil Python pour générer des pages HTML à partir de contenus textes structurés (formats ReST, Markdown, Org). Vous écrivez déjà vos README en Markdown sur Github, pourquoi pas vos articles de blog ?
Tout est d'ailleurs prévu dans Pelican pour construire un blog : il connaît la notion d'article, de brouillon, de page, de flux RSS et bien sûr de thème. Pour les templates, il utilise Jinja dont la syntaxe est très proche des templates Django.
Pelican peut même récupérer les articles d'un Wordpress, à partir de l'export XML :
$ pelican-import -m markdown --wpfile -o ./content ./content.xml
2. Blog statique VS blog dynamique
On a déjà évoqué le confort d'écrire ses articles de blog depuis son éditeur chéri. Quels autres avantages offre Pelican par rapport à un classique Wordpress ?
Avec Pelican, vous n'avez pas besoin de vous soucier de l'hébergement. Vous ne générez le contenu HTML que lorsque vous ajoutez un nouvel article ou que vous modifiez votre thème. Ensuite n'importe quel hébergeur de fichiers basique fera l'affaire. Vous pouvez par exemple utiliser le service gratuit Github Pages ou un serveur Nginx chez vous, sur un Raspberry, sans crainte pour les performances.
L'autre avantage du site statique c'est qu'il ne présente aucun risque de compromission. Wordpress, désormais le moteur de plus de 17% du Web, est lui au contraire la cible numéro 1 de tous les scripts kiddies du monde. Si le code de Wordpress en lui même est sûr et bien revu, ce n'est pas le cas de tous les plugins tiers, dont les failles sont utilisées comme vecteur d'attaque.
Bien sûr, Pelican n'est pas pour tout le monde. Si vous partez faire le tour du monde à vélo sans pc, préférez-lui un compte sur un service que vous n'aurez pas à maintenir et auquel vous pourrez accéder via un simple navigateur.
3. Installation
On va considérer notre blog comme un projet de développement à part entière. À ce titre, il aura son environnement virtuel Python dédié, basé sur Python 2.7.
Pour ce faire, installez Virtual Env et VirtualEnv Wrapper, si ce n'est pas déjà fait :
$ sudo apt-get install python-virtualenv python-dev python-pip
$ sudo pip install virtualenvwrapper
N'oubliez pas de rajouter ceci à la fin de votre ~/.bashrc :
export WORKON_HOME=~/.virtualenvs
. /usr/local/bin/virtualenvwrapper.sh
On est maintenant armé pour créer notre virtualenv :
$ mkvirtualenv blog
Désormais quand on voudra travailler sur le blog, on tapera :
$ workon blog
On installe Pelican dans cet environnement :
$ pip install pelican Markdown typogrify
Puis on lance l'assistant de Pelican (il faudra répondre à quelques questions) :
$ pelican-quickstart
4. Configuration
Les réponses fournies à l'assistant ont servi à créer tous les fichiers et dossiers nécessaires au bon fonctionnement de Pelican :
.
├── content/
├── develop_server.sh
├── fabfile.py
├── Makefile
├── output/
├── pelicanconf.py
└── publishconf.py
Parmi eux, pelicanconf.py, LA tour de contrôle de Pelican. On va y apporter plusieurs modifications pour adapter Pelican à nos attentes.
Bon à savoir : toutes les valeurs de configuration par défaut sont définies dans ~/.virtualenvs/blog/lib/python2.7/site-packages/pelican/settings.py.
4.1 Pages
Le dossier content/ contiendra les textes des articles. Pour les pages, Pelican cherche par défaut dans le dossier pages/, il faut donc le créer :
$ mkdir content/pages
4.2 Ressources statiques
On va créer un dossier pour les images :
$ mkdir content/images
Par ailleurs, on va mettre dans un dossier extra/ d'autres fichiers statiques utiles pour le blog : un fichier .htaccess contenant des directives pour Apache par exemple.
On explicite tout ça pour Pelican dans pelicanconf.py :
STATIC_PATHS = [ 'images', 'extra/.htaccess', 'extra/robots.txt', 'extra/favicon.ico' ]
EXTRA_PATH_METADATA = {
'extra/.htaccess': { 'path': '.htaccess' },
'extra/robots.txt': { 'path': 'robots.txt' },
'extra/favicon.ico': { 'path': 'favicon.ico' },
}
4.3 URLs
Pour les articles, je souhaite des URLs propres contenant date et slug et sans extension de fichier, par exemple : /blog/2015/03/27/celery-django/
La solution est de configurer Pelican pour qu'il crée plusieurs répertoires :
ARTICLE_URL = 'blog/{date:%Y}/{date:%m}/{date:%d}/{slug}/'
ARTICLE_SAVE_AS = ARTICLE_URL + 'index.html'
Pour rester cohérent avec le format d'URL choisi, on crée des pages d'archives :
- une page contenant les archives complètes : /blog/ ;
- une page par année comme par exemple /blog/2015/ ;
- une page par mois comme par exemple /blog/2015/03/.
ARCHIVES_SAVE_AS = 'blog/index.html'
YEAR_ARCHIVE_SAVE_AS = 'blog/{date:%Y}/index.html'
MONTH_ARCHIVE_SAVE_AS = 'blog/{date:%Y}/{date:%m}/index.html'
Contrairement aux articles, les pages sont souvent intemporelles : la page « à propos » par exemple. On ne va donc pas inclure la date dans leur URL :
PAGE_URL = '{slug}/'
PAGE_SAVE_AS = PAGE_URL + 'index.html'
4.4 Organisation du contenu
Je serai le seul auteur sur le blog. On désactive donc les pages ou flux RSS superflus :
AUTHOR_URL = False
AUTHOR_SAVE_AS = False
AUTHORS_SAVE_AS = False
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
De même, je ne souhaite pas utiliser de catégories pour organiser mes articles :
CATEGORY_URL = False
CATEGORY_SAVE_AS = False
CATEGORIES_SAVE_AS = False
CATEGORY_FEED_ATOM = None
CATEGORY_FEED_RSS = None
Je préfère recourir au système plus souple des tags. On crée donc une page par tag mais pas de page listant tous les tags ni de flux RSS dédiés :
TAG_URL = 'blog/{slug}/'
TAG_SAVE_AS = TAG_URL + 'index.html'
TAGS_SAVE_AS = False
TAG_FEED_ATOM = None
TAG_FEED_RSS = None
Et on crée un nuage de tags que j'afficherai sur la première page du blog :
TAG_CLOUD_STEPS = 4
TAG_CLOUD_MAX_ITEMS = 30
4.5 Flux RSS
Pour l'instant, on a modifié exclusivement pelicanconf.py. Il est temps d'introduire publishconf.py qui est le fichier de configuration utilisé au moment de la publication, c'est-à-dire au moment de mettre le site en ligne.
En l’occurrence, on va complètement désactiver la génération des flux RSS pendant la phase de rédaction (pelicanconf.py) :
FEED_RSS = None
FEED_ATOM = None
FEED_ALL_ATOM = None
FEED_ALL_RSS = None
TRANSLATION_FEED_ATOM = None
TRANSLATION_FEED_RSS = None
Mais on va l'activer au moment de la publication (publishconf.py) :
FEED_ALL_ATOM = 'feeds/all.atom.xml'
FEED_ALL_RSS = 'feeds/all.rss.xml'
À noter que je choisis de placer les articles entiers dans les flux (FEED_ALL_*). C'est parce que j'ai horreur, en tant que lecteur, des flux qui me forcent à aller sur le blog pour voir l'article complet.
4.6 Coloration syntaxique
Pelican utilise le module Python-Markdown pour la transformation des contenus Markdown en HTML. Ce module est livré avec une extension nommée CodeHilite qui se base sur la bibliothèque Pygments pour coloriser les blocs de code.
On passe donc des arguments à l'extension CodeHilite pour qu'elle configure Pygments en conséquence :
MD_EXTENSIONS = [ 'codehilite(css_class=highlight,linenums=False)' ]
Pygments propose plusieurs styles de coloration, exprimés sous forme de classes Python :
> from pygments.styles import get_all_styles
> print ', '.join(sorted(list(get_all_styles())))
autumn, borland, bw, colorful, default, emacs, friendly, fruity, igor, manni, monokai, murphy, native, paraiso-dark, paraiso-light, pastie, perldoc, rrt, tango, trac, vim, vs, xcode
> from pygments.styles.emacs import EmacsStyle
Pour générer le fichier CSS correspondant à un de ces styles :
$ pygmentize -S emacs -f html -a .highlight > pygments.css
On restreint l'application des classes de Pygments aux éléments de classe .highlight pour ne pas polluer tout le namespace CSS.
5. Premier lancement
On lance le serveur de développement :
$ make devserver
Cela lance Pelican en mode re-génération automatique : dès que vous modifiez un article, le thème ou les paramètres, le blog est re-généré. Cela lance aussi un serveur HTTP en local pour apprécier en direct le résultat de notre travail.
Rédigeons maintenant un premier article : content/test.md.
Title: Test
Date: 2015-04-22 15:19
Slug: test
# Ceci n'est pas un test
Yes we Pelican !
Une fois le fichier sauvé, vous devriez voir apparaître le message suivant dans votre terminal :
-> Modified: content. re-generating...
Ouvrez l'adresse suivante dans votre navigateur : http://localhost:8000. Tadam ! L'article apparaît. Bon, pour l'instant c'est le thème par défaut qui est utilisé donc ça ne crève pas l'écran. On va y remédier.
Exécutez make stopserver pour tuer les deux processus lancés par devserver.
6. Les thèmes
Un thème est un ensemble de templates HTML et de fichiers statiques CSS ou JS. Voici l'arborescence type d'un thème :
.
├── README.md
├── screenshot.png
├── static
│ └── css
│ ├── pygments.css
│ └── style.css
└── templates
├── archives.html
├── article.html
├── author.html
├── base.html
├── categories.html
├── category.html
├── header.html
├── index.html
├── page.html
├── pagination.html
├── tag.html
└── tags.html
De nombreux thèmes ont été créés par la communauté. On peut les prévisualiser sur http://pelicanthemes.com et les télécharger via le Github du projet :
$ cd ~/.pelican
$ git clone --recursive https://github.com/getpelican/pelican-themes
Pour activer un thème :
THEME = "/home/nils/.pelican/pelican-themes/pure"
Les thèmes se personnalisent via des variables dans pelicanconf.py, par exemple :
TAGLINE = 'Bidouilleur'
Toutes ces variables sont d'ailleurs accessibles dans les templates :
<h2>{{ TAGLINE }}</h2>
Une fois trouvé un thème qui vous plaît, je vous invite à en faire une copie dans le dossier themes/ pour mieux l'adapter :
THEME = "./themes/custom"
7. Les plugins
Pelican est facilement extensible via des plugins. De nombreux plugins ont été développés par la communauté et sont téléchargeables via le Github du projet :
$ cd ~/.pelican
$ git clone https://github.com/getpelican/pelican-plugins.git
Pelican ira chercher les plugins dans les dossiers spécifiés via PLUGIN_PATHS. Il n'activera que ceux explicitement définis dans PLUGINS :
PLUGIN_PATHS = ('/home/nils/.pelican/pelican-plugins', '/home/nils/.pelican/other-plugins', )
PLUGINS = [ 'nom_du_plugin' ]
7.1 Les plugins indispensables
7.1.1 Assets
Ce plugin sous-traite la gestion des assets du site à la bibliothèque webassets. Concrètement, cela permet d'automatiquement :
- compiler les CSS écrites dans un méta-language CSS tel Sass ou Less ;
- minifier les fichiers CSS / JS.
Ne pas oublier d'installer webassets dans l'environnement virtuel :
$ pip install webassets
Voilà ce que contient mon template base.html pour fusionner et minifier mes CSS dans un seul fichier style.min.css :
{% assets filters="cssmin", output="style.min.css", "css/normalize.css", "css/skeleton.css", "css/pygments.css", "css/gh-fork-ribbon.css", "css/custom.css" %}
<link href="/{{ ASSET_URL }}" rel="stylesheet">
Par défaut, webassets sauvegarde le résultat de son travail dans OUTPUT_PATH/theme.
7.1.2 Liquid Tags
Ce plugin étend la syntaxe Markdown pour permettre d'insérer des vidéos (Youtube / Vimeo) et des fichiers externes dans les articles. C'est très pratique pour inclure des codes sources tout en les gardant dans des fichiers séparés :
{% include_code celery/redis.conf lang:text :hidefilename: redis.conf %}
Dans pelicanconf.py, on explicite où LiquidTags ira chercher les fichiers au moment de les inclure :
CODE_DIR = 'code'
Ce dossier code/ sera lui-même dans le dossier content/. Ne pas oublier de l'ajouter à la liste des dossiers à copier :
STATIC_PATHS += [ 'code' ]
Ainsi, les visiteurs pourront voir le code source dans l'article ET télécharger le fichier correspondant.
7.1.3 Sitemap
Ce plugin produit un fichier sitemap.xml pour aider les moteurs de recherche à mieux indexer votre site.
Vous pourrez ainsi envoyer votre sitemap à Google via les GoogleWebmasterTools [1].
7.2 Écrire un plugin
Chaque plugin est un simple module Python. Il contient une fonction register qui permet de venir se greffer au fonctionnement de Pelican. Par exemple :
Fichier __init__.py :
from .test import *
Fichier test.py :
def test(sender):
print "Go go go!"
def register():
signals.initialized.connect(test)
Si l'on active ce plugin test, on verra un message s'afficher dans le terminal à chaque re-génération.
À chaque étape de la génération du site correspond un signal. Il en existe plus d'une vingtaine comme, par exemple : article_generator_write_article juste avant la sauvegarde de l'article, finalized lorsque Pelican a terminé son travail… [2].
8. Publication
make publish lance la génération du site avec les paramètres de publishconf.py.
Plusieurs méthodes sont ensuite possibles pour le mettre en ligne : FTP, SSH (SCP, Rsync), S3, Dropbox ou Github Pages.
8.1 Publier son blog via SSH
Il faut renseigner les variables SSH_* en début du Makefile.
Vous pourrez alors utiliser make scp_upload ou make rsync_upload. J'ai personnellement rajouté --exclude="drafts/" aux arguments de rsync pour ne pas mettre en ligne mes brouillons.
8.2 Publier son blog sur Github Pages
Github Pages propose d'héberger un site statique par repository, utilisateur ou organisation.
Dans mon cas, mon blog Pelican sera le site statique rattaché à mon utilisateur Github. Il sera hébergé à l'adresse http://nilshamerlinck.github.io.
Pour cela, il me faut juste créer le repository nilshamerlinck.github.io via l'interface de Github : https://github.com/new
Puis il me suffit de pusher le contenu du site dans la branche master de ce repository.
J'initialise donc mon repository en local :
$ git init
$ git remote add origin git@github.com:nilshamerlinck/nilshamerlinck.github.io.git
On installe le petit utilitaire ghp-import qui va gérer l'import de output/ dans le repository :
$ pip install ghp-import
Désormais, pour mettre le site en ligne, il suffira d'invoquer :
$ make github
À noter qu'il est même possible de faire pointer gratuitement un nom de domaine vers notre site statique [3].
8.3 Automatiser la publication
Une fois Pelican configuré aux petits oignons, on peut tout à fait imaginer automatiser le processus de publication.
L'idée c'est de stocker votre dossier content/ dans une Dropbox (ou équivalent), partagée sur un serveur. À chaque modification détectée par inotify, le serveur re-génére et re-transfère le site.
Une autre approche est celle de calepin.co, service gratuit et open source. Il utilise l'API Dropbox pour proposer du PaaS : Pelican-as-a-Service. Vous vous connectez avec votre compte Dropbox et vous générez votre blog Pelican en un clic à partir du dossier contenant vos articles en .md.
9. Dynamiser un peu notre blog statique
9.1 Commentaires
Pour permettre aux lecteurs d'apporter des commentaires, la solution est d'intégrer un widget JS tiers :
- Disqus.com : supporté de base par Pelican ;
- Facebook Comments [4] : widget officiel basé sur les identités Facebook ;
- Google+ Comments [5] : basé sur les identités Google ; l'API n'est pas officiellement ouverte mais le widget utilisé par Blogger.com fonctionne sur n'importe quel site. Le plugin Pelican s'appelle googleplus_comments.
9.2 Moteur de recherche
Pour permettre aux lecteurs de chercher des articles par mots-clés, deux approches sont possibles.
9.2.1 Service tiers : DuckDuckGo
La première consiste à se baser sur un moteur de recherche tiers : DuckDuckGo par exemple, ou Google via son service Custom Search Engine [6]. Cela revient à faire une requête dans leur index en limitant les résultats à votre domaine (ie site:http://nils.hamerlinck.fr). La prise en compte de vos nouveaux articles dépendra du bon vouloir de leurs robots crawlers.
Pour DuckDuckGo, l'assistant en ligne https://duckduckgo.com/search_box génère une iframe à intégrer dans votre thème. Mais il est aussi possible, en bidouillant un peu, d'utiliser un simple input stylé à votre convenance [7].
9.2.2 Côté client : Tipue Search
La deuxième approche consiste à utiliser un moteur de recherche côté client : Tipue Search [8], un plugin Jquery.
Il va se baser sur un index au format json que l'on va générer via le plugin Pelican tipue_search.
Conclusion
Une fois que l'on commence à bien maîtriser Pelican, on se dit qu'il est suffisamment simple et flexible pour servir de base à d'autres projets de publication. Un prochain article explorera son utilisation pour produire des PDF et des ebooks.
Références
[1] Outil Sitemaps dans les Google Webmasters Tools : https://www.google.com/webmasters/tools/sitemap-list
[2] Liste complète des signaux dans la documentation officielle de Pelican : http://docs.getpelican.com/en/latest/plugins.html
[3] Tutoriel pour faire pointer un nom de domaine vers son site statique Github Pages : https://help.github.com/articles/setting-up-a-custom-domain-with-github-pages/
[4] Assistant pour obtenir le code d'intégration de Facebook Comments : https://developers.facebook.com/docs/plugins/comments/
[5] Tutoriel pour intégrer Google+ Comments : http://browsingthenet.blogspot.com/2013/04/google-plus-comments-on-any-website.html
[6] Assistant pour intégrer un Google Custom Search Engine sur son site : https://cse.google.com/cse/
[7] Tutoriel pour intégrer DuckDuckGo sur son site, avec style : http://hardik.org/2013/06/01/stylising-duckduckgo-site-search/
[8] Site officiel de Tipue Search : http://www.tipue.com/search/